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