From: Tony Tkacik Date: Tue, 28 Jan 2014 16:43:08 +0000 (+0100) Subject: Added support for discovering YangModuleInfo via ServiceLoader. X-Git-Tag: release/helium~719 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=refs%2Fchanges%2F57%2F5157%2F5;p=yangtools.git Added support for discovering YangModuleInfo via ServiceLoader. - Added ModuleInfoBackedContext which provides - GeneratedClassLoadingStrategy which supports proactive search - Creation of SchemaContext based on registered YangModuleInfo Change-Id: I890c275031151b874b42b4e46bee4edcfce595ec Signed-off-by: Tony Tkacik --- diff --git a/code-generator/binding-generator-impl/src/main/java/org/opendaylight/yangtools/sal/binding/generator/impl/GeneratedClassLoadingStrategy.java b/code-generator/binding-generator-impl/src/main/java/org/opendaylight/yangtools/sal/binding/generator/impl/GeneratedClassLoadingStrategy.java index 89c9fa2396..104245a235 100644 --- a/code-generator/binding-generator-impl/src/main/java/org/opendaylight/yangtools/sal/binding/generator/impl/GeneratedClassLoadingStrategy.java +++ b/code-generator/binding-generator-impl/src/main/java/org/opendaylight/yangtools/sal/binding/generator/impl/GeneratedClassLoadingStrategy.java @@ -14,6 +14,14 @@ public abstract class GeneratedClassLoadingStrategy { private static final GeneratedClassLoadingStrategy TCCL_STRATEGY = new TCCLClassLoadingStrategy(); + private static final GeneratedClassLoadingStrategy ALWAYS_FAIL_STRATEGY = new GeneratedClassLoadingStrategy() { + + @Override + public Class loadClass(String fullyQualifiedName) throws ClassNotFoundException { + throw new ClassNotFoundException(fullyQualifiedName); + } + }; + public Class loadClass(Type type) throws ClassNotFoundException { return loadClass(type.getFullyQualifiedName()); } @@ -24,6 +32,10 @@ public abstract class GeneratedClassLoadingStrategy { return TCCL_STRATEGY; } + public static final GeneratedClassLoadingStrategy getAlwaysFailClassLoadingStrategy() { + return ALWAYS_FAIL_STRATEGY; + } + private static final class TCCLClassLoadingStrategy extends GeneratedClassLoadingStrategy { @Override diff --git a/code-generator/binding-generator-impl/src/main/java/org/opendaylight/yangtools/sal/binding/generator/impl/ModuleInfoBackedContext.java b/code-generator/binding-generator-impl/src/main/java/org/opendaylight/yangtools/sal/binding/generator/impl/ModuleInfoBackedContext.java new file mode 100644 index 0000000000..0a8c2dcea0 --- /dev/null +++ b/code-generator/binding-generator-impl/src/main/java/org/opendaylight/yangtools/sal/binding/generator/impl/ModuleInfoBackedContext.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.sal.binding.generator.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.ref.WeakReference; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.opendaylight.yangtools.concepts.AbstractObjectRegistration; +import org.opendaylight.yangtools.concepts.Registration; +import org.opendaylight.yangtools.sal.binding.generator.util.ClassLoaderUtils; +import org.opendaylight.yangtools.yang.binding.YangModuleInfo; +import org.opendaylight.yangtools.yang.binding.util.BindingReflections; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.util.repo.AdvancedSchemaSourceProvider; +import org.opendaylight.yangtools.yang.model.util.repo.SourceIdentifier; +import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; + +public class ModuleInfoBackedContext extends GeneratedClassLoadingStrategy // + implements // + AdvancedSchemaSourceProvider { + + private ModuleInfoBackedContext(GeneratedClassLoadingStrategy loadingStrategy) { + this.backingLoadingStrategy = loadingStrategy; + } + + public static ModuleInfoBackedContext create() { + return new ModuleInfoBackedContext(getTCCLClassLoadingStrategy()); + } + + public static ModuleInfoBackedContext create(GeneratedClassLoadingStrategy loadingStrategy) { + return new ModuleInfoBackedContext(loadingStrategy); + } + + private static final Logger LOG = LoggerFactory.getLogger(ModuleInfoBackedContext.class); + + private final ConcurrentMap> packageNameToClassLoader = new ConcurrentHashMap<>(); + private final ConcurrentMap sourceIdentifierToModuleInfo = new ConcurrentHashMap<>(); + + private final GeneratedClassLoadingStrategy backingLoadingStrategy; + + @Override + public Class loadClass(String fullyQualifiedName) throws ClassNotFoundException { + String modulePackageName = BindingReflections.getModelRootPackageName(fullyQualifiedName); + + WeakReference classLoaderRef = packageNameToClassLoader.get(modulePackageName); + ClassLoader classloader = null; + if (classLoaderRef != null && (classloader = classLoaderRef.get()) != null) { + return ClassLoaderUtils.loadClass(classloader, fullyQualifiedName); + } + if (backingLoadingStrategy == null) { + throw new ClassNotFoundException(fullyQualifiedName); + } + Class cls = backingLoadingStrategy.loadClass(fullyQualifiedName); + if (BindingReflections.isBindingClass(cls)) { + boolean newModule = resolveModuleInfo(cls); + if (newModule) { + recreateSchemaContext(); + } + } + return cls; + } + + private synchronized Optional recreateSchemaContext() { + try { + ImmutableList streams = getAvailableStreams(); + YangParserImpl parser = new YangParserImpl(); + Set modules = parser.parseYangModelsFromStreams(streams); + SchemaContext schemaContext = parser.resolveSchemaContext(modules); + return Optional.of(schemaContext); + } catch (IOException e) { + LOG.error("Schema was not recreated.",e); + } + return Optional.absent(); + } + + public synchronized Optional tryToCreateSchemaContext() { + return recreateSchemaContext(); + } + + private ImmutableList getAvailableStreams() throws IOException { + ImmutableSet moduleInfos = ImmutableSet.copyOf(sourceIdentifierToModuleInfo.values()); + + ImmutableList.Builder sourceStreams = ImmutableList. builder(); + for (YangModuleInfo moduleInfo : moduleInfos) { + sourceStreams.add(moduleInfo.getModuleSourceStream()); + } + return sourceStreams.build(); + } + + private boolean resolveModuleInfo(Class cls) { + try { + return resolveModuleInfo(BindingReflections.getModuleInfo(cls)); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + private boolean resolveModuleInfo(YangModuleInfo moduleInfo) { + + SourceIdentifier identifier = sourceIdentifierFrom(moduleInfo); + YangModuleInfo previous = sourceIdentifierToModuleInfo.putIfAbsent(identifier, moduleInfo); + ClassLoader moduleClassLoader = moduleInfo.getClass().getClassLoader(); + if (previous == null) { + String modulePackageName = moduleInfo.getClass().getPackage().getName(); + packageNameToClassLoader.putIfAbsent(modulePackageName, new WeakReference(moduleClassLoader)); + + for (YangModuleInfo importedInfo : moduleInfo.getImportedModules()) { + resolveModuleInfo(importedInfo); + } + } else { + return false; + } + return true; + } + + private SourceIdentifier sourceIdentifierFrom(YangModuleInfo moduleInfo) { + return SourceIdentifier.create(moduleInfo.getName(), Optional.of(moduleInfo.getRevision())); + } + + public void addModuleInfos(Iterable moduleInfos) { + for (YangModuleInfo yangModuleInfo : moduleInfos) { + registerModuleInfo(yangModuleInfo); + } + } + + private Registration registerModuleInfo(YangModuleInfo yangModuleInfo) { + YangModuleInfoRegistration registration = new YangModuleInfoRegistration(yangModuleInfo, this); + + resolveModuleInfo(yangModuleInfo); + + return registration; + } + + @Override + public Optional getSchemaSource(SourceIdentifier sourceIdentifier) { + YangModuleInfo info = sourceIdentifierToModuleInfo.get(sourceIdentifier); + if (info == null) { + return Optional.absent(); + } + try { + return Optional.of(info.getModuleSourceStream()); + } catch (IOException e) { + return Optional.absent(); + } + } + + @Override + public Optional getSchemaSource(String moduleName, Optional revision) { + return getSchemaSource(SourceIdentifier.create(moduleName, revision)); + } + + private static class YangModuleInfoRegistration extends AbstractObjectRegistration { + + private final ModuleInfoBackedContext context; + + public YangModuleInfoRegistration(YangModuleInfo instance, ModuleInfoBackedContext context) { + super(instance); + this.context = context; + } + + @Override + protected void removeRegistration() { + context.remove(this); + } + + } + + private void remove(YangModuleInfoRegistration registration) { + + } +} diff --git a/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/YangModuleInfoTemplate.xtend b/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/YangModuleInfoTemplate.xtend index 7c19f20a9d..57bcc3bfa7 100644 --- a/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/YangModuleInfoTemplate.xtend +++ b/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/yangtools/sal/java/api/generator/YangModuleInfoTemplate.xtend @@ -30,50 +30,69 @@ import org.opendaylight.yangtools.yang.model.api.Module import org.opendaylight.yangtools.yang.model.api.SchemaContext import com.google.common.collect.ImmutableSet +import static org.opendaylight.yangtools.yang.binding.BindingMapping.* +import org.opendaylight.yangtools.yang.binding.YangModelBindingProvider +import com.google.common.base.Preconditions class YangModuleInfoTemplate { - val CLASS = "$YangModuleInfoImpl" val Module module val SchemaContext ctx val Map importMap = new LinkedHashMap() - new (Module module, SchemaContext ctx) { - if (module == null) { - throw new IllegalArgumentException("Module reference cannot be NULL!") - } + @Property + val String packageName; + + @Property + val String modelBindingProviderName; + + new(Module module, SchemaContext ctx) { + Preconditions.checkArgument(module != null, "Module must not be null."); this.module = module this.ctx = ctx + _packageName = BindingGeneratorUtil.moduleNamespaceToPackageName(module); + _modelBindingProviderName = '''«packageName».«MODEL_BINDING_PROVIDER_CLASS_NAME»'''; } def String generate() { - val String classBody = body().toString - ''' - package «BindingGeneratorUtil.moduleNamespaceToPackageName(module)» ; + val body = ''' + public final class «MODULE_INFO_CLASS_NAME» implements «YangModuleInfo.importedName» { + + private static final «YangModuleInfo.importedName» INSTANCE = new «MODULE_INFO_CLASS_NAME»(); - «imports» + private final Set importedModules; - «classBody» + public static «YangModuleInfo.importedName» getInstance() { + return INSTANCE; + } + + «module.classBody» + } + ''' + return ''' + + package «packageName» ; + «imports» + «body» '''.toString } - def body() ''' - public final class «CLASS» implements «YangModuleInfo.importedName» { - - private static final «YangModuleInfo.importedName» INSTANCE = new «CLASS»(); + def String generateModelProvider() { + ''' + package «packageName»; - private final Set importedModules; + public final class «MODEL_BINDING_PROVIDER_CLASS_NAME» implements «YangModelBindingProvider.name» { - public static «YangModuleInfo.importedName» getInstance() { - return INSTANCE; + public «YangModuleInfo.name» getModuleInfo() { + return «MODULE_INFO_CLASS_NAME».getInstance(); + } } + ''' - «module.classBody» - } - ''' + } private def CharSequence classBody(Module m) ''' - private «CLASS»() { + private «MODULE_INFO_CLASS_NAME»() { «IF m.imports.size != 0» «Set.importedName»<«YangModuleInfo.importedName»> set = new «HashSet.importedName»<>(); «FOR imp : m.imports» @@ -87,24 +106,23 @@ class YangModuleInfoTemplate { «sorted.put(module.revision, module)» «ENDIF» «ENDFOR» - set.add(«BindingGeneratorUtil.moduleNamespaceToPackageName(sorted.lastEntry().value)».«CLASS».getInstance()); + set.add(«BindingGeneratorUtil.moduleNamespaceToPackageName(sorted.lastEntry().value)».«MODULE_INFO_CLASS_NAME».getInstance()); «ELSE» - set.add(«BindingGeneratorUtil.moduleNamespaceToPackageName(ctx.findModuleByName(name, rev))».«CLASS».getInstance()); + set.add(«BindingGeneratorUtil.moduleNamespaceToPackageName(ctx.findModuleByName(name, rev))».«MODULE_INFO_CLASS_NAME».getInstance()); «ENDIF» «ENDFOR» importedModules = «ImmutableSet.importedName».copyOf(set); «ELSE» importedModules = «Collections.importedName».emptySet(); «ENDIF» - - «InputStream.importedName» stream = «CLASS».class.getResourceAsStream("«sourcePath»"); + «InputStream.importedName» stream = «MODULE_INFO_CLASS_NAME».class.getResourceAsStream("«sourcePath»"); if (stream == null) { throw new IllegalStateException("Resource «sourcePath» is missing"); } try { stream.close(); } catch («IOException.importedName» e) { - // Resource leak, but there is nothing we can do + // Resource leak, but there is nothing we can do } } @@ -126,7 +144,7 @@ class YangModuleInfoTemplate { @Override public «InputStream.importedName» getModuleSourceStream() throws IOException { - «InputStream.importedName» stream = «CLASS».class.getResourceAsStream("«sourcePath»"); + «InputStream.importedName» stream = «MODULE_INFO_CLASS_NAME».class.getResourceAsStream("«sourcePath»"); if (stream == null) { throw new «IOException.importedName»("Resource «sourcePath» is missing"); } @@ -138,7 +156,7 @@ class YangModuleInfoTemplate { return importedModules; } ''' - + def getSourcePath() { return "/" + module.moduleSourcePath.replace(java.io.File.separatorChar, '/') } @@ -151,7 +169,6 @@ class YangModuleInfoTemplate { «ENDIF» «ENDFOR» «ENDIF» - ''' final protected def importedName(Class cls) { @@ -226,7 +243,7 @@ class YangModuleInfoTemplate { return "?"; } val StringBuilder builder = new StringBuilder(); - + var int i = 0; for (pType : pTypes) { val Type t = pTypes.get(i) diff --git a/code-generator/maven-sal-api-gen-plugin/src/main/java/org/opendaylight/yangtools/maven/sal/api/gen/plugin/CodeGeneratorImpl.java b/code-generator/maven-sal-api-gen-plugin/src/main/java/org/opendaylight/yangtools/maven/sal/api/gen/plugin/CodeGeneratorImpl.java index 9320e5af20..704596ec74 100644 --- a/code-generator/maven-sal-api-gen-plugin/src/main/java/org/opendaylight/yangtools/maven/sal/api/gen/plugin/CodeGeneratorImpl.java +++ b/code-generator/maven-sal-api-gen-plugin/src/main/java/org/opendaylight/yangtools/maven/sal/api/gen/plugin/CodeGeneratorImpl.java @@ -27,6 +27,8 @@ import org.opendaylight.yangtools.sal.binding.generator.impl.BindingGeneratorImp import org.opendaylight.yangtools.sal.binding.model.api.Type; import org.opendaylight.yangtools.sal.java.api.generator.GeneratorJavaFile; import org.opendaylight.yangtools.sal.java.api.generator.YangModuleInfoTemplate; +import org.opendaylight.yangtools.yang.binding.BindingMapping; +import org.opendaylight.yangtools.yang.binding.YangModelBindingProvider; import org.opendaylight.yangtools.yang.model.api.Module; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.opendaylight.yangtools.yang2sources.spi.BuildContextAware; @@ -35,7 +37,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonatype.plexus.build.incremental.BuildContext; +import com.google.common.base.Joiner; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSet.Builder; public final class CodeGeneratorImpl implements CodeGenerator, BuildContextAware { private static final String FS = File.separator; @@ -45,6 +50,7 @@ public final class CodeGeneratorImpl implements CodeGenerator, BuildContextAware private static final Logger logger = LoggerFactory.getLogger(CodeGeneratorImpl.class); private MavenProject mavenProject; + private File resourceBaseDir; @Override public Collection generateSources(final SchemaContext context, final File outputDir, @@ -69,11 +75,32 @@ public final class CodeGeneratorImpl implements CodeGenerator, BuildContextAware } List result = generator.generateToFile(outputBaseDir, persistentSourcesDir); + + result.addAll(generateModuleInfos(outputBaseDir, yangModules, context)); + return result; + } + + private Collection generateModuleInfos(File outputBaseDir, Set yangModules, + SchemaContext context) { + Builder result = ImmutableSet.builder(); + Builder bindingProviders = ImmutableSet.builder(); for (Module module : yangModules) { - // TODO: add YangModuleInfo class - result.add(generateYangModuleInfo(outputBaseDir, module, context)); + result.addAll(generateYangModuleInfo(outputBaseDir, module, context, bindingProviders)); } - return result; + + result.add(writeMetaInfServices(resourceBaseDir, YangModelBindingProvider.class, bindingProviders.build())); + return result.build(); + } + + private File writeMetaInfServices(File outputBaseDir, Class serviceClass, + ImmutableSet services) { + File metainfServicesFolder = new File(outputBaseDir, "META-INF" + File.separator + "services"); + metainfServicesFolder.mkdirs(); + File serviceFile = new File(metainfServicesFolder, serviceClass.getName()); + + String src = Joiner.on('\n').join(services); + + return writeFile(serviceFile, src); } public static final String DEFAULT_OUTPUT_BASE_DIR_PATH = "target" + File.separator + "generated-sources" @@ -93,7 +120,8 @@ public final class CodeGeneratorImpl implements CodeGenerator, BuildContextAware } @Override - public void setLog(Log log) {} + public void setLog(Log log) { + } @Override public void setAdditionalConfig(Map additionalConfiguration) { @@ -102,7 +130,7 @@ public final class CodeGeneratorImpl implements CodeGenerator, BuildContextAware @Override public void setResourceBaseDir(File resourceBaseDir) { - // no resource processing necessary + this.resourceBaseDir = resourceBaseDir; } @Override @@ -116,29 +144,48 @@ public final class CodeGeneratorImpl implements CodeGenerator, BuildContextAware this.buildContext = Preconditions.checkNotNull(buildContext); } - private File generateYangModuleInfo(File outputBaseDir, Module module, SchemaContext ctx) { + private Set generateYangModuleInfo(File outputBaseDir, Module module, SchemaContext ctx, + Builder providerSourceSet) { + Builder generatedFiles = ImmutableSet. builder(); + final YangModuleInfoTemplate template = new YangModuleInfoTemplate(module, ctx); - String generatedCode = template.generate(); - if (generatedCode.isEmpty()) { + String moduleInfoSource = template.generate(); + if (moduleInfoSource.isEmpty()) { throw new IllegalStateException("Generated code should not be empty!"); } + String providerSource = template.generateModelProvider(); + + final File packageDir = GeneratorJavaFile.packageToDirectory(outputBaseDir, + BindingGeneratorUtil.moduleNamespaceToPackageName(module)); + + generatedFiles.add(writeJavaSource(packageDir, BindingMapping.MODULE_INFO_CLASS_NAME, moduleInfoSource)); + generatedFiles + .add(writeJavaSource(packageDir, BindingMapping.MODEL_BINDING_PROVIDER_CLASS_NAME, providerSource)); + providerSourceSet.add(template.getModelBindingProviderName()); + + return generatedFiles.build(); - final File packageDir = GeneratorJavaFile.packageToDirectory(outputBaseDir, BindingGeneratorUtil.moduleNamespaceToPackageName(module)); + } - final File file = new File(packageDir, "$YangModuleInfoImpl.java"); + private File writeJavaSource(File packageDir, String className, String source) { + final File file = new File(packageDir, className + ".java"); + writeFile(file, source); + return file; + } + + private File writeFile(File file, String source) { try (final OutputStream stream = buildContext.newFileOutputStream(file)) { try (final Writer fw = new OutputStreamWriter(stream)) { try (final BufferedWriter bw = new BufferedWriter(fw)) { - bw.write(generatedCode); + bw.write(source); } } catch (Exception e) { - // TODO handle exception + logger.error("Could not write file: {}",file,e); } } catch (Exception e) { - // TODO handle exception + logger.error("Could not create file: {}",file,e); } return file; - } } diff --git a/integration-test/pom.xml b/integration-test/pom.xml new file mode 100644 index 0000000000..60800929ea --- /dev/null +++ b/integration-test/pom.xml @@ -0,0 +1,95 @@ + + + + + + + org.opendaylight.yangtools + yangtools + 0.6.2-SNAPSHOT + + + 4.0.0 + integration-tests + pom + + + yang-runtime-tests + + + + + + org.opendaylight.yangtools + yang-common + ${project.version} + + + org.opendaylight.yangtools + yang-data-api + ${project.version} + + + org.opendaylight.yangtools + yang-data-util + ${project.version} + + + org.opendaylight.yangtools + yang-model-api + ${project.version} + + + org.opendaylight.yangtools + yang-model-util + ${project.version} + + + org.opendaylight.yangtools + yang-binding + ${project.version} + + + org.opendaylight.yangtools + yang-parser-api + ${project.version} + + + org.opendaylight.yangtools + yang-parser-impl + ${project.version} + + + org.opendaylight.yangtools + yang-maven-plugin + ${project.version} + + + org.opendaylight.yangtools + yang-maven-plugin-spi + ${project.version} + + + + + org.apache.maven + maven-core + 3.0.5 + + + org.apache.maven + maven-plugin-api + 3.0.5 + + + org.opendaylight.yangtools + binding-generator-impl + ${project.version} + + + + diff --git a/integration-test/yang-runtime-tests/pom.xml b/integration-test/yang-runtime-tests/pom.xml new file mode 100644 index 0000000000..0d6cbc4b83 --- /dev/null +++ b/integration-test/yang-runtime-tests/pom.xml @@ -0,0 +1,45 @@ + + + + + 4.0.0 + + org.opendaylight.yangtools + integration-tests + 0.6.2-SNAPSHOT + + yang-runtime-tests + bundle + + + + org.opendaylight.yangtools + yang-binding + + + org.opendaylight.yangtools + binding-generator-impl + + + org.opendaylight.yangtools + yang-parser-impl + + + org.opendaylight.yangtools.model + ietf-topology + 2013.10.21.2-SNAPSHOT + + + junit + junit + + + org.mockito + mockito-all + + + diff --git a/integration-test/yang-runtime-tests/src/test/java/org/opendaylight/yangtools/it/yang/runtime/tests/ModelDiscoveryTest.java b/integration-test/yang-runtime-tests/src/test/java/org/opendaylight/yangtools/it/yang/runtime/tests/ModelDiscoveryTest.java new file mode 100644 index 0000000000..d9944bc8fd --- /dev/null +++ b/integration-test/yang-runtime-tests/src/test/java/org/opendaylight/yangtools/it/yang/runtime/tests/ModelDiscoveryTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.it.yang.runtime.tests; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.net.URL; +import java.util.Enumeration; +import java.util.Vector; + +import org.junit.Test; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Link; +import org.opendaylight.yangtools.sal.binding.generator.impl.GeneratedClassLoadingStrategy; +import org.opendaylight.yangtools.sal.binding.generator.impl.ModuleInfoBackedContext; +import org.opendaylight.yangtools.yang.binding.YangModelBindingProvider; +import org.opendaylight.yangtools.yang.binding.YangModuleInfo; +import org.opendaylight.yangtools.yang.binding.util.BindingReflections; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; + +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableSet; + +public class ModelDiscoveryTest { + + public static final YangModuleInfo TOPOLOGY_OLD_MODULE = org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev130712.$YangModuleInfoImpl + .getInstance(); + public static final YangModuleInfo TOPOLOGY_NEW_MODULE = org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.$YangModuleInfoImpl + .getInstance(); + + public static final String METAINF_PATH = "META-INF/services/" + YangModelBindingProvider.class.getName(); + + @Test + public void discoveryUsing_BindingReflections_TCCL() { + + ImmutableSet moduleInfoSet = BindingReflections.loadModuleInfos(); + assertNotNull(moduleInfoSet); + assertFalse(moduleInfoSet.isEmpty()); + assertTrue(moduleInfoSet.contains(TOPOLOGY_NEW_MODULE)); + } + + @Test + public void discoveryUsing_BindingReflections_classloader_partialServiceMetadata() throws Exception { + + ClassLoader topologyModelClassLoader = new ClassLoader(Thread.currentThread().getContextClassLoader()) { + + @Override + public Enumeration getResources(String name) throws IOException { + if (METAINF_PATH.equals(name)) { + Vector topologyUrlVector = new Vector<>(); + topologyUrlVector.add(TOPOLOGY_NEW_MODULE.getClass().getResource("/" + METAINF_PATH)); + return topologyUrlVector.elements(); + } + return super.getResources(name); + } + }; + + ImmutableSet moduleInfoSet = BindingReflections.loadModuleInfos(topologyModelClassLoader); + assertNotNull(moduleInfoSet); + assertFalse(moduleInfoSet.isEmpty()); + assertTrue(moduleInfoSet.contains(TOPOLOGY_NEW_MODULE)); + } + + @Test + public void moduleInfoBackedContextTCCL() throws Exception { + + ModuleInfoBackedContext context = ModuleInfoBackedContext.create(GeneratedClassLoadingStrategy.getAlwaysFailClassLoadingStrategy()); + + ImmutableSet moduleInfoSet = BindingReflections.loadModuleInfos(); + + context.addModuleInfos(moduleInfoSet); + assertNotNull(moduleInfoSet); + assertFalse(moduleInfoSet.isEmpty()); + assertTrue(moduleInfoSet.contains(TOPOLOGY_NEW_MODULE)); + + Class linkClass = context.loadClass(Link.class.getName()); + assertNotNull(linkClass); + assertEquals(Link.class, linkClass); + Optional schemaContext = context.tryToCreateSchemaContext(); + assertNotNull(schemaContext); + assertNotNull(schemaContext.get()); + } + +} diff --git a/integration-test/yang-runtime-tests/src/test/java/org/opendaylight/yangtools/it/yang/runtime/tests/MultipleRevisionsSupportTest.java b/integration-test/yang-runtime-tests/src/test/java/org/opendaylight/yangtools/it/yang/runtime/tests/MultipleRevisionsSupportTest.java new file mode 100644 index 0000000000..654dc11b43 --- /dev/null +++ b/integration-test/yang-runtime-tests/src/test/java/org/opendaylight/yangtools/it/yang/runtime/tests/MultipleRevisionsSupportTest.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.it.yang.runtime.tests; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; + +import java.io.InputStream; +import java.net.URI; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.junit.Test; +import org.opendaylight.yangtools.sal.binding.generator.impl.BindingGeneratorImpl; +import org.opendaylight.yangtools.sal.binding.generator.impl.ModuleContext; +import org.opendaylight.yangtools.yang.binding.YangModuleInfo; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.model.api.ChoiceNode; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.SchemaNode; +import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableList.Builder; +import com.google.common.collect.ImmutableSet; + +public class MultipleRevisionsSupportTest { + + public static final YangModuleInfo TOPOLOGY_OLD_MODULE = org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev130712.$YangModuleInfoImpl + .getInstance(); + public static final YangModuleInfo TOPOLOGY_NEW_MODULE = org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.$YangModuleInfoImpl + .getInstance(); + + public static final Set DEPENDENCIES = ImmutableSet. builder() // + .addAll(TOPOLOGY_OLD_MODULE.getImportedModules()) // + .addAll(TOPOLOGY_NEW_MODULE.getImportedModules()).build(); + + @Test + public void dependenciesOlderNewer() throws Exception { + List streams = ImmutableList. builder()// + .addAll(toInputStreams(DEPENDENCIES)) // + .add(TOPOLOGY_OLD_MODULE.getModuleSourceStream()) // + .add(TOPOLOGY_NEW_MODULE.getModuleSourceStream()) // + .build(); + SchemaContext schemaContext = contextVerified(streams); + verifySchemaDifference(schemaContext, TOPOLOGY_OLD_MODULE, TOPOLOGY_NEW_MODULE); + verifyBindingDifference(schemaContext, TOPOLOGY_OLD_MODULE, TOPOLOGY_NEW_MODULE); + } + + @Test + public void dependenciesNewerOlder() throws Exception { + List streams = ImmutableList. builder()// + .addAll(toInputStreams(DEPENDENCIES)) // + .add(TOPOLOGY_NEW_MODULE.getModuleSourceStream()) // + .add(TOPOLOGY_OLD_MODULE.getModuleSourceStream()) // + .build(); + SchemaContext schemaContext = contextVerified(streams); + verifySchemaDifference(schemaContext, TOPOLOGY_OLD_MODULE, TOPOLOGY_NEW_MODULE); + verifyBindingDifference(schemaContext, TOPOLOGY_OLD_MODULE, TOPOLOGY_NEW_MODULE); + } + + @Test + public void newerOlderDependencies() throws Exception { + List streams = ImmutableList. builder()// + .add(TOPOLOGY_NEW_MODULE.getModuleSourceStream()) // + .add(TOPOLOGY_OLD_MODULE.getModuleSourceStream()) // + .addAll(toInputStreams(DEPENDENCIES)) // + .build(); + SchemaContext schemaContext = contextVerified(streams); + verifySchemaDifference(schemaContext, TOPOLOGY_OLD_MODULE, TOPOLOGY_NEW_MODULE); + verifyBindingDifference(schemaContext, TOPOLOGY_OLD_MODULE, TOPOLOGY_NEW_MODULE); + } + + @Test + public void newerDependenciesOlder() throws Exception { + List streams = ImmutableList. builder()// + .add(TOPOLOGY_NEW_MODULE.getModuleSourceStream()) // + .addAll(toInputStreams(DEPENDENCIES)) // + .add(TOPOLOGY_OLD_MODULE.getModuleSourceStream()) // + .build(); + SchemaContext schemaContext = contextVerified(streams); + verifySchemaDifference(schemaContext, TOPOLOGY_OLD_MODULE, TOPOLOGY_NEW_MODULE); + verifyBindingDifference(schemaContext, TOPOLOGY_OLD_MODULE, TOPOLOGY_NEW_MODULE); + } + + @Test + public void OlderNewerDependencies() throws Exception { + List streams = ImmutableList. builder()// + .add(TOPOLOGY_OLD_MODULE.getModuleSourceStream()) // + .add(TOPOLOGY_NEW_MODULE.getModuleSourceStream()) // + .addAll(toInputStreams(DEPENDENCIES)) // + .build(); + SchemaContext schemaContext = contextVerified(streams); + verifySchemaDifference(schemaContext, TOPOLOGY_OLD_MODULE, TOPOLOGY_NEW_MODULE); + verifyBindingDifference(schemaContext, TOPOLOGY_OLD_MODULE, TOPOLOGY_NEW_MODULE); + } + + @Test + public void olderDependenciesNewer() throws Exception { + List streams = ImmutableList. builder()// + .add(TOPOLOGY_OLD_MODULE.getModuleSourceStream()) // + .add(TOPOLOGY_NEW_MODULE.getModuleSourceStream()) // + .addAll(toInputStreams(DEPENDENCIES)) // + .build(); + SchemaContext schemaContext = contextVerified(streams); + verifySchemaDifference(schemaContext, TOPOLOGY_OLD_MODULE, TOPOLOGY_NEW_MODULE); + verifyBindingDifference(schemaContext, TOPOLOGY_OLD_MODULE, TOPOLOGY_NEW_MODULE); + } + + private SchemaContext contextVerified(List streams) { + YangParserImpl parser = new YangParserImpl(); + Set modules = parser.parseYangModelsFromStreams(streams); + assertNotNull(modules); + assertFalse(modules.isEmpty()); + SchemaContext context = parser.resolveSchemaContext(modules); + assertNotNull(context); + return context; + } + + private void verifyBindingDifference(SchemaContext schemaContext, YangModuleInfo oldModule, YangModuleInfo newModule) { + + Map generatedTypes = generatedTypesVerified(schemaContext, oldModule, newModule); + + } + + private Map generatedTypesVerified(SchemaContext schemaContext, YangModuleInfo oldModule, + YangModuleInfo newModule) { + BindingGeneratorImpl generator = new BindingGeneratorImpl(); + generator.generateTypes(schemaContext); + return generator.getModuleContexts(); + } + + private void verifySchemaDifference(SchemaContext context, YangModuleInfo topologyOldModule, + YangModuleInfo topologyNewModule) { + Module oldModel = context.findModuleByNamespaceAndRevision(// + URI.create(TOPOLOGY_OLD_MODULE.getNamespace()), QName.parseRevision(TOPOLOGY_OLD_MODULE.getRevision())); + + Module newModel = context.findModuleByNamespaceAndRevision(// + URI.create(TOPOLOGY_NEW_MODULE.getNamespace()), QName.parseRevision(TOPOLOGY_NEW_MODULE.getRevision())); + + SchemaNode oldNode = findSchemaNode(oldModel, "network-topology", "topology", "link"); + SchemaNode newNode = findSchemaNode(newModel, "network-topology", "topology", "link"); + + assertNotNull(oldNode); + assertNotNull(newNode); + + assertDeepRevision(TOPOLOGY_OLD_MODULE.getRevision(), oldNode); + assertDeepRevision(TOPOLOGY_NEW_MODULE.getRevision(), newNode); + } + + private static void assertDeepRevision(String revision, SchemaNode node) { + assertEquals("Wrong revision: " + node.getPath(), revision, node.getQName().getFormattedRevision()); + if (node instanceof DataNodeContainer) { + for (DataSchemaNode child : ((DataNodeContainer) node).getChildNodes()) { + assertDeepRevision(revision, child); + } + } else if (node instanceof ChoiceNode) { + for (DataSchemaNode child : ((ChoiceNode) node).getCases()) { + assertDeepRevision(revision, child); + } + } + } + + private static final SchemaNode findSchemaNode(DataNodeContainer container, String... pathArgs) { + DataNodeContainer previous = container; + + SchemaNode result = (container instanceof SchemaNode) ? (SchemaNode) container : null; + for (String arg : pathArgs) { + if (previous == null) { + return null; + } + Set childs = previous.getChildNodes(); + for (DataSchemaNode child : childs) { + if (child.getQName().getLocalName().equals(arg)) { + if (child instanceof DataNodeContainer) { + previous = (DataNodeContainer) child; + } else { + previous = null; + } + result = child; + break; + } + } + } + return result; + } + + private static final Iterable toInputStreams(Set moduleInfos) + throws Exception { + Builder streams = ImmutableList. builder(); + for (YangModuleInfo yangModuleInfo : moduleInfos) { + streams.add(yangModuleInfo.getModuleSourceStream()); + } + return streams.build(); + } + +} diff --git a/pom.xml b/pom.xml index b79a027190..2b9136de82 100644 --- a/pom.xml +++ b/pom.xml @@ -58,6 +58,7 @@ code-generator model restconf + integration-test mockito-configuration diff --git a/yang/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/BindingMapping.java b/yang/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/BindingMapping.java index 5b1d45a679..2f1dfb275e 100644 --- a/yang/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/BindingMapping.java +++ b/yang/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/BindingMapping.java @@ -35,7 +35,8 @@ public final class BindingMapping { private static final Splitter SPACE_SPLITTER = Splitter.on(" ").omitEmptyStrings().trimResults(); - public static final String MODULE_INFO_CLASS_NAME = "$ModuleInfoImpl"; + public static final String MODULE_INFO_CLASS_NAME = "$YangModuleInfoImpl"; + public static final String MODEL_BINDING_PROVIDER_CLASS_NAME = "$YangModelBindingProvider"; public static final String getMethodName(QName name) { checkArgument(name != null, "Name should not be null."); diff --git a/yang/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/YangModelBindingProvider.java b/yang/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/YangModelBindingProvider.java new file mode 100644 index 0000000000..fc05db84c5 --- /dev/null +++ b/yang/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/YangModelBindingProvider.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang.binding; + +import java.util.ServiceLoader; + +/** + * + * Provider of YangModuleInfo for specified package / model. + * + * Implementation of this interface should be discoverable + * via {@link ServiceLoader} + * + * + * + */ +public interface YangModelBindingProvider { + + /** + * YangModuleInfo associated to package + * + * @return + */ + YangModuleInfo getModuleInfo(); + +} diff --git a/yang/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/util/BindingReflections.java b/yang/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/util/BindingReflections.java index 11c4842501..48ebcdea43 100644 --- a/yang/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/util/BindingReflections.java +++ b/yang/yang-binding/src/main/java/org/opendaylight/yangtools/yang/binding/util/BindingReflections.java @@ -11,6 +11,7 @@ import java.beans.MethodDescriptor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Type; +import java.util.ServiceLoader; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @@ -28,6 +29,7 @@ import org.opendaylight.yangtools.yang.binding.DataContainer; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.Notification; import org.opendaylight.yangtools.yang.binding.RpcService; +import org.opendaylight.yangtools.yang.binding.YangModelBindingProvider; import org.opendaylight.yangtools.yang.binding.YangModuleInfo; import org.opendaylight.yangtools.yang.binding.annotations.ModuleQName; import org.opendaylight.yangtools.yang.common.QName; @@ -40,6 +42,8 @@ import com.google.common.base.Preconditions; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSet.Builder; public class BindingReflections { @@ -214,5 +218,25 @@ public class BindingReflections { checkArgument(potentialNotification != null); return Notification.class.isAssignableFrom(potentialNotification); } + + public static ImmutableSet loadModuleInfos() { + return loadModuleInfos(Thread.currentThread().getContextClassLoader()); + } + + public static ImmutableSet loadModuleInfos(ClassLoader loader) { + Builder moduleInfoSet = ImmutableSet.builder(); + ServiceLoader serviceLoader = ServiceLoader.load(YangModelBindingProvider.class, loader); + for(YangModelBindingProvider bindingProvider : serviceLoader) { + collectYangModuleInfo(bindingProvider.getModuleInfo(),moduleInfoSet); + } + return moduleInfoSet.build(); + } + + private static void collectYangModuleInfo(YangModuleInfo moduleInfo, Builder moduleInfoSet) { + moduleInfoSet.add(moduleInfo); + for(YangModuleInfo dependency : moduleInfo.getImportedModules()) { + collectYangModuleInfo(dependency, moduleInfoSet); + } + } }