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