8e34080d43b0e36d7033563a5c66219200699474
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / gen / impl / AbstractStreamWriterGenerator.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.mdsal.binding.dom.codec.gen.impl;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.cache.CacheBuilder;
14 import com.google.common.cache.CacheLoader;
15 import com.google.common.cache.LoadingCache;
16 import java.lang.reflect.Field;
17 import java.lang.reflect.InvocationTargetException;
18 import java.util.Map.Entry;
19 import javassist.CannotCompileException;
20 import javassist.CtClass;
21 import javassist.CtField;
22 import javassist.CtMethod;
23 import javassist.Modifier;
24 import javassist.NotFoundException;
25 import org.opendaylight.mdsal.binding.dom.codec.gen.spi.StaticConstantDefinition;
26 import org.opendaylight.mdsal.binding.dom.codec.util.AugmentableDispatchSerializer;
27 import org.opendaylight.mdsal.binding.generator.util.BindingRuntimeContext;
28 import org.opendaylight.mdsal.binding.generator.util.JavassistUtils;
29 import org.opendaylight.mdsal.binding.model.api.GeneratedType;
30 import org.opendaylight.mdsal.binding.model.util.Types;
31 import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections;
32 import org.opendaylight.yangtools.util.ClassLoaderUtils;
33 import org.opendaylight.yangtools.yang.binding.BindingStreamEventWriter;
34 import org.opendaylight.yangtools.yang.binding.DataContainer;
35 import org.opendaylight.yangtools.yang.binding.DataObject;
36 import org.opendaylight.yangtools.yang.binding.DataObjectSerializerImplementation;
37 import org.opendaylight.yangtools.yang.binding.DataObjectSerializerRegistry;
38 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
39 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
40 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
41 import org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus;
42 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
43 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 abstract class AbstractStreamWriterGenerator extends AbstractGenerator implements DataObjectSerializerGenerator {
48     private static final Logger LOG = LoggerFactory.getLogger(AbstractStreamWriterGenerator.class);
49
50     protected static final String SERIALIZE_METHOD_NAME = "serialize";
51     protected static final AugmentableDispatchSerializer AUGMENTABLE = new AugmentableDispatchSerializer();
52     private static final Field FIELD_MODIFIERS;
53
54     private final LoadingCache<Class<?>, DataObjectSerializerImplementation> implementations;
55     private final CtClass[] serializeArguments;
56     private final JavassistUtils javassist;
57     private BindingRuntimeContext context;
58
59     static {
60         /*
61          * Cache reflection access to field modifiers field. We need this to set
62          * fix the static declared fields to final once we initialize them. If we
63          * cannot get access, that's fine, too.
64          */
65         Field field = null;
66         try {
67             field = Field.class.getDeclaredField("modifiers");
68             field.setAccessible(true);
69         } catch (NoSuchFieldException | SecurityException e) {
70             LOG.warn("Could not get Field modifiers field, serializers run at decreased efficiency", e);
71         }
72
73         FIELD_MODIFIERS = field;
74     }
75
76     protected AbstractStreamWriterGenerator(final JavassistUtils utils) {
77         this.javassist = requireNonNull(utils, "JavassistUtils instance is required.");
78         synchronized (javassist) {
79             this.serializeArguments = new CtClass[] {
80                     javassist.asCtClass(DataObjectSerializerRegistry.class),
81                     javassist.asCtClass(DataObject.class),
82                     javassist.asCtClass(BindingStreamEventWriter.class),
83             };
84             javassist.appendClassLoaderIfMissing(DataObjectSerializerPrototype.class.getClassLoader());
85         }
86         this.implementations = CacheBuilder.newBuilder()
87                 .removalListener(notification -> LOG.debug("onRemoval: cause={}, wasEvicted={}",
88                         notification.getCause(), notification.wasEvicted()))
89                 .weakKeys().build(new SerializerImplementationLoader());
90         LOG.debug("AbstractStreamWriterGenerator constructor, new instance: {}", this);
91     }
92
93     @Override
94     public final DataObjectSerializerImplementation getSerializer(final Class<?> type) {
95         return implementations.getUnchecked(type);
96     }
97
98     @Override
99     public final void onBindingRuntimeContextUpdated(final BindingRuntimeContext runtime) {
100         this.context = runtime;
101         LOG.debug("onBindingRuntimeContextUpdated() : {}", runtime);
102     }
103
104     @Override
105     protected final String loadSerializerFor(final Class<?> cls) {
106         return getSerializer(cls).getClass().getName();
107     }
108
109     private final class SerializerImplementationLoader
110             extends CacheLoader<Class<?>, DataObjectSerializerImplementation> {
111
112         private static final String GETINSTANCE_METHOD_NAME = "getInstance";
113         private static final String SERIALIZER_SUFFIX = "$StreamWriter";
114
115         private String getSerializerName(final Class<?> type) {
116             return type.getName() + SERIALIZER_SUFFIX;
117         }
118
119         @Override
120         @SuppressWarnings("unchecked")
121         public DataObjectSerializerImplementation load(final Class<?> type) throws Exception {
122             checkArgument(BindingReflections.isBindingClass(type));
123             checkArgument(DataContainer.class.isAssignableFrom(type),
124                 "DataContainer is not assingnable from %s from classloader %s.", type, type.getClassLoader());
125
126             final String serializerName = getSerializerName(type);
127
128             Class<? extends DataObjectSerializerImplementation> cls;
129             try {
130                 cls = (Class<? extends DataObjectSerializerImplementation>) ClassLoaderUtils
131                         .loadClass(type.getClassLoader(), serializerName);
132             } catch (final ClassNotFoundException e) {
133                 cls = generateSerializer(type, serializerName);
134             }
135
136             final DataObjectSerializerImplementation obj =
137                     (DataObjectSerializerImplementation) cls.getDeclaredMethod(GETINSTANCE_METHOD_NAME).invoke(null);
138             LOG.trace("Loaded serializer {} for class {}", obj, type);
139             return obj;
140         }
141
142         private Class<? extends DataObjectSerializerImplementation> generateSerializer(final Class<?> type,
143                 final String serializerName) throws CannotCompileException, IllegalAccessException,
144                 IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException,
145                 NoSuchFieldException {
146             LOG.debug("generateSerializer() due to Cache miss: typeName={}, typeClassLoader={}, serializerName={}",
147                     type.getTypeName(), type.getClassLoader(), serializerName);
148             final DataObjectSerializerSource source = generateEmitterSource(type, serializerName);
149             final CtClass poolClass = generateEmitter0(type, source, serializerName);
150             final Class<? extends DataObjectSerializerImplementation> cls =
151                     poolClass.toClass(type.getClassLoader(), type.getProtectionDomain())
152                     .asSubclass(DataObjectSerializerImplementation.class);
153
154             /*
155              * Due to OSGi class loader rules we cannot initialize the fields during
156              * construction, as the initializer expressions do not see our implementation
157              * classes. This should be almost as good as that, as we are resetting the
158              * fields to final before ever leaking the class.
159              */
160             for (final StaticConstantDefinition constant : source.getStaticConstants()) {
161                 final Field field = cls.getDeclaredField(constant.getName());
162                 field.setAccessible(true);
163                 field.set(null, constant.getValue());
164
165                 if (FIELD_MODIFIERS != null) {
166                     FIELD_MODIFIERS.setInt(field, field.getModifiers() | Modifier.FINAL);
167                 }
168             }
169
170             return cls;
171         }
172     }
173
174     private DataObjectSerializerSource generateEmitterSource(final Class<?> type, final String serializerName) {
175         Types.typeForClass(type);
176         javassist.appendClassLoaderIfMissing(type.getClassLoader());
177         final Entry<GeneratedType, WithStatus> typeWithSchema = context.getTypeWithSchema(type);
178         final GeneratedType generatedType = typeWithSchema.getKey();
179         final WithStatus schema = typeWithSchema.getValue();
180
181         final DataObjectSerializerSource source;
182         if (schema instanceof ContainerSchemaNode) {
183             source = generateContainerSerializer(generatedType, (ContainerSchemaNode) schema);
184         } else if (schema instanceof ListSchemaNode) {
185             final ListSchemaNode casted = (ListSchemaNode) schema;
186             if (casted.getKeyDefinition().isEmpty()) {
187                 source = generateUnkeyedListEntrySerializer(generatedType, casted);
188             } else {
189                 source = generateMapEntrySerializer(generatedType, casted);
190             }
191         } else if (schema instanceof AugmentationSchemaNode) {
192             source = generateSerializer(generatedType,(AugmentationSchemaNode) schema);
193         } else if (schema instanceof CaseSchemaNode) {
194             source = generateCaseSerializer(generatedType,(CaseSchemaNode) schema);
195         } else if (schema instanceof NotificationDefinition) {
196             source = generateNotificationSerializer(generatedType,(NotificationDefinition) schema);
197         } else {
198             throw new UnsupportedOperationException("Schema type " + schema.getClass() + " is not supported");
199         }
200         return source;
201     }
202
203     private CtClass generateEmitter0(final Class<?> type, final DataObjectSerializerSource source,
204             final String serializerName) {
205         final CtClass product;
206
207         /*
208          * getSerializerBody() has side effects, such as loading classes and codecs, it should be run in model class
209          * loader in order to correctly reference load child classes.
210          *
211          * Furthermore the fact that getSerializedBody() can trigger other code generation to happen, we need to take
212          * care of this before calling instantiatePrototype(), as that will call our customizer with the lock held,
213          * hence any code generation will end up being blocked on the javassist lock.
214          */
215         final String body = ClassLoaderUtils.getWithClassLoader(type.getClassLoader(), source::getSerializerBody)
216                 .toString();
217
218         try {
219             product = javassist.instantiatePrototype(DataObjectSerializerPrototype.class.getName(), serializerName,
220                 cls -> {
221                     // Generate any static fields
222                     for (final StaticConstantDefinition def : source.getStaticConstants()) {
223                         final CtField field = new CtField(javassist.asCtClass(def.getType()), def.getName(), cls);
224                         field.setModifiers(Modifier.PRIVATE + Modifier.STATIC);
225                         cls.addField(field);
226                     }
227
228                     // Replace serialize() -- may reference static fields
229                     final CtMethod serializeTo = cls.getDeclaredMethod(SERIALIZE_METHOD_NAME, serializeArguments);
230                     serializeTo.setBody(body);
231
232                     // The prototype is not visible, so we need to take care of that
233                     cls.setModifiers(Modifier.setPublic(cls.getModifiers()));
234                 });
235         } catch (NotFoundException | CannotCompileException e) {
236             LOG.error("Failed to instatiate serializer {}", source, e);
237             throw new LinkageError("Unexpected instantation problem: serializer prototype not found", e);
238         }
239         return product;
240     }
241
242     /**
243      * Generates serializer source code for supplied container node, which will read supplied binding type and invoke
244      * proper methods on supplied {@link BindingStreamEventWriter}.
245      *
246      * <p>
247      * Implementation is required to recursively invoke events for all reachable binding objects.
248      *
249      * @param type Binding type of container
250      * @param node Schema of container
251      * @return Source for container node writer
252      */
253     protected abstract DataObjectSerializerSource generateContainerSerializer(GeneratedType type,
254             ContainerSchemaNode node);
255
256     /**
257      * Generates serializer source for supplied case node, which will read supplied binding type and invoke proper
258      * methods on supplied {@link BindingStreamEventWriter}.
259      *
260      * <p>
261      * Implementation is required to recursively invoke events for all reachable binding objects.
262      *
263      * @param type Binding type of case
264      * @param node Schema of case
265      * @return Source for case node writer
266      */
267     protected abstract DataObjectSerializerSource generateCaseSerializer(GeneratedType type, CaseSchemaNode node);
268
269     /**
270      * Generates serializer source for supplied list node, which will read supplied binding type and invoke proper
271      * methods on supplied {@link BindingStreamEventWriter}.
272      *
273      * <p>
274      * Implementation is required to recursively invoke events for all reachable binding objects.
275      *
276      * @param type Binding type of list
277      * @param node Schema of list
278      * @return Source for list node writer
279      */
280     protected abstract DataObjectSerializerSource generateMapEntrySerializer(GeneratedType type, ListSchemaNode node);
281
282     /**
283      * Generates serializer source for supplied list node, which will read supplied binding type and invoke proper
284      * methods on supplied {@link BindingStreamEventWriter}.
285      *
286      * <p>
287      * Implementation is required to recursively invoke events for all reachable binding objects.
288      *
289      * @param type Binding type of list
290      * @param node Schema of list
291      * @return Source for list node writer
292      */
293     protected abstract DataObjectSerializerSource generateUnkeyedListEntrySerializer(GeneratedType type,
294             ListSchemaNode node);
295
296     /**
297      * Generates serializer source for supplied augmentation node, which will read supplied binding type and invoke
298      * proper methods on supplied {@link BindingStreamEventWriter}.
299      *
300      * <p>
301      * Implementation is required to recursively invoke events for all reachable binding objects.
302      *
303      * @param type Binding type of augmentation
304      * @param schema Schema of augmentation
305      * @return Source for augmentation node writer
306      */
307     protected abstract DataObjectSerializerSource generateSerializer(GeneratedType type, AugmentationSchemaNode schema);
308
309     /**
310      * Generates serializer source for notification node, which will read supplied binding type and invoke proper
311      * methods on supplied {@link BindingStreamEventWriter}.
312      *
313      * <p>
314      * Implementation is required to recursively invoke events for all reachable binding objects.
315      *
316      * @param type Binding type of notification
317      * @param node Schema of notification
318      * @return Source for notification node writer
319      */
320     protected abstract DataObjectSerializerSource generateNotificationSerializer(GeneratedType type,
321             NotificationDefinition node);
322 }