2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.mdsal.binding.dom.codec.gen.impl;
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static java.util.Objects.requireNonNull;
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.security.AccessController;
19 import java.security.PrivilegedAction;
20 import java.util.Map.Entry;
21 import javassist.CannotCompileException;
22 import javassist.CtClass;
23 import javassist.CtField;
24 import javassist.CtMethod;
25 import javassist.Modifier;
26 import javassist.NotFoundException;
27 import org.opendaylight.mdsal.binding.dom.codec.gen.spi.StaticConstantDefinition;
28 import org.opendaylight.mdsal.binding.dom.codec.util.AugmentableDispatchSerializer;
29 import org.opendaylight.mdsal.binding.generator.util.BindingRuntimeContext;
30 import org.opendaylight.mdsal.binding.generator.util.JavassistUtils;
31 import org.opendaylight.mdsal.binding.model.api.GeneratedType;
32 import org.opendaylight.mdsal.binding.model.util.Types;
33 import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections;
34 import org.opendaylight.yangtools.util.ClassLoaderUtils;
35 import org.opendaylight.yangtools.yang.binding.BindingStreamEventWriter;
36 import org.opendaylight.yangtools.yang.binding.DataContainer;
37 import org.opendaylight.yangtools.yang.binding.DataObject;
38 import org.opendaylight.yangtools.yang.binding.DataObjectSerializerImplementation;
39 import org.opendaylight.yangtools.yang.binding.DataObjectSerializerRegistry;
40 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
41 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
42 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
43 import org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus;
44 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
45 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
49 abstract class AbstractStreamWriterGenerator extends AbstractGenerator implements DataObjectSerializerGenerator {
50 private static final Logger LOG = LoggerFactory.getLogger(AbstractStreamWriterGenerator.class);
52 protected static final String SERIALIZE_METHOD_NAME = "serialize";
53 protected static final AugmentableDispatchSerializer AUGMENTABLE = new AugmentableDispatchSerializer();
54 private static final Field FIELD_MODIFIERS = getModifiersField();
56 private final LoadingCache<Class<?>, DataObjectSerializerImplementation> implementations;
57 private final CtClass[] serializeArguments;
58 private final JavassistUtils javassist;
59 private BindingRuntimeContext context;
61 private static Field getModifiersField() {
63 * Cache reflection access to field modifiers field. We need this to set
64 * fix the static declared fields to final once we initialize them. If we
65 * cannot get access, that's fine, too.
69 field = Field.class.getDeclaredField("modifiers");
70 } catch (NoSuchFieldException | SecurityException e) {
71 LOG.warn("Could not get modifiers field, serializers run at decreased efficiency", e);
76 AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
77 field.setAccessible(true);
80 } catch (SecurityException e) {
81 LOG.warn("Could not get access to modifiers field, serializers run at decreased efficiency", e);
88 protected AbstractStreamWriterGenerator(final JavassistUtils utils) {
89 this.javassist = requireNonNull(utils, "JavassistUtils instance is required.");
90 synchronized (javassist) {
91 this.serializeArguments = new CtClass[] {
92 javassist.asCtClass(DataObjectSerializerRegistry.class),
93 javassist.asCtClass(DataObject.class),
94 javassist.asCtClass(BindingStreamEventWriter.class),
96 javassist.appendClassLoaderIfMissing(DataObjectSerializerPrototype.class.getClassLoader());
98 this.implementations = CacheBuilder.newBuilder()
99 .removalListener(notification -> LOG.debug("onRemoval: cause={}, wasEvicted={}",
100 notification.getCause(), notification.wasEvicted()))
101 .weakKeys().build(new SerializerImplementationLoader());
102 LOG.debug("AbstractStreamWriterGenerator constructor, new instance: {}", this);
106 public final DataObjectSerializerImplementation getSerializer(final Class<?> type) {
107 return implementations.getUnchecked(type);
111 public final void onBindingRuntimeContextUpdated(final BindingRuntimeContext runtime) {
112 this.context = runtime;
113 LOG.debug("onBindingRuntimeContextUpdated() : {}", runtime);
117 protected final String loadSerializerFor(final Class<?> cls) {
118 return getSerializer(cls).getClass().getName();
121 private final class SerializerImplementationLoader
122 extends CacheLoader<Class<?>, DataObjectSerializerImplementation> {
124 private static final String GETINSTANCE_METHOD_NAME = "getInstance";
125 private static final String SERIALIZER_SUFFIX = "$StreamWriter";
127 private String getSerializerName(final Class<?> type) {
128 return type.getName() + SERIALIZER_SUFFIX;
132 @SuppressWarnings("unchecked")
133 public DataObjectSerializerImplementation load(final Class<?> type) throws Exception {
134 checkArgument(BindingReflections.isBindingClass(type));
135 checkArgument(DataContainer.class.isAssignableFrom(type),
136 "DataContainer is not assingnable from %s from classloader %s.", type, type.getClassLoader());
138 final String serializerName = getSerializerName(type);
140 Class<? extends DataObjectSerializerImplementation> cls;
142 cls = (Class<? extends DataObjectSerializerImplementation>) ClassLoaderUtils
143 .loadClass(type.getClassLoader(), serializerName);
144 } catch (final ClassNotFoundException e) {
145 cls = generateSerializer(type, serializerName);
148 final DataObjectSerializerImplementation obj =
149 (DataObjectSerializerImplementation) cls.getDeclaredMethod(GETINSTANCE_METHOD_NAME).invoke(null);
150 LOG.trace("Loaded serializer {} for class {}", obj, type);
154 private Class<? extends DataObjectSerializerImplementation> generateSerializer(final Class<?> type,
155 final String serializerName) throws CannotCompileException, IllegalAccessException,
156 IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException,
157 NoSuchFieldException {
158 LOG.debug("generateSerializer() due to Cache miss: typeName={}, typeClassLoader={}, serializerName={}",
159 type.getTypeName(), type.getClassLoader(), serializerName);
160 final DataObjectSerializerSource source = generateEmitterSource(type, serializerName);
161 final CtClass poolClass = generateEmitter0(type, source, serializerName);
162 final Class<? extends DataObjectSerializerImplementation> cls =
163 poolClass.toClass(type.getClassLoader(), type.getProtectionDomain())
164 .asSubclass(DataObjectSerializerImplementation.class);
167 * Due to OSGi class loader rules we cannot initialize the fields during
168 * construction, as the initializer expressions do not see our implementation
169 * classes. This should be almost as good as that, as we are resetting the
170 * fields to final before ever leaking the class.
172 for (final StaticConstantDefinition constant : source.getStaticConstants()) {
173 final Field field = cls.getDeclaredField(constant.getName());
174 field.setAccessible(true);
175 field.set(null, constant.getValue());
177 if (FIELD_MODIFIERS != null) {
178 FIELD_MODIFIERS.setInt(field, field.getModifiers() | Modifier.FINAL);
186 private DataObjectSerializerSource generateEmitterSource(final Class<?> type, final String serializerName) {
187 Types.typeForClass(type);
188 javassist.appendClassLoaderIfMissing(type.getClassLoader());
189 final Entry<GeneratedType, WithStatus> typeWithSchema = context.getTypeWithSchema(type);
190 final GeneratedType generatedType = typeWithSchema.getKey();
191 final WithStatus schema = typeWithSchema.getValue();
193 final DataObjectSerializerSource source;
194 if (schema instanceof ContainerSchemaNode) {
195 source = generateContainerSerializer(generatedType, (ContainerSchemaNode) schema);
196 } else if (schema instanceof ListSchemaNode) {
197 final ListSchemaNode casted = (ListSchemaNode) schema;
198 if (casted.getKeyDefinition().isEmpty()) {
199 source = generateUnkeyedListEntrySerializer(generatedType, casted);
201 source = generateMapEntrySerializer(generatedType, casted);
203 } else if (schema instanceof AugmentationSchemaNode) {
204 source = generateSerializer(generatedType,(AugmentationSchemaNode) schema);
205 } else if (schema instanceof CaseSchemaNode) {
206 source = generateCaseSerializer(generatedType,(CaseSchemaNode) schema);
207 } else if (schema instanceof NotificationDefinition) {
208 source = generateNotificationSerializer(generatedType,(NotificationDefinition) schema);
210 throw new UnsupportedOperationException("Schema type " + schema.getClass() + " is not supported");
215 private CtClass generateEmitter0(final Class<?> type, final DataObjectSerializerSource source,
216 final String serializerName) {
217 final CtClass product;
220 * getSerializerBody() has side effects, such as loading classes and codecs, it should be run in model class
221 * loader in order to correctly reference load child classes.
223 * Furthermore the fact that getSerializedBody() can trigger other code generation to happen, we need to take
224 * care of this before calling instantiatePrototype(), as that will call our customizer with the lock held,
225 * hence any code generation will end up being blocked on the javassist lock.
227 final String body = ClassLoaderUtils.getWithClassLoader(type.getClassLoader(), source::getSerializerBody)
231 product = javassist.instantiatePrototype(DataObjectSerializerPrototype.class.getName(), serializerName,
233 // Generate any static fields
234 for (final StaticConstantDefinition def : source.getStaticConstants()) {
235 final CtField field = new CtField(javassist.asCtClass(def.getType()), def.getName(), cls);
236 field.setModifiers(Modifier.PRIVATE + Modifier.STATIC);
240 // Replace serialize() -- may reference static fields
241 final CtMethod serializeTo = cls.getDeclaredMethod(SERIALIZE_METHOD_NAME, serializeArguments);
242 serializeTo.setBody(body);
244 // The prototype is not visible, so we need to take care of that
245 cls.setModifiers(Modifier.setPublic(cls.getModifiers()));
247 } catch (NotFoundException | CannotCompileException e) {
248 LOG.error("Failed to instatiate serializer {}", source, e);
249 throw new LinkageError("Unexpected instantation problem: serializer prototype not found", e);
255 * Generates serializer source code for supplied container node, which will read supplied binding type and invoke
256 * proper methods on supplied {@link BindingStreamEventWriter}.
259 * Implementation is required to recursively invoke events for all reachable binding objects.
261 * @param type Binding type of container
262 * @param node Schema of container
263 * @return Source for container node writer
265 protected abstract DataObjectSerializerSource generateContainerSerializer(GeneratedType type,
266 ContainerSchemaNode node);
269 * Generates serializer source for supplied case node, which will read supplied binding type and invoke proper
270 * methods on supplied {@link BindingStreamEventWriter}.
273 * Implementation is required to recursively invoke events for all reachable binding objects.
275 * @param type Binding type of case
276 * @param node Schema of case
277 * @return Source for case node writer
279 protected abstract DataObjectSerializerSource generateCaseSerializer(GeneratedType type, CaseSchemaNode node);
282 * Generates serializer source for supplied list node, which will read supplied binding type and invoke proper
283 * methods on supplied {@link BindingStreamEventWriter}.
286 * Implementation is required to recursively invoke events for all reachable binding objects.
288 * @param type Binding type of list
289 * @param node Schema of list
290 * @return Source for list node writer
292 protected abstract DataObjectSerializerSource generateMapEntrySerializer(GeneratedType type, ListSchemaNode node);
295 * Generates serializer source for supplied list node, which will read supplied binding type and invoke proper
296 * methods on supplied {@link BindingStreamEventWriter}.
299 * Implementation is required to recursively invoke events for all reachable binding objects.
301 * @param type Binding type of list
302 * @param node Schema of list
303 * @return Source for list node writer
305 protected abstract DataObjectSerializerSource generateUnkeyedListEntrySerializer(GeneratedType type,
306 ListSchemaNode node);
309 * Generates serializer source for supplied augmentation node, which will read supplied binding type and invoke
310 * proper methods on supplied {@link BindingStreamEventWriter}.
313 * Implementation is required to recursively invoke events for all reachable binding objects.
315 * @param type Binding type of augmentation
316 * @param schema Schema of augmentation
317 * @return Source for augmentation node writer
319 protected abstract DataObjectSerializerSource generateSerializer(GeneratedType type, AugmentationSchemaNode schema);
322 * Generates serializer source for notification node, which will read supplied binding type and invoke proper
323 * methods on supplied {@link BindingStreamEventWriter}.
326 * Implementation is required to recursively invoke events for all reachable binding objects.
328 * @param type Binding type of notification
329 * @param node Schema of notification
330 * @return Source for notification node writer
332 protected abstract DataObjectSerializerSource generateNotificationSerializer(GeneratedType type,
333 NotificationDefinition node);