80dec5a29ed465e0cc2ea4ab107cc36b5a107fd7
[yangtools.git] / code-generator / binding-generator-impl / src / main / java / org / opendaylight / yangtools / sal / binding / generator / impl / RuntimeGeneratedMappingServiceImpl.java
1 /**
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.sal.binding.generator.impl;
9
10 import java.util.AbstractMap.SimpleEntry;
11 import java.util.ArrayList;
12 import java.util.Collection;
13 import java.util.Collections;
14 import java.util.HashSet;
15 import java.util.LinkedList;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.Map.Entry;
19 import java.util.Set;
20 import java.util.concurrent.ConcurrentHashMap;
21 import java.util.concurrent.ConcurrentMap;
22 import java.util.concurrent.ExecutionException;
23
24 import javassist.ClassPool;
25
26 import javax.annotation.concurrent.GuardedBy;
27
28 import org.opendaylight.yangtools.binding.generator.util.BindingGeneratorUtil;
29 import org.opendaylight.yangtools.binding.generator.util.ReferencedTypeImpl;
30 import org.opendaylight.yangtools.binding.generator.util.Types;
31 import org.opendaylight.yangtools.sal.binding.generator.api.ClassLoadingStrategy;
32 import org.opendaylight.yangtools.sal.binding.generator.util.YangSchemaUtils;
33 import org.opendaylight.yangtools.sal.binding.model.api.Type;
34 import org.opendaylight.yangtools.sal.binding.model.api.type.builder.GeneratedTypeBuilder;
35 import org.opendaylight.yangtools.yang.binding.Augmentation;
36 import org.opendaylight.yangtools.yang.binding.BindingMapping;
37 import org.opendaylight.yangtools.yang.binding.DataContainer;
38 import org.opendaylight.yangtools.yang.binding.DataObject;
39 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.IdentifiableItem;
40 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.Item;
41 import org.opendaylight.yangtools.yang.binding.RpcService;
42 import org.opendaylight.yangtools.yang.common.QName;
43 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
44 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
45 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates;
46 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
47 import org.opendaylight.yangtools.yang.data.api.Node;
48 import org.opendaylight.yangtools.yang.data.impl.CompositeNodeTOImpl;
49 import org.opendaylight.yangtools.yang.data.impl.SimpleNodeTOImpl;
50 import org.opendaylight.yangtools.yang.data.impl.codec.AugmentationCodec;
51 import org.opendaylight.yangtools.yang.data.impl.codec.BindingIndependentMappingService;
52 import org.opendaylight.yangtools.yang.data.impl.codec.CodecRegistry;
53 import org.opendaylight.yangtools.yang.data.impl.codec.DataContainerCodec;
54 import org.opendaylight.yangtools.yang.data.impl.codec.DeserializationException;
55 import org.opendaylight.yangtools.yang.data.impl.codec.InstanceIdentifierCodec;
56 import org.opendaylight.yangtools.yang.data.impl.codec.ValueWithQName;
57 import org.opendaylight.yangtools.yang.model.api.Module;
58 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
59 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
60 import org.opendaylight.yangtools.yang.model.api.SchemaContextHolder;
61 import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
62 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
63 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
64 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
65 import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
66 import org.slf4j.Logger;
67 import org.slf4j.LoggerFactory;
68
69 import com.google.common.base.Optional;
70 import com.google.common.base.Preconditions;
71 import com.google.common.collect.HashMultimap;
72 import com.google.common.collect.Multimap;
73 import com.google.common.util.concurrent.SettableFuture;
74
75 public class RuntimeGeneratedMappingServiceImpl implements BindingIndependentMappingService, SchemaContextListener,
76         SchemaLock, AutoCloseable, SchemaContextHolder {
77
78     private static final Logger LOG = LoggerFactory.getLogger(RuntimeGeneratedMappingServiceImpl.class);
79
80     private final ConcurrentMap<Type, Type> typeDefinitions = new ConcurrentHashMap<>();
81     private final ConcurrentMap<Type, GeneratedTypeBuilder> typeToDefinition = new ConcurrentHashMap<>();
82     private final ConcurrentMap<Type, SchemaNode> typeToSchemaNode = new ConcurrentHashMap<>();
83     private final ConcurrentMap<Type, Set<QName>> serviceTypeToRpc = new ConcurrentHashMap<>();
84
85     /**
86      * This is map of types which users are waiting for.
87      */
88     @GuardedBy("this")
89     private final Multimap<Type, SettableFuture<Type>> promisedTypes = HashMultimap.create();
90
91     private final ClassLoadingStrategy classLoadingStrategy;
92
93     // FIXME: will become final
94     private ClassPool pool;
95     private TransformerGenerator binding;
96     private LazyGeneratedCodecRegistry registry;
97
98     private SchemaContext schemaContext;
99
100     @Deprecated
101     public RuntimeGeneratedMappingServiceImpl() {
102         this(GeneratedClassLoadingStrategy.getTCCLClassLoadingStrategy());
103     }
104
105     @Deprecated
106     public RuntimeGeneratedMappingServiceImpl(final ClassLoadingStrategy strat) {
107         classLoadingStrategy = strat;
108     }
109
110     public RuntimeGeneratedMappingServiceImpl(final ClassPool pool) {
111         this(pool, GeneratedClassLoadingStrategy.getTCCLClassLoadingStrategy());
112     }
113
114     public RuntimeGeneratedMappingServiceImpl(final ClassPool pool, final ClassLoadingStrategy strat) {
115         this.pool = Preconditions.checkNotNull(pool);
116         this.classLoadingStrategy = Preconditions.checkNotNull(strat);
117
118         // FIXME: merge into constructor once legacy init() is removed
119         doInit();
120     }
121
122     private void doInit() {
123         binding = new TransformerGenerator(pool);
124         registry = new LazyGeneratedCodecRegistry(this, classLoadingStrategy);
125
126         registry.setGenerator(binding);
127         // binding.staticFieldsInitializer = registry
128         binding.setListener(registry);
129         binding.setTypeToDefinition(typeToDefinition);
130         binding.setTypeToSchemaNode(typeToSchemaNode);
131         binding.setTypeDefinitions(typeDefinitions);
132
133         // if (ctx !== null) {
134         // listenerRegistration = ctx.registerService(SchemaServiceListener,
135         // this, new Hashtable<String, String>());
136         // }
137     }
138
139     @Deprecated
140     public void setPool(final ClassPool pool) {
141         this.pool = pool;
142     }
143
144     @Override
145     public synchronized SchemaContext getSchemaContext() {
146         return schemaContext;
147     }
148
149     @Override
150     public synchronized void onGlobalContextUpdated(final SchemaContext context) {
151         this.schemaContext = Preconditions.checkNotNull(context);
152         this.recreateBindingContext(context);
153         this.registry.onGlobalContextUpdated(context);
154     }
155
156     @GuardedBy("this")
157     private void recreateBindingContext(final SchemaContext schemaContext) {
158         BindingGeneratorImpl newBinding = new BindingGeneratorImpl();
159         newBinding.generateTypes(schemaContext);
160
161         for (Map.Entry<Module, ModuleContext> entry : newBinding.getModuleContexts().entrySet()) {
162
163             registry.onModuleContextAdded(schemaContext, entry.getKey(), entry.getValue());
164             binding.getPathToType().putAll(entry.getValue().getChildNodes());
165             Module module = entry.getKey();
166             ModuleContext context = entry.getValue();
167             updateBindingFor(context.getChildNodes(), schemaContext);
168             updateBindingFor(context.getCases(), schemaContext);
169             String namespace = BindingGeneratorUtil.moduleNamespaceToPackageName(module);
170
171             if (!module.getRpcs().isEmpty()) {
172                 Set<QName> rpcs = new HashSet<>();
173                 for (RpcDefinition rpc : module.getRpcs()) {
174                     rpcs.add(rpc.getQName());
175                 }
176                 Type serviceClass = new ReferencedTypeImpl(namespace, BindingMapping.getClassName(module.getName())
177                         + "Service");
178                 serviceTypeToRpc.put(serviceClass, rpcs);
179             }
180
181             Map<SchemaPath, Type> typedefs = context.getTypedefs();
182             for (Map.Entry<SchemaPath, Type> typedef : typedefs.entrySet()) {
183                 Type value = typedef.getValue();
184                 Type typeRef = new ReferencedTypeImpl(value.getPackageName(), value.getName());
185                 binding.getTypeDefinitions().put(typeRef, value);
186                 TypeDefinition<?> schemaNode = YangSchemaUtils.findTypeDefinition(schemaContext, typedef.getKey());
187                 if (schemaNode != null) {
188
189                     binding.getTypeToSchemaNode().put(typeRef, schemaNode);
190                 } else {
191                     LOG.error("Type definition for {} is not available", value);
192                 }
193             }
194             List<GeneratedTypeBuilder> augmentations = context.getAugmentations();
195             for (GeneratedTypeBuilder augmentation : augmentations) {
196                 binding.getTypeToDefinition().put(augmentation, augmentation);
197             }
198             binding.getTypeToAugmentation().putAll(context.getTypeToAugmentation());
199             for (GeneratedTypeBuilder augmentation : augmentations) {
200                 updatePromisedSchemas(augmentation);
201             }
202         }
203     }
204
205     @Override
206     public CompositeNode toDataDom(final DataObject data) {
207         return toCompositeNodeImpl(data);
208     }
209
210     @Override
211     public Entry<InstanceIdentifier, CompositeNode> toDataDom(
212             final Entry<org.opendaylight.yangtools.yang.binding.InstanceIdentifier<? extends DataObject>, DataObject> entry) {
213         try {
214             org.opendaylight.yangtools.yang.data.api.InstanceIdentifier key = toDataDom(entry.getKey());
215             CompositeNode data;
216             if (Augmentation.class.isAssignableFrom(entry.getKey().getTargetType())) {
217                 data = toCompositeNodeImplAugument(key, entry.getValue());
218             } else {
219                 data = toCompositeNodeImpl(key, entry.getValue());
220             }
221             return new SimpleEntry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, CompositeNode>(key,
222                     data);
223
224         } catch (Exception e) {
225             LOG.error("Error during serialization for {}.", entry.getKey(), e);
226             throw e;
227         }
228     }
229
230     private CompositeNode toCompositeNodeImpl(final DataObject object) {
231         Class<? extends DataContainer> cls = object.getImplementedInterface();
232         waitForSchema(cls);
233         DataContainerCodec<DataObject> codec = (DataContainerCodec<DataObject>) registry.getCodecForDataObject(cls);
234         return codec.serialize(new ValueWithQName<DataObject>(null, object));
235     }
236
237     private CompositeNode toCompositeNodeImpl(final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier identifier,
238             final DataObject object) {
239         PathArgument last = identifier.getPath().get(identifier.getPath().size() - 1);
240         Class<? extends DataContainer> cls = object.getImplementedInterface();
241         waitForSchema(cls);
242         DataContainerCodec<DataObject> codec = (DataContainerCodec<DataObject>) registry.getCodecForDataObject(cls);
243         return codec.serialize(new ValueWithQName<DataObject>(last.getNodeType(), object));
244     }
245
246     private CompositeNode toCompositeNodeImplAugument(
247             final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier identifier, final DataObject object) {
248
249         // val cls = object.implementedInterface;
250         // waitForSchema(cls);
251         org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument last = identifier.getPath().get(
252                 identifier.getPath().size() - 1);
253         AugmentationCodec codec = registry.getCodecForAugmentation((Class) object.getImplementedInterface());
254         CompositeNode ret = codec.serialize(new ValueWithQName<DataObject>(last.getNodeType(), object));
255         if (last instanceof NodeIdentifierWithPredicates) {
256             NodeIdentifierWithPredicates predicates = (NodeIdentifierWithPredicates) last;
257             List<Node<?>> newNodes = new ArrayList<Node<?>>(predicates.getKeyValues().size());
258             for (Map.Entry<QName, Object> predicate : predicates.getKeyValues().entrySet()) {
259                 newNodes.add(new SimpleNodeTOImpl<Object>(predicate.getKey(), null, predicate.getValue()));
260             }
261             newNodes.addAll(ret.getChildren());
262             return new CompositeNodeTOImpl(last.getNodeType(), null, newNodes);
263         }
264         return ret;
265     }
266
267     @Override
268     public void waitForSchema(final Class<?> cls) {
269         if (!registry.isCodecAvailable(cls)) {
270             final Type ref = Types.typeForClass(cls);
271             try {
272                 getSchemaWithRetry(ref);
273             } catch (InterruptedException | ExecutionException e) {
274                 LOG.warn("Waiting for schema for class {} failed", cls, e);
275                 throw new IllegalStateException(String.format("Failed to get schema for %s", cls), e);
276             }
277         }
278     }
279
280     @Override
281     public InstanceIdentifier toDataDom(
282             final org.opendaylight.yangtools.yang.binding.InstanceIdentifier<? extends DataObject> path) {
283         for (final org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument arg : path.getPathArguments()) {
284             this.waitForSchema(arg.getType());
285         }
286
287         final InstanceIdentifierCodec c = registry.getInstanceIdentifierCodec();
288         Preconditions.checkState(c != null, "InstanceIdentifierCodec not present");
289         return c.serialize(path);
290     }
291
292     @Override
293     public DataObject dataObjectFromDataDom(
294             final org.opendaylight.yangtools.yang.binding.InstanceIdentifier<? extends DataObject> path,
295             final CompositeNode domData) throws DeserializationException {
296         if (domData == null) {
297             return null;
298         }
299
300         try {
301             final Class<? extends DataContainer> container = path.getTargetType();
302             // FIXME: deprecate use without iid
303             final org.opendaylight.yangtools.yang.binding.InstanceIdentifier<? extends DataObject> wildcardedPath = createWildcarded(path);
304
305             final DataContainerCodec<? extends DataContainer> transformer = registry.getCodecForDataObject(container);
306             Preconditions.checkState(transformer != null, "Failed to find codec for type %s", container);
307
308             final ValueWithQName<? extends DataContainer> deserialize = transformer.deserialize(domData, wildcardedPath);
309             if (deserialize == null) {
310                 return null;
311             }
312
313             return (DataObject) deserialize.getValue();
314         } catch (Exception e) {
315             LOG.warn("Failed to deserialize path {} data {}", path, domData);
316             throw new DeserializationException("Data deserialization failed", e);
317         }
318     }
319
320     @Override
321     public org.opendaylight.yangtools.yang.binding.InstanceIdentifier<? extends Object> fromDataDom(final InstanceIdentifier entry) throws DeserializationException {
322         try {
323             final InstanceIdentifierCodec c = registry.getInstanceIdentifierCodec();
324             Preconditions.checkState(c != null, "InstanceIdentifierCodec not present");
325             return c.deserialize(entry);
326         } catch (Exception e) {
327             LOG.warn("Failed to deserialize entry {}", entry);
328             throw new DeserializationException("Entry deserialization failed", e);
329         }
330     }
331
332     @Override
333     public CodecRegistry getCodecRegistry() {
334         return this.registry;
335     }
336
337     private void updateBindingFor(final Map<SchemaPath, GeneratedTypeBuilder> map, final SchemaContext module) {
338         for (Map.Entry<SchemaPath, GeneratedTypeBuilder> entry : map.entrySet()) {
339             SchemaNode schemaNode = SchemaContextUtil.findDataSchemaNode(module, entry.getKey());
340
341             // LOG.info("{} : {}",entry.key,entry.value.fullyQualifiedName)
342             Type typeRef = new ReferencedTypeImpl(entry.getValue().getPackageName(), entry.getValue().getName());
343             typeToDefinition.put(typeRef, entry.getValue());
344             if (schemaNode != null) {
345                 typeToSchemaNode.put(typeRef, schemaNode);
346                 updatePromisedSchemas(entry.getValue());
347             }
348         }
349     }
350
351     @Deprecated
352     public void init() {
353         doInit();
354     }
355
356     @Override
357     public Set<QName> getRpcQNamesFor(final Class<? extends RpcService> service) {
358         Set<QName> serviceRef = serviceTypeToRpc.get(new ReferencedTypeImpl(service.getPackage().getName(), service
359                 .getSimpleName()));
360         if (serviceRef == null) {
361             serviceRef = Collections.emptySet();
362         }
363         return serviceRef;
364     }
365
366     private void getSchemaWithRetry(final Type type) throws InterruptedException, ExecutionException {
367         final SettableFuture<Type> f;
368
369         synchronized (this) {
370             if (typeToDefinition.containsKey(type)) {
371                 return;
372             }
373
374             LOG.info("Thread blocked waiting for schema for: {}", type.getFullyQualifiedName());
375             f = SettableFuture.create();
376             promisedTypes.put(type, f);
377         }
378
379         f.get();
380         LOG.info("Schema for {} became available, thread unblocked", type.getFullyQualifiedName());
381     }
382
383     @GuardedBy("this")
384     private void updatePromisedSchemas(final Type builder) {
385         final Type ref = new ReferencedTypeImpl(builder.getPackageName(), builder.getName());
386         final Collection<SettableFuture<Type>> futures = promisedTypes.get(ref);
387
388         if (futures != null) {
389             for (SettableFuture<Type> future : futures) {
390                 future.set(builder);
391             }
392             promisedTypes.removeAll(builder);
393         }
394     }
395
396     @Override
397     public void close() {
398         // Nothing to do
399     }
400
401     @Override
402     public DataContainer dataObjectFromDataDom(final Class<? extends DataContainer> container,
403             final CompositeNode domData) {
404         // FIXME: Add check for valids inputs
405         // which are Notification and Rpc Input / Rpc Output
406
407         org.opendaylight.yangtools.yang.binding.InstanceIdentifier<? extends DataContainer> id = org.opendaylight.yangtools.yang.binding.InstanceIdentifier
408                 .create((Class) container);
409         Preconditions.checkNotNull(id, "Failed to create path for type %s", container);
410
411         try {
412             return dataObjectFromDataDom(id, domData);
413         } catch (DeserializationException e) {
414             LOG.warn("Conversion of class {} path {} data {} failed", container, id, domData, e);
415             throw new IllegalStateException("Failed to create data object", e);
416         }
417     }
418
419     @Override
420     public synchronized Optional<Class<? extends RpcService>> getRpcServiceClassFor(final String namespace, final String revision) {
421         Module module = null;
422         if (schemaContext != null) {
423             module = schemaContext.findModuleByName(namespace, QName.parseRevision(revision));
424         }
425         if (module == null) {
426             return Optional.absent();
427         }
428         try {
429             Optional<Type> rpcTypeName = getRpcServiceType(module);
430             if (rpcTypeName.isPresent()) {
431                 Class<?> rpcClass = binding.getClassLoadingStrategy().loadClass(
432                         rpcTypeName.get().getFullyQualifiedName());
433                 return Optional.<Class<? extends RpcService>> of((Class<? extends RpcService>) rpcClass);
434             }
435         } catch (Exception e) {
436             LOG.debug("RPC class not present for {},{}", namespace, revision, e);
437         }
438         return Optional.absent();
439     }
440
441     public Optional<Type> getRpcServiceType(final Module module) {
442         String namespace = BindingGeneratorUtil.moduleNamespaceToPackageName(module);
443         if (module.getRpcs().isEmpty()) {
444             return Optional.<Type> absent();
445         }
446         return Optional.<Type> of(new ReferencedTypeImpl(namespace, BindingMapping.getClassName(module.getName())
447                 + BindingMapping.RPC_SERVICE_SUFFIX));
448     }
449
450     @SuppressWarnings({ "rawtypes", "unchecked" })
451     private static final org.opendaylight.yangtools.yang.binding.InstanceIdentifier<? extends DataObject> createWildcarded(
452             final org.opendaylight.yangtools.yang.binding.InstanceIdentifier<? extends DataObject> path) {
453
454         LinkedList<org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument> wildcardedArgs = new LinkedList<>();
455         for(org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument pathArg : path.getPathArguments()) {
456             if(pathArg instanceof IdentifiableItem<?,?>) {
457                 pathArg = new Item(pathArg.getType());
458             }
459             wildcardedArgs.add(pathArg);
460         }
461         return org.opendaylight.yangtools.yang.binding.InstanceIdentifier.create(wildcardedArgs);
462     }
463 }