Configuration system employs two phase commit to validate and push configuration changes to a running system. It allows changing simple attributes, complex transfer objects, and also provides dependency injection between configured modules.
Module config-api contains base yang model config.yang, defining language extensions and other elements required by all configuration models. Other than that it contains both apis implemented by config-manager and spis to be implemeted by configuration providers. Since the configuration system is internally driven by JMX, package org.opendaylight.controller.config.api.jmx contains all M(X)Bean interfaces exposed by config-manager.
Module config-manager is implementation of config-api.
Module config-util contains clients (both JMX and jolokia, which is http based bridge to JMX) of configuration system.
Module yang-jmx-generator parses yang models and creates java representation of configuration models and service interfaces.
Module yang-jmx-generator-plugin is connector to yangtools yang-maven-plugin that generates skeletons of java files needed by configuration providers.
Module yang-test contains example yang file, from which code is being generated.
Module yang-jmx-generator-it tests yang-test.
Module yang-store-api contains api for a registry of all yang models parsed by yang-jmx-generator.
Module yang-store-impl uses OSGi extender pattern to read META-INF/yang/*.yang from all bundles and provides snapshot view of currently available configuration models.
Change-Id: Icf3201f9754e4ca28ebce3411d2a667dcd7e75c8
Signed-off-by: Tomas Olvecky <tolvecky@cisco.com>
<enforcer.version>1.3.1</enforcer.version>
<bundle.plugin.version>2.3.7</bundle.plugin.version>
<junit.version>4.8.1</junit.version>
+ <bgpcep.version>0.2.0-SNAPSHOT</bgpcep.version>
</properties>
<dependencyManagement>
<artifactId>javassist</artifactId>
<version>${javassist.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.opendaylight.bgpcep</groupId>
+ <artifactId>concepts</artifactId>
+ <version>${bgpcep.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.bgpcep</groupId>
+ <artifactId>util</artifactId>
+ <version>${bgpcep.version}</version>
+ </dependency>
</dependencies>
<properties>
<build.suffix>${project.version}</build.suffix>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
- <version>2.3</version>
+ <version>2.4</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
+ <version>1.7</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ <version>${commons.lang.version}</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
--- /dev/null
+target
+.classpath
+.settings
--- /dev/null
+<?xml version="1.0"?>
+<project
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+ xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <artifactId>config-subsystem</artifactId>
+ <groupId>org.opendaylight</groupId>
+ <version>0.2.1-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>config-api</artifactId>
+ <name>${project.artifactId}</name>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.bgpcep</groupId>
+ <artifactId>concepts</artifactId>
+ <version>0.2.0-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Import-Package>
+ javax.management,
+ org.opendaylight.protocol.concepts
+ </Import-Package>
+ <Export-Package>
+ org.opendaylight.controller.config.api,
+ org.opendaylight.controller.config.api.annotations,
+ org.opendaylight.controller.config.spi,
+ org.opendaylight.controller.config.api.jmx,
+ org.opendaylight.controller.config.api.jmx.constants,
+ org.opendaylight.controller.config.api.runtime,
+ </Export-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.api;
+
+import java.util.List;
+import java.util.Set;
+
+import javax.management.ObjectName;
+
+import org.opendaylight.controller.config.api.jmx.CommitStatus;
+import org.opendaylight.controller.config.api.jmx.ConfigTransactionControllerMXBean;
+import org.opendaylight.controller.config.api.jmx.constants.ConfigRegistryConstants;
+
+/**
+ * Provides functionality for working with configuration registry - mainly
+ * creating and committing config transactions.
+ */
+public interface ConfigRegistry extends LookupRegistry {
+
+ /**
+ * Only well-known ObjectName in configuration system, under which
+ * ConfigRegisry is registered.
+ */
+ public static final ObjectName OBJECT_NAME = ConfigRegistryConstants.OBJECT_NAME;
+
+ /**
+ * Opens new configuration transaction.
+ *
+ * @return {@link ObjectName} of {@link ConfigTransactionControllerMXBean}
+ */
+ ObjectName beginConfig();
+
+ /**
+ * Verifies and commits transaction.
+ *
+ * @param transactionControllerON
+ * {@link ObjectName} of
+ * {@link ConfigTransactionControllerMXBean} that was received in
+ * {@link #beginConfig()} method call.
+ * @return CommitStatus
+ * @throws ValidationException
+ * if validation fails
+ * @throws ConflictingVersionException
+ * if configuration state was changed
+ */
+ CommitStatus commitConfig(ObjectName transactionControllerON)
+ throws ConflictingVersionException, ValidationException;
+
+ /**
+ * @return list of open configuration transactions.
+ */
+ List<ObjectName> getOpenConfigs();
+
+ /**
+ * Will return true unless there was a transaction that succeeded during
+ * validation but failed in second phase of commit. In this case the server
+ * is unstable and its state is undefined.
+ */
+ boolean isHealthy();
+
+ Set<String> getAvailableModuleNames();
+
+ /**
+ * Find all runtime beans
+ *
+ * @return objectNames
+ */
+ Set<ObjectName> lookupRuntimeBeans();
+
+ /**
+ * Find all runtime of specified module
+ *
+ * @param moduleName
+ * of bean
+ * @param instanceName
+ * of bean
+ * @return objectNames
+ */
+ Set<ObjectName> lookupRuntimeBeans(String moduleName, String instanceName);
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.api;
+
+import java.util.Set;
+
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.InstanceNotFoundException;
+import javax.management.ObjectName;
+
+/**
+ * Represents functionality provided by configuration transaction.
+ */
+public interface ConfigTransactionController extends LookupRegistry {
+
+ /**
+ * Create new configuration bean.
+ *
+ * @param moduleName
+ * @param instanceName
+ * @return ObjectName of newly created module
+ * @throws InstanceAlreadyExistsException
+ * if given ifcName and instanceName is already registered
+ */
+ ObjectName createModule(String moduleName, String instanceName)
+ throws InstanceAlreadyExistsException;
+
+ /**
+ * Destroy existing module.
+ *
+ * @param objectName
+ * can be either read-only module name that can be obtained using
+ * {@link ConfigRegistry#lookupConfigBean(String, String)} or
+ * writable module name that must contain current transaction
+ * name.
+ * @throws InstanceNotFoundException
+ * if module is not found
+ * @throws IllegalArgumentException
+ * if object name contains wrong transaction name or domain
+ */
+ void destroyModule(ObjectName objectName) throws InstanceNotFoundException;
+
+ /**
+ * Destroy current transaction.
+ */
+ void abortConfig();
+
+ /**
+ * This method can be called multiple times, has no side effects.
+ *
+ * @throws ValidationException
+ * if validation fails
+ */
+ void validateConfig() throws ValidationException;
+
+ /**
+ *
+ * @return transactionName
+ */
+ String getTransactionName();
+
+ Set<String> getAvailableModuleNames();
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.api;
+
+/**
+ * Can be thrown during
+ * {@link ConfigRegistry#commitConfig(javax.management.ObjectName)} to indicate
+ * that the transaction cannot be committed due to the fact that another
+ * transaction was committed after creating this transaction. Clients can create
+ * new transaction and merge the changes.
+ */
+public class ConflictingVersionException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ public ConflictingVersionException() {
+ super();
+ }
+
+ public ConflictingVersionException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ConflictingVersionException(String message) {
+ super(message);
+ }
+
+ public ConflictingVersionException(Throwable cause) {
+ super(cause);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.api;
+
+import javax.management.ObjectName;
+
+import org.opendaylight.controller.config.api.annotations.AbstractServiceInterface;
+
+/**
+ * Each new {@link org.opendaylight.controller.config.spi.Module} can receive
+ * resolver from {@link org.opendaylight.controller.config.spi.ModuleFactory}
+ * for looking up dependencies during validation and second phase commit.
+ *
+ * @see org.opendaylight.controller.config.spi.Module
+ */
+public interface DependencyResolver {
+
+ /**
+ * To be used during validation phase to validate serice interface of
+ * dependent module.
+ *
+ * @param expectedServiceInterface
+ * MBean/MXBean interface which will back the proxy object.
+ * @param objectName
+ * ObjectName of dependent module without transaction name
+ * (platformON).
+ * @param jmxAttribute
+ * @throws {@link IllegalArgumentException} when module is not found
+ * @throws {@link IllegalStateException} if module does not export this
+ * service interface.
+ */
+ void validateDependency(
+ Class<? extends AbstractServiceInterface> expectedServiceInterface,
+ ObjectName objectName, JmxAttribute jmxAttribute);
+
+ @Deprecated
+ // TODO remove once all config code is generated
+ void validateDependency(
+ Class<? extends AbstractServiceInterface> expectedServiceInterface,
+ ObjectName objectName, String attributeNameForErrorReporting);
+
+ /**
+ * To be used during commit phase to wire actual dependencies.
+ *
+ * @return dependency instance using
+ * {@link org.opendaylight.controller.config.spi.Module#getInstance()}
+ * @throws {@link IllegalArgumentException} when module is not found
+ */
+ <T> T resolveInstance(Class<T> expectedType, ObjectName objectName,
+ JmxAttribute jmxAttribute);
+
+ @Deprecated
+ <T> T resolveInstance(Class<T> expectedType, ObjectName objectName);
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.api;
+
+import javax.management.DynamicMBean;
+
+import org.opendaylight.controller.config.spi.Module;
+
+/**
+ * Each {@link org.opendaylight.controller.config.spi.Module} that is committed
+ * will be wrapped into this interface.
+ */
+public interface DynamicMBeanWithInstance extends DynamicMBean {
+
+ /**
+ * Get original module that is wrapped with this instance.
+ */
+ Module getModule();
+
+ /**
+ * Gets 'live object' associated with current config object. Useful when
+ * reconfiguring {@link org.opendaylight.controller.config.spi.Module}
+ * instances.
+ */
+ AutoCloseable getInstance();
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.api;
+
+/**
+ * Wrapper around strings to make {@link JmxAttributeValidationException} type
+ * safe.
+ */
+public class JmxAttribute {
+ private final String attributeName;
+
+ public JmxAttribute(String attributeName) {
+ if (attributeName == null)
+ throw new NullPointerException("Parameter 'attributeName' is null");
+ this.attributeName = attributeName;
+ }
+
+ public String getAttributeName() {
+ return attributeName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ JmxAttribute that = (JmxAttribute) o;
+
+ if (attributeName != null ? !attributeName.equals(that.attributeName)
+ : that.attributeName != null)
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return attributeName != null ? attributeName.hashCode() : 0;
+ }
+
+ @Override
+ public String toString() {
+ return "JmxAttribute{'" + attributeName + "'}";
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.api;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Exception that can be thrown during validation phase. This allows for
+ * pointing user to a specific list of parameters that fail the validation. Note
+ * that {@link org.opendaylight.controller.config.spi.Module#validate()} can
+ * throw any runtime exception to trigger validation failure.
+ */
+public class JmxAttributeValidationException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+ private final List<JmxAttribute> attributeNames;
+
+ public JmxAttributeValidationException(JmxAttribute jmxAttribute) {
+ this(Arrays.asList(jmxAttribute));
+ }
+
+ public JmxAttributeValidationException(List<JmxAttribute> jmxAttribute) {
+ this.attributeNames = jmxAttribute;
+ }
+
+ public JmxAttributeValidationException(String message,
+ JmxAttribute jmxAttribute) {
+ this(message, Arrays.asList(jmxAttribute));
+ }
+
+ public JmxAttributeValidationException(String message,
+ List<JmxAttribute> jmxAttributes) {
+ super(message);
+ this.attributeNames = jmxAttributes;
+ }
+
+ public JmxAttributeValidationException(String message, Throwable cause,
+ JmxAttribute jmxAttribute) {
+ this(message, cause, Arrays.asList(jmxAttribute));
+ }
+
+ public JmxAttributeValidationException(String message, Throwable cause,
+ List<JmxAttribute> jmxAttributes) {
+ super(message, cause);
+ this.attributeNames = jmxAttributes;
+ }
+
+ public List<JmxAttribute> getAttributeNames() {
+ return attributeNames;
+ }
+
+ public static <T> T checkNotNull(T param, JmxAttribute jmxAttribute) {
+ String message = "is null";
+ return checkNotNull(param, message, jmxAttribute);
+ }
+
+ public static <T> T checkNotNull(T param, String message,
+ JmxAttribute jmxAttribute) {
+ if (param == null) {
+ throw new JmxAttributeValidationException(
+ jmxAttribute.getAttributeName() + " " + message,
+ jmxAttribute);
+ }
+ return param;
+ }
+
+ public static JmxAttributeValidationException wrap(Throwable throwable,
+ JmxAttribute jmxAttribute) throws JmxAttributeValidationException {
+ return wrap(throwable, throwable.getMessage(), jmxAttribute);
+ }
+
+ public static JmxAttributeValidationException wrap(Throwable throwable,
+ String message, JmxAttribute jmxAttribute) {
+
+ throw new JmxAttributeValidationException(
+ jmxAttribute.getAttributeName() + " " + message, throwable,
+ jmxAttribute);
+ }
+
+ public static void checkCondition(boolean condition, String message,
+ JmxAttribute jmxAttribute) throws JmxAttributeValidationException {
+ if (condition == false) {
+ throw new JmxAttributeValidationException(
+ jmxAttribute.getAttributeName() + " " + message,
+ jmxAttribute);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.api;
+
+import java.util.Set;
+
+import javax.management.InstanceNotFoundException;
+import javax.management.ObjectName;
+
+public interface LookupRegistry {
+
+ /**
+ * Find all modules. Same Module can be registered multiple times.
+ *
+ * @return objectNames
+ */
+ Set<ObjectName> lookupConfigBeans();
+
+ /**
+ * Find modules with given module name.
+ *
+ * @param moduleName
+ * @return objectNames
+ */
+ Set<ObjectName> lookupConfigBeans(String moduleName);
+
+ /**
+ * Find read modules.
+ *
+ * @param moduleName
+ * exact match for searched module name, can contain '*' to match
+ * all values.
+ * @param instanceName
+ * exact match for searched instance name, can contain '*' to
+ * match all values.
+ * @return objectNames
+ */
+ Set<ObjectName> lookupConfigBeans(String moduleName, String instanceName);
+
+ /**
+ * Find read module.
+ *
+ * @param moduleName
+ * exact match for searched module name, can contain '*' to match
+ * all values.
+ * @param instanceName
+ * exact match for searched instance name, can contain '*' to
+ * match all values.
+ * @return objectNames
+ * @throws InstanceNotFoundException
+ * if search did not find exactly one instance
+ */
+ ObjectName lookupConfigBean(String moduleName, String instanceName)
+ throws InstanceNotFoundException;
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.api;
+
+import org.opendaylight.protocol.concepts.Identifier;
+
+public class ModuleIdentifier implements Identifier {
+ private static final long serialVersionUID = 1L;
+ private final String factoryName, instanceName;
+
+ public ModuleIdentifier(String factoryName, String instanceName) {
+ if (factoryName == null)
+ throw new IllegalArgumentException(
+ "Parameter 'factoryName' is null");
+ if (instanceName == null)
+ throw new IllegalArgumentException(
+ "Parameter 'instanceName' is null");
+ this.factoryName = factoryName;
+ this.instanceName = instanceName;
+ }
+
+ public String getFactoryName() {
+ return factoryName;
+ }
+
+ public String getInstanceName() {
+ return instanceName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ ModuleIdentifier that = (ModuleIdentifier) o;
+
+ if (!factoryName.equals(that.factoryName))
+ return false;
+ if (!instanceName.equals(that.instanceName))
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = factoryName.hashCode();
+ result = 31 * result + instanceName.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "ModuleIdentifier{" + "factoryName='" + factoryName + '\''
+ + ", instanceName='" + instanceName + '\'' + '}';
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.api;
+
+import java.io.Closeable;
+
+import org.opendaylight.controller.config.api.runtime.RootRuntimeBeanRegistrator;
+import org.opendaylight.controller.config.spi.Module;
+
+/**
+ * Module implementing this interface will receive
+ * {@link RootRuntimeBeanRegistrator} before getInstance() is invoked.
+ */
+public interface RuntimeBeanRegistratorAwareModule extends Module {
+ /**
+ * Configuration framework will call this setter on all modules implementing
+ * this interface. It is responsibility of modules or rather their instances
+ * to close registrator in their {@link Closeable#close()} method. Same
+ * module will get the same registrator during reconfiguration.
+ *
+ * @param rootRuntimeBeanRegistrator
+ */
+ public void setRuntimeBeanRegistrator(
+ RootRuntimeBeanRegistrator rootRuntimeBeanRegistrator);
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.api;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * This exception is not intended to be used while implementing modules,
+ * itaggregates validation exceptions and sends them back to the user.
+ */
+public class ValidationException extends RuntimeException {
+ private static final long serialVersionUID = -6072893219820274247L;
+
+ private final Map<String, Map<String, ExceptionMessageWithStackTrace>> failedValidations;
+
+ public ValidationException(
+ Map<String /* module name */, Map<String /* instance name */, ExceptionMessageWithStackTrace>> failedValidations) {
+ super(failedValidations.toString());
+ this.failedValidations = Collections.unmodifiableMap(failedValidations);
+ }
+
+ public static ValidationException createFromCollectedValidationExceptions(
+ List<ValidationException> collectedExceptions) {
+ Map<String, Map<String, ExceptionMessageWithStackTrace>> failedValidations = new HashMap<>();
+ for (ValidationException ve : collectedExceptions) {
+ for (Entry<String, Map<String, ExceptionMessageWithStackTrace>> outerEntry : ve
+ .getFailedValidations().entrySet()) {
+ for (Entry<String, ExceptionMessageWithStackTrace> innerEntry : outerEntry
+ .getValue().entrySet()) {
+ String moduleName = outerEntry.getKey();
+ String instanceName = innerEntry.getKey();
+ ExceptionMessageWithStackTrace ex = innerEntry.getValue();
+ Map<String, ExceptionMessageWithStackTrace> instanceToExMap = failedValidations
+ .get(moduleName);
+ if (instanceToExMap == null) {
+ instanceToExMap = new HashMap<>();
+ failedValidations.put(moduleName, instanceToExMap);
+ }
+ if (instanceToExMap.containsKey(instanceName)) {
+ throw new IllegalArgumentException(
+ "Cannot merge with same module name "
+ + moduleName + " and instance name "
+ + instanceName);
+ }
+ instanceToExMap.put(instanceName, ex);
+ }
+ }
+ }
+ return new ValidationException(failedValidations);
+ }
+
+ public static ValidationException createForSingleException(
+ ModuleIdentifier moduleIdentifier, Exception e) {
+ Map<String, Map<String, ExceptionMessageWithStackTrace>> failedValidations = new HashMap<>();
+ Map<String, ExceptionMessageWithStackTrace> innerMap = new HashMap<>();
+
+ failedValidations.put(moduleIdentifier.getFactoryName(), innerMap);
+ innerMap.put(moduleIdentifier.getInstanceName(),
+ new ExceptionMessageWithStackTrace(e));
+ return new ValidationException(failedValidations);
+ }
+
+ public Map<String, Map<String, ExceptionMessageWithStackTrace>> getFailedValidations() {
+ return failedValidations;
+ }
+
+ public static class ExceptionMessageWithStackTrace {
+ private String message, stackTrace;
+
+ public ExceptionMessageWithStackTrace() {
+ }
+
+ public ExceptionMessageWithStackTrace(String message, String stackTrace) {
+ this.message = message;
+ this.stackTrace = stackTrace;
+ }
+
+ public ExceptionMessageWithStackTrace(Exception e) {
+ this(e.getMessage(), Arrays.toString(e.getStackTrace()));
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public String getTrace() {
+ return stackTrace;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public void setTrace(String stackTrace) {
+ this.stackTrace = stackTrace;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + ((message == null) ? 0 : message.hashCode());
+ result = prime * result
+ + ((stackTrace == null) ? 0 : stackTrace.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ ExceptionMessageWithStackTrace other = (ExceptionMessageWithStackTrace) obj;
+ if (message == null) {
+ if (other.message != null)
+ return false;
+ } else if (!message.equals(other.message))
+ return false;
+ if (stackTrace == null) {
+ if (other.stackTrace != null)
+ return false;
+ } else if (!stackTrace.equals(other.stackTrace))
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "ExceptionMessageWithStackTrace [message=" + message
+ + ", stackTrace=" + stackTrace + "]";
+ }
+
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.api.annotations;
+
+/**
+ * Marker interface on all Service Interface annotated classes, each Service
+ * Interface must extend this interface. Service Intefaces can form hierarchies,
+ * one SI can extend another one, in which case all annotations in hierarchy
+ * will be observed.
+ */
+public abstract interface AbstractServiceInterface {
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.api.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Exports attribute and module class descriptions. Description annotation can
+ * be applied to module directly or to its super class or MXBean interface.
+ */
+@Target({ ElementType.TYPE, ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Description {
+ /**
+ * Returns a human-readable description of the annotated attribute.
+ * Descriptions should be clear and concise, describing what the attribute
+ * affects.
+ *
+ * @return attribute description
+ */
+ String value();
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.api.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import javax.management.ObjectName;
+
+/**
+ * Indicates what service interface is expected to be obtained as a dependency
+ * of a module. This annotation must be present for each dependency setter in
+ * {@link org.opendaylight.controller.config.spi.Module} M(X)Bean interface.
+ * Together with name of dependent bean the {@link #value()} will be used to get
+ * {@link ObjectName} of dependency.
+ *
+ * <p>
+ * Example:<br>
+ *
+ * <code>
+ *
+ * @RequireInterface(value = ThreadPoolServiceInterface.class, optional =
+ * false)<br/> void setThreadPool(ObjectName on);
+ * </code>
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface RequireInterface {
+
+ /**
+ * Declares dependency on service interface.
+ */
+ Class<? extends AbstractServiceInterface> value();
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.api.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks an interface implemented by
+ * {@link org.opendaylight.controller.config.spi.Module} as a Service Interface.
+ * Each service interface is identified by globally unique and human readable
+ * name. By convention the name is all lower case without spaces.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface ServiceInterfaceAnnotation {
+
+ /**
+ * Specifies human readable name of this service. Each service name should
+ * be globally unique. Should not contain spaces.
+ */
+ String value();
+
+ /**
+ * Mandatory class which will be used as key for OSGi service registration
+ * once {@link org.opendaylight.controller.config.spi.Module#getInstance()}
+ * is called.
+ */
+ Class<?> osgiRegistrationType();
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.api.jmx;
+
+import java.beans.ConstructorProperties;
+import java.util.Collections;
+import java.util.List;
+
+import javax.annotation.concurrent.Immutable;
+import javax.management.ObjectName;
+
+@Immutable
+public class CommitStatus {
+ private final List<ObjectName> newInstances, reusedInstances,
+ recreatedInstances;
+
+ /**
+ *
+ * @param newInstances
+ * newly created instances
+ * @param reusedInstances
+ * reused instances
+ * @param recreatedInstances
+ * recreated instances
+ */
+ @ConstructorProperties({ "newInstances", "reusedInstances",
+ "recreatedInstances" })
+ public CommitStatus(List<ObjectName> newInstances,
+ List<ObjectName> reusedInstances,
+ List<ObjectName> recreatedInstances) {
+ this.newInstances = Collections.unmodifiableList(newInstances);
+ this.reusedInstances = Collections.unmodifiableList(reusedInstances);
+ this.recreatedInstances = Collections
+ .unmodifiableList(recreatedInstances);
+ }
+
+ /**
+ *
+ * @return list of objectNames representing newly created instances
+ */
+ public List<ObjectName> getNewInstances() {
+ return newInstances;
+ }
+
+ /**
+ *
+ * @return list of objectNames representing reused instances
+ */
+ public List<ObjectName> getReusedInstances() {
+ return reusedInstances;
+ }
+
+ /**
+ *
+ * @return list of objectNames representing recreated instances
+ */
+ public List<ObjectName> getRecreatedInstances() {
+ return recreatedInstances;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + ((newInstances == null) ? 0 : newInstances.hashCode());
+ result = prime
+ * result
+ + ((recreatedInstances == null) ? 0 : recreatedInstances
+ .hashCode());
+ result = prime * result
+ + ((reusedInstances == null) ? 0 : reusedInstances.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ CommitStatus other = (CommitStatus) obj;
+ if (newInstances == null) {
+ if (other.newInstances != null)
+ return false;
+ } else if (!newInstances.equals(other.newInstances))
+ return false;
+ if (recreatedInstances == null) {
+ if (other.recreatedInstances != null)
+ return false;
+ } else if (!recreatedInstances.equals(other.recreatedInstances))
+ return false;
+ if (reusedInstances == null) {
+ if (other.reusedInstances != null)
+ return false;
+ } else if (!reusedInstances.equals(other.reusedInstances))
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "CommitStatus [newInstances=" + newInstances
+ + ", reusedInstances=" + reusedInstances
+ + ", recreatedInstances=" + recreatedInstances + "]";
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.api.jmx;
+
+import org.opendaylight.controller.config.api.ConfigRegistry;
+
+/**
+ * Represents entry point of configuration management. Note: Reason for having
+ * methods in super interface is that JMX allows only one MXBean to be
+ * implemented and implementations can expose additional methods to be exported.<br>
+ * Implementation of {@link ConfigRegistry} is not required to implement this
+ * interface, but is required to export all methods of ConfigRegistry to JMX so
+ * that this interface can be used as a proxy.
+ */
+public interface ConfigRegistryMXBean extends ConfigRegistry {
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.api.jmx;
+
+import org.opendaylight.controller.config.api.ConfigTransactionController;
+
+/**
+ * Those are executed by Jolokia clients on configuration transaction
+ * represented by {@link ConfigMBeanServer} instance. Note: Reason for having
+ * methods in super interface is that JMX allows only one MXBean to be
+ * implemented and implementations can expose additional methods to be exported. <br>
+ * Implementation of {@link ConfigTransactionController} is not required to
+ * implement this interface, but is required to export all methods of
+ * ConfigTransactionController to JMX so that this interface can be used as a
+ * proxy.
+ */
+public interface ConfigTransactionControllerMXBean extends
+ ConfigTransactionController {
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.api.jmx;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.annotation.concurrent.ThreadSafe;
+import javax.management.ObjectName;
+
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.jmx.constants.ConfigRegistryConstants;
+
+/**
+ * Provides ObjectName creation. Each created ObjectName consists of domain that
+ * is defined as {@link #ON_DOMAIN} and at least one key-value pair. The only
+ * mandatory property is {@link #TYPE_KEY}. All transaction related mbeans have
+ * {@link #TRANSACTION_NAME_KEY} property set.
+ *
+ */
+@ThreadSafe
+public class ObjectNameUtil {
+
+ public static final String ON_DOMAIN = ConfigRegistryConstants.ON_DOMAIN;
+ public static final String MODULE_FACTORY_NAME_KEY = "moduleFactoryName";
+ public static final String INSTANCE_NAME_KEY = "instanceName";
+ public static final String TYPE_KEY = ConfigRegistryConstants.TYPE_KEY;
+ public static final String TYPE_CONFIG_REGISTRY = ConfigRegistryConstants.TYPE_CONFIG_REGISTRY;
+ public static final String TYPE_CONFIG_TRANSACTION = "ConfigTransaction";
+ public static final String TYPE_MODULE = "Module";
+ public static final String TYPE_RUNTIME_BEAN = "RuntimeBean";
+
+ public static final String TRANSACTION_NAME_KEY = "TransactionName";
+
+ public static ObjectName createON(String on) {
+ try {
+ return new ObjectName(on);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static ObjectName createONWithDomainAndType(String type) {
+ return ConfigRegistryConstants.createONWithDomainAndType(type);
+ }
+
+ public static ObjectName createON(String name, String key, String value) {
+ return ConfigRegistryConstants.createON(name, key, value);
+ }
+
+ public static ObjectName createON(String name, Map<String, String> attribs) {
+ Hashtable<String, String> table = new Hashtable<>(attribs);
+ try {
+ return new ObjectName(name, table);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ public static ObjectName createTransactionControllerON(
+ String transactionName) {
+ Map<String, String> onParams = new HashMap<>();
+ onParams.put(TRANSACTION_NAME_KEY, transactionName);
+ onParams.put(TYPE_KEY, TYPE_CONFIG_TRANSACTION);
+ return createON(ON_DOMAIN, onParams);
+ }
+
+ public static ObjectName createTransactionModuleON(String transactionName,
+ ModuleIdentifier moduleIdentifier) {
+ return createTransactionModuleON(transactionName,
+ moduleIdentifier.getFactoryName(),
+ moduleIdentifier.getInstanceName());
+ }
+
+ public static ObjectName createTransactionModuleON(String transactionName,
+ String moduleName, String instanceName) {
+ Map<String, String> onParams = createModuleON(moduleName, instanceName);
+ onParams.put(TRANSACTION_NAME_KEY, transactionName);
+ return createON(ON_DOMAIN, onParams);
+ }
+
+ public static ObjectName createTransactionModuleON(String transactionName,
+ ObjectName on) {
+ return createTransactionModuleON(transactionName, getFactoryName(on),
+ getInstanceName(on));
+ }
+
+ public static ObjectName createReadOnlyModuleON(
+ ModuleIdentifier moduleIdentifier) {
+ return createReadOnlyModuleON(moduleIdentifier.getFactoryName(),
+ moduleIdentifier.getInstanceName());
+ }
+
+ public static ObjectName createReadOnlyModuleON(String moduleName,
+ String instanceName) {
+ Map<String, String> onParams = createModuleON(moduleName, instanceName);
+ return createON(ON_DOMAIN, onParams);
+ }
+
+ private static Map<String, String> createModuleON(String moduleName,
+ String instanceName) {
+ Map<String, String> onParams = new HashMap<>();
+ onParams.put(TYPE_KEY, TYPE_MODULE);
+ onParams.put(MODULE_FACTORY_NAME_KEY, moduleName);
+ onParams.put(INSTANCE_NAME_KEY, instanceName);
+ return onParams;
+ }
+
+ public static String getFactoryName(ObjectName objectName) {
+ return objectName.getKeyProperty(MODULE_FACTORY_NAME_KEY);
+ }
+
+ public static String getInstanceName(ObjectName objectName) {
+ return objectName.getKeyProperty(INSTANCE_NAME_KEY);
+ }
+
+ public static String getTransactionName(ObjectName objectName) {
+ return objectName.getKeyProperty(TRANSACTION_NAME_KEY);
+ }
+
+ public static ObjectName withoutTransactionName(ObjectName on) {
+ if (getTransactionName(on) == null) {
+ throw new IllegalArgumentException(
+ "Expected ObjectName with transaction:" + on);
+ }
+ if (ON_DOMAIN.equals(on.getDomain()) == false) {
+ throw new IllegalArgumentException("Expected different domain: "
+ + on);
+ }
+ String moduleName = getFactoryName(on);
+ String instanceName = getInstanceName(on);
+ return createReadOnlyModuleON(moduleName, instanceName);
+ }
+
+ private static void assertDoesNotContain(
+ Map<String, String> additionalProperties, String key) {
+ if (additionalProperties.containsKey(key)) {
+ throw new IllegalArgumentException(
+ "Map 'additionalProperties' cannot overwrite attribute "
+ + key);
+ }
+ }
+
+ public static ObjectName createRuntimeBeanName(String moduleName,
+ String instanceName, Map<String, String> additionalProperties) {
+ // check that there is no overwriting of default attributes
+ assertDoesNotContain(additionalProperties, MODULE_FACTORY_NAME_KEY);
+ assertDoesNotContain(additionalProperties, INSTANCE_NAME_KEY);
+ assertDoesNotContain(additionalProperties, TYPE_KEY);
+ assertDoesNotContain(additionalProperties, TRANSACTION_NAME_KEY);
+ Map<String, String> map = new HashMap<>(additionalProperties);
+ map.put(MODULE_FACTORY_NAME_KEY, moduleName);
+ map.put(INSTANCE_NAME_KEY, instanceName);
+ map.put(TYPE_KEY, TYPE_RUNTIME_BEAN);
+ return createON(ON_DOMAIN, map);
+ }
+
+ private static Set<String> blacklist = new HashSet<>(Arrays.asList(
+ MODULE_FACTORY_NAME_KEY, INSTANCE_NAME_KEY, TYPE_KEY));
+
+ public static Map<String, String> getAdditionalPropertiesOfRuntimeBeanName(
+ ObjectName on) {
+ checkType(on, TYPE_RUNTIME_BEAN);
+ Map<String, String> allProperties = getAdditionalProperties(on);
+ Map<String, String> result = new HashMap<>();
+ for (Entry<String, String> entry : allProperties.entrySet()) {
+ if (blacklist.contains(entry.getKey()) == false) {
+ result.put(entry.getKey(), entry.getValue());
+ }
+ }
+ return result;
+ }
+
+ public static Map<String, String> getAdditionalProperties(ObjectName on) {
+ Hashtable<String, String> keyPropertyList = on.getKeyPropertyList();
+ Map<String, String> result = new HashMap<>();
+ for (Entry<String, String> entry : keyPropertyList.entrySet()) {
+ result.put(entry.getKey(), entry.getValue());
+ }
+ return result;
+ }
+
+ public static void checkDomain(ObjectName objectName) {
+ if (!ON_DOMAIN.equals(objectName.getDomain())) {
+ throw new IllegalArgumentException("Wrong domain " + objectName);
+ }
+
+ }
+
+ public static void checkType(ObjectName objectName, String type) {
+ if (!type.equals(objectName.getKeyProperty(TYPE_KEY))) {
+ throw new IllegalArgumentException("Wrong type, expected '" + type
+ + "', got " + objectName);
+ }
+ }
+
+ public static ObjectName createModulePattern(String moduleName,
+ String instanceName) {
+ if (moduleName == null)
+ moduleName = "*";
+ if (instanceName == null)
+ instanceName = "*";
+ // do not return object names containing transaction name
+ ObjectName namePattern = ObjectNameUtil
+ .createON(ObjectNameUtil.ON_DOMAIN + ":"
+ + ObjectNameUtil.TYPE_KEY + "="
+ + ObjectNameUtil.TYPE_MODULE + ","
+ + ObjectNameUtil.MODULE_FACTORY_NAME_KEY + "="
+ + moduleName + "," + ""
+ + ObjectNameUtil.INSTANCE_NAME_KEY + "=" + instanceName);
+ return namePattern;
+ }
+
+ public static ObjectName createModulePattern(String ifcName,
+ String instanceName, String transactionName) {
+ return ObjectNameUtil.createON(ObjectNameUtil.ON_DOMAIN
+ + ":type=Module," + ObjectNameUtil.MODULE_FACTORY_NAME_KEY
+ + "=" + ifcName + "," + ObjectNameUtil.INSTANCE_NAME_KEY + "="
+ + instanceName + "," + ObjectNameUtil.TRANSACTION_NAME_KEY
+ + "=" + transactionName);
+ }
+
+ public static ObjectName createRuntimeBeanPattern(String moduleName,
+ String instanceName) {
+ return ObjectNameUtil.createON(ObjectNameUtil.ON_DOMAIN + ":"
+ + ObjectNameUtil.TYPE_KEY + "="
+ + ObjectNameUtil.TYPE_RUNTIME_BEAN + ","
+ + ObjectNameUtil.MODULE_FACTORY_NAME_KEY + "=" + moduleName
+ + "," + ObjectNameUtil.INSTANCE_NAME_KEY + "=" + instanceName
+ + ",*");
+
+ }
+
+ public static ModuleIdentifier fromON(ObjectName objectName,
+ String expectedType) {
+ checkType(objectName, expectedType);
+ String factoryName = getFactoryName(objectName);
+ if (factoryName == null)
+ throw new IllegalArgumentException(
+ "ObjectName does not contain module name");
+ String instanceName = getInstanceName(objectName);
+ if (instanceName == null)
+ throw new IllegalArgumentException(
+ "ObjectName does not contain instance name");
+ return new ModuleIdentifier(factoryName, instanceName);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.api.jmx.constants;
+
+import javax.management.ObjectName;
+
+public class ConfigRegistryConstants {
+
+ public static final String TYPE_CONFIG_REGISTRY = "ConfigRegistry";
+
+ public static final String ON_DOMAIN = "org.opendaylight.controller";
+
+ public static final String TYPE_KEY = "type";
+
+ public static final ObjectName OBJECT_NAME = createONWithDomainAndType(TYPE_CONFIG_REGISTRY);
+
+ public static String GET_AVAILABLE_MODULE_NAMES_ATTRIBUT_NAME = "AvailableModuleNames";
+
+ public static ObjectName createONWithDomainAndType(String type) {
+ return createON(ON_DOMAIN, TYPE_KEY, type);
+ }
+
+ public static ObjectName createON(String name, String key, String value) {
+ try {
+ return new ObjectName(name, key, value);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.api.runtime;
+
+import javax.management.ObjectName;
+
+public interface HierarchicalRuntimeBeanRegistration extends AutoCloseable {
+
+ ObjectName getObjectName();
+
+ HierarchicalRuntimeBeanRegistration register(String key, String value,
+ RuntimeBean mxBean);
+
+ /**
+ * Unregister beans that were registered using this registrator and its
+ * child registrators. This method is not idempotent, it is not allowed to
+ * use this instance or child registrators after they are closed.
+ */
+ @Override
+ void close();
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.api.runtime;
+
+import java.io.Closeable;
+
+/**
+ * Entry point for runtime bean functionality. Allows for registering root
+ * runtime bean, returning an object that allows for hierarchical registrations.
+ */
+public interface RootRuntimeBeanRegistrator extends Closeable {
+
+ HierarchicalRuntimeBeanRegistration registerRoot(RuntimeBean mxBean);
+
+ /**
+ * Close all runtime beans. This method is idempotent. It is allowed to use
+ * this instance to register root or create new child registrators
+ * afterwards, but it is not allowed to use closed registrations.
+ */
+ @Override
+ void close();
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.api.runtime;
+
+/**
+ * Marker interface for all runtime beans
+ */
+
+public interface RuntimeBean {
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.spi;
+
+import javax.annotation.concurrent.NotThreadSafe;
+
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.annotations.RequireInterface;
+import org.opendaylight.protocol.concepts.NamedObject;
+
+/**
+ * Represents one service that is to be configured. These methods need to be
+ * implemented in addition to the usual attribute getters/setters. Dependencies
+ * should always be injected as ObjectName references to the corresponding
+ * ConfigBeans.
+ * <p>
+ * In order to guide dependency resolution, the setter method should be
+ * annotated with {@link RequireInterface}.
+ * </p>
+ * <p>
+ * Thread safety note: implementations of this interface are not required to be
+ * thread safe as thread safety is enforced by configuration manager.
+ * </p>
+ */
+@NotThreadSafe
+public interface Module extends NamedObject<ModuleIdentifier> {
+ /**
+ * This method will be called as first phase in two phase commit. Instance
+ * can check attributes, but is not allowed to do any kind of work that
+ * could leave any resources open. It is prohibited to call
+ * {@link #getInstance()} on dependent {@link Module} because it would
+ * destroy separation between validation and commit phase.
+ *
+ */
+ void validate();
+
+ /**
+ * Returns 'live' object that was configured using this object. It is
+ * allowed to call this method only after all ConfigBeans were validated. In
+ * this method new resources might be opened or old instance might be
+ * modified. Note that when obtaining dependent Module using
+ * {@link org.opendaylight.controller.config.api.DependencyResolver#validateDependency(Class, javax.management.ObjectName, String)}
+ * a proxy will be created that will disallow calling this method before
+ * second commit phase begins.
+ *
+ * @return closeable instance: After bundle update the factory might be able
+ * to copy old configuration into new one without being able to cast
+ * Module or the instance. Thus to clean up old instance, it will
+ * call close().
+ */
+ AutoCloseable getInstance();
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.spi;
+
+import javax.management.DynamicMBean;
+
+import org.opendaylight.controller.config.api.DependencyResolver;
+import org.opendaylight.controller.config.api.DynamicMBeanWithInstance;
+import org.opendaylight.controller.config.api.annotations.AbstractServiceInterface;
+
+/**
+ * Factory which creates {@link Module instances. An instance of this interface
+ * needs to be exported into the OSGi Service Registry. Such an instance
+ * provides metadata describing services which can be published from it.
+ *
+ * Each {@link Module } can optionally be instantiated with a
+ * {@link DynamicMBean} which represents the configuration of the currently
+ * running instance.
+ */
+public interface ModuleFactory {
+
+ /**
+ * Returns the human-friendly implementation name. This value needs to be
+ * unique within all implementations of all interfaces returned by
+ * getImplementedInterfaces().
+ *
+ * @return human-friendly implementation name
+ */
+ public String getImplementationName();
+
+ /**
+ * Create a new Module instance. The returned object is expected to use the
+ * dependencyResolver provided when resolving ObjectNames to actual Module
+ * instances.
+ *
+ * @param dependencyResolver
+ * This resolver will return actual config mbean based on its
+ * ObjectName.
+ * @return newly created module
+ *
+ */
+ public Module createModule(String instanceName,
+ DependencyResolver dependencyResolver);
+
+ /**
+ * Create a new Module instance. The returned object is expected to use the
+ * dependencyResolver provided when resolving ObjectNames to actual Module
+ * instances. A reference to an abstract view of the previous configuration
+ * is also provided in the form of a {@link DynamicMBean}. Implementations
+ * should use the MBeanInfo interface to understand the structure of the
+ * configuration information.
+ *
+ * Structural information impacts hot-swap operations in that in order to
+ * perform such a swap the newly loaded code needs to understand the
+ * previously-running instance configuration layout and how to map it onto
+ * itself.
+ *
+ * @param dependencyResolver
+ * This resolver will return actual config mbean based on its
+ * ObjectName.
+ * @param old
+ * existing module from platform MBeanServer that is being
+ * reconfigured. Implementations should inspect its attributes
+ * using {@link DynamicMBean#getAttribute(String)} and set those
+ * attributes on newly created module. If reconfiguration of live
+ * instances is supported, this live instance can be retreived
+ * using
+ * {@link org.opendaylight.controller.config.api.DynamicMBeanWithInstance#getInstance()}
+ * . It is possible that casting this old instance throws
+ * {@link ClassCastException} when OSGi bundle is being updated.
+ * In this case, implementation should revert to creating new
+ * instance.
+ * @return newly created module
+ * @throws Exception
+ * if it is not possible to recover configuration from old. This
+ * leaves server in a running state but no configuration
+ * transaction can be created.
+ */
+ public Module createModule(String instanceName,
+ DependencyResolver dependencyResolver, DynamicMBeanWithInstance old)
+ throws Exception;
+
+ boolean isModuleImplementingServiceInterface(
+ Class<? extends AbstractServiceInterface> serviceInterface);
+
+}
--- /dev/null
+// vi: set smarttab et sw=4 tabstop=4:
+module config {
+ yang-version 1;
+ namespace "urn:opendaylight:params:xml:ns:yang:controller:config";
+ prefix "config";
+
+
+ description
+ "This module contains the base YANG definitions for NS-OS
+ configuration subsystem. The system modeled revolves around two
+ major concepts: modules and services.";
+
+ revision "2013-04-05" {
+ description
+ "Reworked to give modules their own space.";
+ }
+
+ revision "2013-04-03" {
+ description
+ "Initial revision.";
+ }
+
+ extension java-class {
+ description
+ "YANG language extension carrying the fully-qualified name of
+ a Java class. Code generation tools use the provided reference
+ to tie a specific construct to its Java representation.";
+
+ argument "name";
+ }
+
+ extension required-identity {
+ description
+ "YANG language extension which indicates that a particular
+ leafref, which points to a identityref, should additionally
+ require the target node is actually set to a descendant to
+ of a particular identity.
+
+ This is a workaround to two YANG deficiencies:
+ 1) not being able to leafref instances of identityref
+ 2) not being able to refine an identityref
+
+ This extension takes one argument, name, which MUST be the name
+ of an identity. Furthermore, that identity MUST be based,
+ directly or indirectly, on the identity, which is referenced by
+ the leaf reference, which is annotated with this extension.";
+
+ argument "name";
+ }
+
+ extension inner-state-bean {
+ description
+ "YANG language extension which indicates that a particular
+ list located under module's state should be treated as a list
+ of child state beans instead of just an ordinary list attribute";
+ }
+
+ extension provided-service {
+ description
+ "YANG language extension which indicates that a particular
+ module provides certain service. This extension can be placed
+ on identities that are based on module-type. Zero or more services
+ can be provided.
+ This extension takes one argument - name - which MUST be the name
+ of an identity. Furthermore, this identity MUST be based on
+ service-type.";
+
+ argument "name";
+ }
+
+ extension java-name-prefix {
+ description
+ "YANG language extension carrying java simple class name prefix
+ that will be taken into account when generating java code from
+ identities that are based on module-type.";
+ argument "java-prefix";
+ }
+
+ identity module-type {
+ description
+ "Module identity base type. All module identities must be derived
+ from this type. A module type uniquely defines a single atomic
+ component, such as an application. Each such component is assumed
+ to have its unique, stable and versioned configuration structure.";
+ }
+
+ identity service-type {
+ description
+ "Service identity base type. All service identities must be
+ derived from this type. A service type uniquely defines a single
+ atomic API contract, such as a Java interface, a set of C
+ function declarations, or similar.
+
+ If the service type has a corresponding Java interface, the name
+ of that interface should be attached to the derived identity MUST
+ include a java-class keyword, whose name argument points to that
+ interface.";
+ }
+
+ typedef service-type-ref {
+ description
+ "Internal type of references to service type identity.";
+
+ type identityref {
+ base service-type;
+ }
+ }
+
+ grouping service-ref {
+ description
+ "Type of references to a particular service instance. This type
+ can be used when defining module configuration to refer to a
+ particular service instance. Containers using this grouping
+ should not define anything else. The run-time implementation
+ is expected to inject a reference to the service as the value
+ of the container.";
+
+ leaf type {
+ description
+ "Type of the service being referenced. Users of this grouping
+ should refine this leaf with required-identity pointing to
+ the actual service-type which is actually required.";
+
+ mandatory true;
+ type leafref {
+ path "/config:services/config:service/config:type";
+ }
+ }
+
+ leaf name {
+ mandatory true;
+ type leafref {
+ path "/config:services/config:service[config:type=current()/../type]/config:instance/config:name";
+ }
+ }
+ }
+
+ container modules {
+ description
+ "Top level container encapsulating configuration of all modules.";
+
+ list module {
+ key "name";
+ leaf name {
+ description "Unique module instance name";
+ type string;
+ mandatory true;
+ }
+
+ leaf type {
+ type identityref {
+ base module-type;
+ }
+ mandatory true;
+ }
+
+ choice configuration {
+ mandatory true;
+ config true;
+ }
+
+ choice state {
+ config false;
+ }
+ }
+ }
+
+
+ container services {
+ list service {
+ key "type";
+ leaf type {
+ type service-type-ref;
+ }
+ list instance {
+ key "name";
+ leaf name {
+ type string;
+ }
+
+ leaf provider {
+ mandatory true;
+ type leafref {
+ path "/modules/module/name";
+ }
+ }
+ }
+ }
+ }
+
+
+}
--- /dev/null
+module rpc-context {
+ yang-version 1;
+ namespace "urn:ietf:params:xml:ns:yang:rpc-context";
+ prefix "rpcx";
+
+ organization "TBD";
+
+ contact "TBD";
+
+ description "";
+
+ revision 2013-06-17 {
+ description "Initial mock";
+ }
+
+
+ grouping rpc-context-ref {
+ description "A reference to RPC context.";
+ leaf context-instance {
+ type instance-identifier;
+ description "Pointer to the context. ";
+ }
+ }
+
+ extension "rpc-context-instance" {
+ description
+ "Marks enclosing (parent) schema node as suitable RPC context.
+ The argument is identity which is used to identify RPC context
+ type.";
+ argument "context-type";
+ }
+}
--- /dev/null
+target
+.classpath
+.settings
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <artifactId>config-subsystem</artifactId>
+ <groupId>org.opendaylight</groupId>
+ <version>0.2.1-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+ <artifactId>config-manager</artifactId>
+ <name>${project.artifactId}</name>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <!-- compile dependencies -->
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>config-api</artifactId>
+ <version>0.2.1-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.googlecode.json-simple</groupId>
+ <artifactId>json-simple</artifactId>
+ <version>1.1</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ </dependency>
+
+ <!-- test dependencies -->
+ <dependency>
+ <groupId>org.opendaylight.bgpcep</groupId>
+ <artifactId>mockito-configuration</artifactId>
+ <version>0.2.0-SNAPSHOT</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>config-util</artifactId>
+ <version>0.2.1-SNAPSHOT</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>config-util</artifactId>
+ <version>0.2.1-SNAPSHOT</version>
+ <scope>test</scope>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Bundle-Activator>org.opendaylight.controller.config.manager.impl.osgi.ConfigManagerActivator
+ </Bundle-Activator>
+ <Private-Package>
+ org.opendaylight.controller.config.manager.*,
+ javax.annotation.*,
+ </Private-Package>
+ <Import-Package>
+ org.opendaylight.controller.config.api.*,
+ org.opendaylight.controller.config.spi.*,
+ org.slf4j,
+ javax.management,
+ org.osgi.framework,
+ org.opendaylight.protocol.concepts,
+ org.apache.commons.io,
+ org.osgi.util.tracker,
+ </Import-Package>
+ <Export-Package>
+ </Export-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <forkCount>1</forkCount>
+ <reuseForks>false</reuseForks>
+ <perCoreThreadCount>false</perCoreThreadCount>
+ <threadCount>1</threadCount>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.concurrent.Immutable;
+
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+
+/**
+ * Structure obtained during first phase commit, contains destroyed modules from
+ * previous transactions that need to be closed and all committed modules with
+ * meta data.
+ */
+@Immutable
+public class CommitInfo {
+ private final List<DestroyedModule> destroyedFromPreviousTransactions;
+ private final Map<ModuleIdentifier, ModuleInternalTransactionalInfo> commitMap;
+
+ public CommitInfo(List<DestroyedModule> destroyedFromPreviousTransactions,
+ Map<ModuleIdentifier, ModuleInternalTransactionalInfo> commitMap) {
+ this.destroyedFromPreviousTransactions = Collections
+ .unmodifiableList(destroyedFromPreviousTransactions);
+ this.commitMap = Collections.unmodifiableMap(commitMap);
+ }
+
+ /**
+ * Get ordered list of modules that can be closed in the same order, i.e.
+ * first element will be a leaf on which no other module depends, n-th
+ * element can only have dependencies with index smaller than n.
+ */
+ public List<DestroyedModule> getDestroyedFromPreviousTransactions() {
+ return destroyedFromPreviousTransactions;
+ }
+
+ public Map<ModuleIdentifier, ModuleInternalTransactionalInfo> getCommitted() {
+ return commitMap;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.annotation.concurrent.GuardedBy;
+import javax.annotation.concurrent.NotThreadSafe;
+import javax.annotation.concurrent.ThreadSafe;
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.InstanceNotFoundException;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerFactory;
+import javax.management.ObjectName;
+
+import org.opendaylight.controller.config.api.ConflictingVersionException;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.RuntimeBeanRegistratorAwareModule;
+import org.opendaylight.controller.config.api.ValidationException;
+import org.opendaylight.controller.config.api.jmx.CommitStatus;
+import org.opendaylight.controller.config.api.jmx.ConfigRegistryMXBean;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.manager.impl.dynamicmbean.DynamicReadableWrapper;
+import org.opendaylight.controller.config.manager.impl.factoriesresolver.HierarchicalConfigMBeanFactoriesHolder;
+import org.opendaylight.controller.config.manager.impl.factoriesresolver.ModuleFactoriesResolver;
+import org.opendaylight.controller.config.manager.impl.jmx.BaseJMXRegistrator;
+import org.opendaylight.controller.config.manager.impl.jmx.ModuleJMXRegistrator;
+import org.opendaylight.controller.config.manager.impl.jmx.RootRuntimeBeanRegistratorImpl;
+import org.opendaylight.controller.config.manager.impl.jmx.TransactionJMXRegistrator;
+import org.opendaylight.controller.config.manager.impl.osgi.BeanToOsgiServiceManager;
+import org.opendaylight.controller.config.manager.impl.osgi.BeanToOsgiServiceManager.OsgiRegistration;
+import org.opendaylight.controller.config.manager.impl.util.LookupBeansUtil;
+import org.opendaylight.controller.config.spi.Module;
+import org.osgi.framework.BundleContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Singleton that is responsible for creating and committing Config
+ * Transactions. It is registered in Platform MBean Server.
+ */
+@ThreadSafe
+public class ConfigRegistryImpl implements AutoCloseable,
+ ConfigRegistryImplMXBean {
+ private static final Logger logger = LoggerFactory
+ .getLogger(ConfigRegistryImpl.class);
+
+ private final ModuleFactoriesResolver resolver;
+ private final MBeanServer configMBeanServer;
+ @GuardedBy("this")
+ private long version = 0;
+ @GuardedBy("this")
+ private long versionCounter = 0;
+
+ /**
+ * Contains current configuration in form of {moduleName:{instanceName,read
+ * only module}} for copying state to new transaction. Each running module
+ * is present just once, no matter how many interfaces it exposes.
+ */
+ @GuardedBy("this")
+ private final ConfigHolder currentConfig = new ConfigHolder();
+
+ /**
+ * Will return true unless there was a transaction that succeeded during
+ * validation but failed in second phase of commit. In this case the server
+ * is unstable and its state is undefined.
+ */
+ @GuardedBy("this")
+ private boolean isHealthy = true;
+
+ /**
+ * Holds Map<transactionName, transactionController> and purges it each time
+ * its content is requested.
+ */
+ @GuardedBy("this")
+ private final TransactionsHolder transactionsHolder = new TransactionsHolder();
+
+ private final BaseJMXRegistrator baseJMXRegistrator;
+
+ private final BeanToOsgiServiceManager beanToOsgiServiceManager;
+
+ // internal jmx server for read only beans
+ private final MBeanServer registryMBeanServer;
+ // internal jmx server shared by all transactions
+ private final MBeanServer transactionsMBeanServer;
+
+ // constructor
+ public ConfigRegistryImpl(ModuleFactoriesResolver resolver,
+ BundleContext bundleContext, MBeanServer configMBeanServer) {
+ this(resolver, bundleContext, configMBeanServer,
+ new BaseJMXRegistrator(configMBeanServer));
+ }
+
+ // constructor
+ public ConfigRegistryImpl(ModuleFactoriesResolver resolver,
+ BundleContext bundleContext, MBeanServer configMBeanServer,
+ BaseJMXRegistrator baseJMXRegistrator) {
+ this.resolver = resolver;
+ this.beanToOsgiServiceManager = new BeanToOsgiServiceManager(
+ bundleContext);
+ this.configMBeanServer = configMBeanServer;
+ this.baseJMXRegistrator = baseJMXRegistrator;
+ this.registryMBeanServer = MBeanServerFactory
+ .createMBeanServer("ConfigRegistry" + configMBeanServer.getDefaultDomain());
+ this.transactionsMBeanServer = MBeanServerFactory
+ .createMBeanServer("ConfigTransactions" + configMBeanServer.getDefaultDomain());
+ }
+
+ /**
+ * Create new {@link ConfigTransactionControllerImpl }
+ */
+ @Override
+ public synchronized ObjectName beginConfig() {
+ return beginConfigInternal().getControllerObjectName();
+ }
+
+ private synchronized ConfigTransactionControllerInternal beginConfigInternal() {
+ versionCounter++;
+ String transactionName = "ConfigTransaction-" + version + "-"
+ + versionCounter;
+ TransactionJMXRegistrator transactionRegistrator = baseJMXRegistrator
+ .createTransactionJMXRegistrator(transactionName);
+ ConfigTransactionControllerInternal transactionController = new ConfigTransactionControllerImpl(
+ transactionName, transactionRegistrator, version,
+ versionCounter, resolver.getAllFactories(),
+ transactionsMBeanServer, configMBeanServer);
+ try {
+ transactionRegistrator.registerMBean(transactionController, transactionController.getControllerObjectName
+ ());
+ } catch (InstanceAlreadyExistsException e) {
+ throw new IllegalStateException(e);
+ }
+
+ // copy old configuration to this server
+ for (ModuleInternalInfo oldConfigInfo : currentConfig.getEntries()) {
+ try {
+ transactionController.copyExistingModule(oldConfigInfo);
+ } catch (InstanceAlreadyExistsException e) {
+ throw new IllegalStateException("Error while copying "
+ + oldConfigInfo, e);
+ }
+ }
+ transactionsHolder.add(transactionName, transactionController);
+ return transactionController;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public synchronized CommitStatus commitConfig(
+ ObjectName transactionControllerON)
+ throws ConflictingVersionException, ValidationException {
+ final String transactionName = ObjectNameUtil
+ .getTransactionName(transactionControllerON);
+ logger.info(
+ "About to commit {}. Current parentVersion: {}, versionCounter {}",
+ transactionName, version, versionCounter);
+
+ // find ConfigTransactionController
+ Map<String, ConfigTransactionControllerInternal> transactions = transactionsHolder
+ .getCurrentTransactions();
+ ConfigTransactionControllerInternal configTransactionController = transactions
+ .get(transactionName);
+ if (configTransactionController == null) {
+ throw new IllegalArgumentException(String.format(
+ "Transaction with name '%s' not found", transactionName));
+ }
+ // check optimistic lock
+ if (version != configTransactionController.getParentVersion()) {
+ throw new ConflictingVersionException(
+ String.format(
+ "Optimistic lock failed. Expected parent version %d, was %d",
+ version,
+ configTransactionController.getParentVersion()));
+ }
+ // optimistic lock ok
+
+ CommitInfo commitInfo = configTransactionController
+ .validateBeforeCommitAndLockTransaction();
+ final ConfigRegistryImpl a = this;
+ // non recoverable from here:
+ try {
+ final CommitStatus secondPhaseCommitStatus = secondPhaseCommit(
+ configTransactionController, commitInfo);
+
+ return secondPhaseCommitStatus;
+ } catch (Throwable t) { // some libs throw Errors: e.g.
+ // javax.xml.ws.spi.FactoryFinder$ConfigurationError
+ isHealthy = false;
+ logger.error(
+ "Configuration Transaction failed on 2PC, server is unhealthy",
+ t);
+ if (t instanceof RuntimeException)
+ throw (RuntimeException) t;
+ else if (t instanceof Error)
+ throw (Error) t;
+ else
+ throw new RuntimeException(t);
+ }
+ }
+
+ private CommitStatus secondPhaseCommit(
+ ConfigTransactionControllerInternal configTransactionController,
+ CommitInfo commitInfo) {
+
+ // close instances which were destroyed by the user, including
+ // (hopefully) runtime beans
+ for (DestroyedModule toBeDestroyed : commitInfo
+ .getDestroyedFromPreviousTransactions()) {
+ toBeDestroyed.close(); // closes instance (which should close
+ // runtime jmx registrator),
+ // also closes osgi registration and ModuleJMXRegistrator
+ // registration
+ currentConfig.remove(toBeDestroyed.getName());
+ }
+
+ // set RuntimeBeanRegistrators on beans implementing
+ // RuntimeBeanRegistratorAwareModule, each module
+ // should have exactly one runtime jmx registrator.
+ Map<ModuleIdentifier, RootRuntimeBeanRegistratorImpl> runtimeRegistrators = new HashMap<>();
+ for (ModuleInternalTransactionalInfo entry : commitInfo.getCommitted()
+ .values()) {
+ RootRuntimeBeanRegistratorImpl runtimeBeanRegistrator;
+ if (entry.hasOldModule() == false) {
+ runtimeBeanRegistrator = baseJMXRegistrator
+ .createRuntimeBeanRegistrator(entry.getName());
+ } else {
+ // reuse old JMX registrator
+ runtimeBeanRegistrator = entry.getOldInternalInfo()
+ .getRuntimeBeanRegistrator();
+ }
+ // set runtime jmx registrator if required
+ Module module = entry.getModule();
+ if (module instanceof RuntimeBeanRegistratorAwareModule) {
+ ((RuntimeBeanRegistratorAwareModule) module)
+ .setRuntimeBeanRegistrator(runtimeBeanRegistrator);
+ }
+ // save it to info so it is accessible afterwards
+ runtimeRegistrators.put(entry.getName(), runtimeBeanRegistrator);
+ }
+
+ // can register runtime beans
+ List<ModuleIdentifier> orderedModuleIdentifiers = configTransactionController
+ .secondPhaseCommit();
+
+ // copy configuration to read only mode
+ List<ObjectName> newInstances = new LinkedList<>();
+ List<ObjectName> reusedInstances = new LinkedList<>();
+ List<ObjectName> recreatedInstances = new LinkedList<>();
+
+ Map<Module, ModuleInternalInfo> newConfigEntries = new HashMap<>();
+
+ int orderingIdx = 0;
+ for (ModuleIdentifier moduleIdentifier : orderedModuleIdentifiers) {
+ ModuleInternalTransactionalInfo entry = commitInfo.getCommitted()
+ .get(moduleIdentifier);
+ if (entry == null)
+ throw new NullPointerException("Module not found "
+ + moduleIdentifier);
+ Module module = entry.getModule();
+ ObjectName primaryReadOnlyON = ObjectNameUtil
+ .createReadOnlyModuleON(moduleIdentifier);
+
+ // determine if current instance was recreated or reused or is new
+
+ // rules for closing resources:
+ // osgi registration - will be (re)created every time, so it needs
+ // to be closed here
+ // module jmx registration - will be (re)created every time, needs
+ // to be closed here
+ // runtime jmx registration - should be taken care of by module
+ // itself
+ // instance - is closed only if it was destroyed
+ ModuleJMXRegistrator newModuleJMXRegistrator = baseJMXRegistrator
+ .createModuleJMXRegistrator();
+
+ if (entry.hasOldModule()) {
+ ModuleInternalInfo oldInternalInfo = entry.getOldInternalInfo();
+ DynamicReadableWrapper oldReadableConfigBean = oldInternalInfo
+ .getReadableModule();
+ currentConfig.remove(entry.getName());
+
+ // test if old instance == new instance
+ if (oldReadableConfigBean.getInstance().equals(
+ module.getInstance())) {
+ // reused old instance:
+ // wrap in readable dynamic mbean
+ reusedInstances.add(primaryReadOnlyON);
+ } else {
+ // recreated instance:
+ // it is responsibility of module to call the old instance -
+ // we just need to unregister configbean
+ recreatedInstances.add(primaryReadOnlyON);
+ }
+ // close old osgi registration in any case
+ oldInternalInfo.getOsgiRegistration().close();
+ // close old module jmx registrator
+ oldInternalInfo.getModuleJMXRegistrator().close();
+ } else {
+ // new instance:
+ // wrap in readable dynamic mbean
+ newInstances.add(primaryReadOnlyON);
+ }
+
+ DynamicReadableWrapper newReadableConfigBean = new DynamicReadableWrapper(
+ module, module.getInstance(), moduleIdentifier,
+ registryMBeanServer, configMBeanServer);
+
+ // register to JMX
+ try {
+ newModuleJMXRegistrator.registerMBean(newReadableConfigBean,
+ primaryReadOnlyON);
+ } catch (InstanceAlreadyExistsException e) {
+ throw new IllegalStateException(e);
+ }
+
+ // register to OSGi
+ OsgiRegistration osgiRegistration = beanToOsgiServiceManager
+ .registerToOsgi(module.getClass(),
+ newReadableConfigBean.getInstance(),
+ entry.getName());
+
+ RootRuntimeBeanRegistratorImpl runtimeBeanRegistrator = runtimeRegistrators
+ .get(entry.getName());
+ ModuleInternalInfo newInfo = new ModuleInternalInfo(
+ entry.getName(), newReadableConfigBean, osgiRegistration,
+ runtimeBeanRegistrator, newModuleJMXRegistrator,
+ orderingIdx);
+
+ newConfigEntries.put(module, newInfo);
+ orderingIdx++;
+ }
+ currentConfig.addAll(newConfigEntries.values());
+
+ // update version
+ version = configTransactionController.getVersion();
+ return new CommitStatus(newInstances, reusedInstances,
+ recreatedInstances);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public synchronized List<ObjectName> getOpenConfigs() {
+ Map<String, ConfigTransactionControllerInternal> transactions = transactionsHolder
+ .getCurrentTransactions();
+ List<ObjectName> result = new ArrayList<>(transactions.size());
+ for (ConfigTransactionControllerInternal configTransactionController : transactions
+ .values()) {
+ result.add(configTransactionController.getControllerObjectName());
+ }
+ return result;
+ }
+
+ /**
+ * Abort open transactions and unregister read only modules. Since this
+ * class is not responsible for registering itself under
+ * {@link ConfigRegistryMXBean#OBJECT_NAME}, it will not unregister itself
+ * here.
+ */
+ @Override
+ public synchronized void close() {
+ // abort transactions
+ Map<String, ConfigTransactionControllerInternal> transactions = transactionsHolder
+ .getCurrentTransactions();
+ for (ConfigTransactionControllerInternal configTransactionController : transactions
+ .values()) {
+ try {
+ configTransactionController.abortConfig();
+ } catch (RuntimeException e) {
+ logger.warn("Ignoring exception while aborting {}",
+ configTransactionController, e);
+ }
+ }
+
+ // destroy all live objects one after another in order of the dependency
+ // hierarchy
+ List<DestroyedModule> destroyedModules = currentConfig
+ .getModulesToBeDestroyed();
+ for (DestroyedModule destroyedModule : destroyedModules) {
+ destroyedModule.close();
+ }
+ // unregister MBeans that failed to unregister properly
+ baseJMXRegistrator.close();
+ // remove jmx servers
+ MBeanServerFactory.releaseMBeanServer(registryMBeanServer);
+ MBeanServerFactory.releaseMBeanServer(transactionsMBeanServer);
+
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public long getVersion() {
+ return version;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Set<String> getAvailableModuleNames() {
+ return new HierarchicalConfigMBeanFactoriesHolder(
+ resolver.getAllFactories()).getModuleNames();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isHealthy() {
+ return isHealthy;
+ }
+
+ // filtering methods
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Set<ObjectName> lookupConfigBeans() {
+ return lookupConfigBeans("*", "*");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Set<ObjectName> lookupConfigBeans(String moduleName) {
+ return lookupConfigBeans(moduleName, "*");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ObjectName lookupConfigBean(String moduleName, String instanceName)
+ throws InstanceNotFoundException {
+ return LookupBeansUtil.lookupConfigBean(this, moduleName, instanceName);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Set<ObjectName> lookupConfigBeans(String moduleName,
+ String instanceName) {
+ ObjectName namePattern = ObjectNameUtil.createModulePattern(moduleName,
+ instanceName);
+ return baseJMXRegistrator.queryNames(namePattern, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Set<ObjectName> lookupRuntimeBeans() {
+ return lookupRuntimeBeans("*", "*");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Set<ObjectName> lookupRuntimeBeans(String moduleName,
+ String instanceName) {
+ if (moduleName == null)
+ moduleName = "*";
+ if (instanceName == null)
+ instanceName = "*";
+ ObjectName namePattern = ObjectNameUtil.createRuntimeBeanPattern(
+ moduleName, instanceName);
+ return baseJMXRegistrator.queryNames(namePattern, null);
+ }
+
+}
+
+/**
+ * Holds currently running modules
+ */
+@NotThreadSafe
+class ConfigHolder {
+ private final Map<ModuleIdentifier, ModuleInternalInfo> currentConfig = new HashMap<>();
+
+ /**
+ * Add all modules to the internal map. Also add service instance to OSGi
+ * Service Registry.
+ */
+ public void addAll(Collection<ModuleInternalInfo> configInfos) {
+ if (currentConfig.size() > 0) {
+ throw new IllegalStateException(
+ "Error - some config entries were not removed: "
+ + currentConfig);
+ }
+ for (ModuleInternalInfo configInfo : configInfos) {
+ add(configInfo);
+ }
+ }
+
+ private void add(ModuleInternalInfo configInfo) {
+ ModuleInternalInfo oldValue = currentConfig.put(configInfo.getName(),
+ configInfo);
+ if (oldValue != null) {
+ throw new IllegalStateException(
+ "Cannot overwrite module with same name:"
+ + configInfo.getName() + ":" + configInfo);
+ }
+ }
+
+ /**
+ * Remove entry from current config.
+ */
+ public void remove(ModuleIdentifier name) {
+ ModuleInternalInfo removed = currentConfig.remove(name);
+ if (removed == null) {
+ throw new IllegalStateException(
+ "Cannot remove from ConfigHolder - name not found:" + name);
+ }
+ }
+
+ public Collection<ModuleInternalInfo> getEntries() {
+ return currentConfig.values();
+ }
+
+ public List<DestroyedModule> getModulesToBeDestroyed() {
+ List<DestroyedModule> result = new ArrayList<>();
+ for (ModuleInternalInfo moduleInternalInfo : getEntries()) {
+ result.add(moduleInternalInfo.toDestroyedModule());
+ }
+ Collections.sort(result);
+ return result;
+ }
+}
+
+/**
+ * Holds Map<transactionName, transactionController> and purges it each time its
+ * content is requested.
+ */
+@NotThreadSafe
+class TransactionsHolder {
+ /**
+ * This map keeps transaction names and
+ * {@link ConfigTransactionControllerInternal} instances, because platform
+ * MBeanServer transforms mbeans into another representation. Map is cleaned
+ * every time current transactions are requested.
+ *
+ */
+ @GuardedBy("ConfigRegistryImpl.this")
+ private final Map<String /* transactionName */, ConfigTransactionControllerInternal> transactions = new HashMap<>();
+
+ /**
+ * Can only be called from within synchronized method.
+ */
+ public void add(String transactionName,
+ ConfigTransactionControllerInternal transactionController) {
+ Object oldValue = transactions.put(transactionName,
+ transactionController);
+ if (oldValue != null) {
+ throw new IllegalStateException(
+ "Error: two transactions with same name");
+ }
+ }
+
+ /**
+ * Purges closed transactions from transactions map. Can only be called from
+ * within synchronized method. Calling this method more than once within the
+ * method can modify the resulting map that was obtained in previous calls.
+ *
+ * @return current view on transactions map.
+ */
+ public Map<String, ConfigTransactionControllerInternal> getCurrentTransactions() {
+ // first, remove closed transaction
+ for (Iterator<Entry<String, ConfigTransactionControllerInternal>> it = transactions
+ .entrySet().iterator(); it.hasNext();) {
+ Entry<String, ConfigTransactionControllerInternal> entry = it
+ .next();
+ if (entry.getValue().isClosed()) {
+ it.remove();
+ }
+ }
+ return Collections.unmodifiableMap(transactions);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl;
+
+import org.opendaylight.controller.config.api.ConfigRegistry;
+
+/**
+ * Exposes version of config registry.
+ */
+public interface ConfigRegistryImplMXBean extends ConfigRegistry {
+ /**
+ * @return version of last committed transaction that is now used as base
+ * version. Transactions can only be committed if their parent
+ * version matches this value, that means, transaction must be
+ * started after last one has been committed.
+ */
+ long getVersion();
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl;
+
+import static java.lang.String.format;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+import javax.management.DynamicMBean;
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.InstanceNotFoundException;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import org.opendaylight.controller.config.api.DependencyResolver;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.ValidationException;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.manager.impl.dependencyresolver.DependencyResolverManager;
+import org.opendaylight.controller.config.manager.impl.dynamicmbean.DynamicWritableWrapper;
+import org.opendaylight.controller.config.manager.impl.dynamicmbean.ReadOnlyAtomicBoolean;
+import org.opendaylight.controller.config.manager.impl.dynamicmbean.ReadOnlyAtomicBoolean.ReadOnlyAtomicBooleanImpl;
+import org.opendaylight.controller.config.manager.impl.factoriesresolver.HierarchicalConfigMBeanFactoriesHolder;
+import org.opendaylight.controller.config.manager.impl.jmx.TransactionJMXRegistrator;
+import org.opendaylight.controller.config.manager.impl.jmx.TransactionModuleJMXRegistrator;
+import org.opendaylight.controller.config.manager.impl.jmx.TransactionModuleJMXRegistrator
+ .TransactionModuleJMXRegistration;
+import org.opendaylight.controller.config.manager.impl.util.LookupBeansUtil;
+import org.opendaylight.controller.config.spi.Module;
+import org.opendaylight.controller.config.spi.ModuleFactory;
+import org.opendaylight.protocol.concepts.NamedObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is a JMX bean representing current transaction. It contains
+ * {@link #transactionIdentifier}, unique version and parent version for
+ * optimistic locking.
+ */
+class ConfigTransactionControllerImpl implements
+ ConfigTransactionControllerInternal,
+ ConfigTransactionControllerImplMXBean,
+ NamedObject<TransactionIdentifier> {
+ private static final Logger logger = LoggerFactory
+ .getLogger(ConfigTransactionControllerImpl.class);
+
+ private final TransactionIdentifier transactionIdentifier;
+ private final ObjectName controllerON;
+ private final TransactionJMXRegistrator transactionRegistrator;
+ private final TransactionModuleJMXRegistrator txModuleJMXRegistrator;
+ private final long parentVersion, currentVersion;
+ private final HierarchicalConfigMBeanFactoriesHolder factoriesHolder;
+ private final DependencyResolverManager dependencyResolverManager;
+ private final TransactionStatus transactionStatus;
+ private final MBeanServer transactionsMBeanServer;
+
+ /**
+ * Disables ability of {@link DynamicWritableWrapper} to change attributes
+ * during validation.
+ */
+ @GuardedBy("this")
+ private final AtomicBoolean configBeanModificationDisabled = new AtomicBoolean(
+ false);
+ private final ReadOnlyAtomicBoolean readOnlyAtomicBoolean = new ReadOnlyAtomicBooleanImpl(
+ configBeanModificationDisabled);
+ private final MBeanServer configMBeanServer;
+
+ public ConfigTransactionControllerImpl(String transactionName,
+ TransactionJMXRegistrator transactionRegistrator,
+ long parentVersion, long currentVersion,
+ List<? extends ModuleFactory> currentlyRegisteredFactories,
+ MBeanServer transactionsMBeanServer, MBeanServer configMBeanServer) {
+
+ this.transactionIdentifier = new TransactionIdentifier(transactionName);
+ this.controllerON = ObjectNameUtil
+ .createTransactionControllerON(transactionName);
+ this.transactionRegistrator = transactionRegistrator;
+ txModuleJMXRegistrator = transactionRegistrator
+ .createTransactionModuleJMXRegistrator();
+ this.parentVersion = parentVersion;
+ this.currentVersion = currentVersion;
+ this.factoriesHolder = new HierarchicalConfigMBeanFactoriesHolder(
+ currentlyRegisteredFactories);
+ this.transactionStatus = new TransactionStatus();
+ this.dependencyResolverManager = new DependencyResolverManager(
+ transactionName, transactionStatus);
+ this.transactionsMBeanServer = transactionsMBeanServer;
+ this.configMBeanServer = configMBeanServer;
+ }
+
+ @Override
+ public synchronized void copyExistingModule(
+ ModuleInternalInfo oldConfigBeanInfo)
+ throws InstanceAlreadyExistsException {
+ transactionStatus.checkNotCommitStarted();
+ transactionStatus.checkNotAborted();
+ ModuleIdentifier moduleIdentifier = oldConfigBeanInfo.getName();
+ dependencyResolverManager.assertNotExists(moduleIdentifier);
+
+ ModuleFactory moduleFactory = factoriesHolder
+ .findByModuleName(moduleIdentifier.getFactoryName());
+
+ Module module;
+ DependencyResolver dependencyResolver = dependencyResolverManager
+ .getOrCreate(moduleIdentifier);
+ try {
+ module = moduleFactory.createModule(
+ moduleIdentifier.getInstanceName(), dependencyResolver,
+ oldConfigBeanInfo.getReadableModule());
+ } catch (Exception e) {
+ throw new IllegalStateException(format(
+ "Error while copying old configuration from %s to %s",
+ oldConfigBeanInfo, moduleFactory), e);
+ }
+ putConfigBeanToJMXAndInternalMaps(moduleIdentifier, module,
+ moduleFactory, oldConfigBeanInfo);
+ }
+
+ @Override
+ public synchronized ObjectName createModule(String factoryName,
+ String instanceName) throws InstanceAlreadyExistsException {
+
+ transactionStatus.checkNotCommitStarted();
+ transactionStatus.checkNotAborted();
+ ModuleIdentifier moduleIdentifier = new ModuleIdentifier(factoryName,
+ instanceName);
+ dependencyResolverManager.assertNotExists(moduleIdentifier);
+
+ // find factory
+ ModuleFactory moduleFactory = factoriesHolder
+ .findByModuleName(factoryName);
+ DependencyResolver dependencyResolver = dependencyResolverManager
+ .getOrCreate(moduleIdentifier);
+ Module module = moduleFactory.createModule(instanceName,
+ dependencyResolver);
+ return putConfigBeanToJMXAndInternalMaps(moduleIdentifier, module,
+ moduleFactory, null);
+ }
+
+ private synchronized ObjectName putConfigBeanToJMXAndInternalMaps(
+ ModuleIdentifier moduleIdentifier, Module module,
+ ModuleFactory moduleFactory,
+ @Nullable ModuleInternalInfo maybeOldConfigBeanInfo)
+ throws InstanceAlreadyExistsException {
+
+ DynamicMBean writableDynamicWrapper = new DynamicWritableWrapper(
+ module, moduleIdentifier, transactionIdentifier,
+ readOnlyAtomicBoolean, transactionsMBeanServer,
+ configMBeanServer);
+
+ ObjectName writableON = ObjectNameUtil.createTransactionModuleON(
+ transactionIdentifier.getName(), moduleIdentifier);
+ // put wrapper to jmx
+ TransactionModuleJMXRegistration transactionModuleJMXRegistration = txModuleJMXRegistrator
+ .registerMBean(writableDynamicWrapper, writableON);
+ ModuleInternalTransactionalInfo moduleInternalTransactionalInfo = new ModuleInternalTransactionalInfo(
+ moduleIdentifier, module, moduleFactory,
+ maybeOldConfigBeanInfo, transactionModuleJMXRegistration);
+
+ dependencyResolverManager.put(moduleInternalTransactionalInfo);
+ return writableON;
+ }
+
+ @Override
+ public void destroyModule(ObjectName objectName)
+ throws InstanceNotFoundException {
+ String foundTransactionName = ObjectNameUtil
+ .getTransactionName(objectName);
+ if (transactionIdentifier.getName().equals(foundTransactionName) == false) {
+ throw new IllegalArgumentException("Wrong transaction name "
+ + objectName);
+ }
+ ObjectNameUtil.checkDomain(objectName);
+ transactionStatus.checkNotAborted();
+ ModuleIdentifier moduleIdentifier = ObjectNameUtil.fromON(objectName,
+ ObjectNameUtil.TYPE_MODULE);
+ ModuleInternalTransactionalInfo removedTInfo = dependencyResolverManager
+ .destroyModule(moduleIdentifier);
+ // remove from jmx
+ removedTInfo.getTransactionModuleJMXRegistration().close();
+ }
+
+ @Override
+ public long getParentVersion() {
+ return parentVersion;
+ }
+
+ @Override
+ public long getVersion() {
+ return currentVersion;
+ }
+
+ @Override
+ public synchronized void validateConfig() throws ValidationException {
+ if (configBeanModificationDisabled.get())
+ throw new IllegalStateException("Cannot start validation");
+ configBeanModificationDisabled.set(true);
+ try {
+ validate_noLocks();
+ } finally {
+ configBeanModificationDisabled.set(false);
+ }
+ }
+
+ private void validate_noLocks() throws ValidationException {
+ transactionStatus.checkNotAborted();
+ logger.info("Validating transaction {}", transactionIdentifier);
+ // call validate()
+ List<ValidationException> collectedExceptions = new ArrayList<>();
+ for (Entry<ModuleIdentifier, Module> entry : dependencyResolverManager
+ .getAllModules().entrySet()) {
+ ModuleIdentifier name = entry.getKey();
+ Module module = entry.getValue();
+ try {
+ module.validate();
+ } catch (Exception e) {
+ logger.warn("Validation exception in {}", getTransactionName(),
+ e);
+ collectedExceptions.add(ValidationException
+ .createForSingleException(name, e));
+ }
+ }
+ if (collectedExceptions.size() > 0) {
+ throw ValidationException
+ .createFromCollectedValidationExceptions(collectedExceptions);
+ }
+ logger.info("Validated transaction {}", transactionIdentifier);
+ }
+
+ /**
+ * If this method passes validation, it will grab
+ * {@link TransactionStatus#secondPhaseCommitStarted} lock. This lock will
+ * prevent calling @{link #validateBeforeCommitAndLockTransaction},
+ * effectively only allowing to call {@link #secondPhaseCommit} after
+ * successful return of this method.
+ */
+ @Override
+ public synchronized CommitInfo validateBeforeCommitAndLockTransaction()
+ throws ValidationException {
+ transactionStatus.checkNotAborted();
+ transactionStatus.checkNotCommitStarted();
+ configBeanModificationDisabled.set(true);
+ try {
+ validate_noLocks();
+ } catch (ValidationException e) {
+ logger.info("Commit failed on validation");
+ configBeanModificationDisabled.set(false); // recoverable error
+ throw e;
+ }
+ // errors in this state are not recoverable. modules are not mutable
+ // anymore.
+ transactionStatus.setSecondPhaseCommitStarted();
+ return dependencyResolverManager.toCommitInfo();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public synchronized List<ModuleIdentifier> secondPhaseCommit() {
+ transactionStatus.checkNotAborted();
+ transactionStatus.checkCommitStarted();
+ if (configBeanModificationDisabled.get() == false) {
+ throw new IllegalStateException(
+ "Internal error - validateBeforeCommitAndLockTransaction should be called "
+ + "to obtain a lock");
+ }
+
+ logger.info("Committing transaction {}", transactionIdentifier);
+
+ // call getInstance()
+ for (Entry<ModuleIdentifier, Module> entry : dependencyResolverManager
+ .getAllModules().entrySet()) {
+ Module module = entry.getValue();
+ ModuleIdentifier name = entry.getKey();
+ try {
+ logger.debug("About to commit {} in transaction {}",
+ transactionIdentifier, name);
+ module.getInstance();
+ } catch (Exception e) {
+ logger.error("Commit failed on {} in transaction {}", name,
+ transactionIdentifier, e);
+ internalAbort();
+ throw new RuntimeException(
+ format("Error - getInstance() failed for %s in transaction %s",
+ name, transactionIdentifier), e);
+ }
+ }
+
+ // count dependency order
+
+ logger.info("Committed configuration {}", transactionIdentifier);
+ transactionStatus.setCommitted();
+ // unregister this and all modules from jmx
+ close();
+
+ return dependencyResolverManager.getSortedModuleIdentifiers();
+ }
+
+ @Override
+ public synchronized void abortConfig() {
+ transactionStatus.checkNotCommitStarted();
+ transactionStatus.checkNotAborted();
+ internalAbort();
+ }
+
+ private void internalAbort() {
+ transactionStatus.setAborted();
+ close();
+ }
+
+ private void close() {
+ transactionRegistrator.close();
+ }
+
+ @Override
+ public ObjectName getControllerObjectName() {
+ return controllerON;
+ }
+
+ @Override
+ public String getTransactionName() {
+ return transactionIdentifier.getName();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Set<ObjectName> lookupConfigBeans() {
+ return lookupConfigBeans("*", "*");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Set<ObjectName> lookupConfigBeans(String moduleName) {
+ return lookupConfigBeans(moduleName, "*");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ObjectName lookupConfigBean(String moduleName, String instanceName)
+ throws InstanceNotFoundException {
+ return LookupBeansUtil.lookupConfigBean(this, moduleName, instanceName);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Set<ObjectName> lookupConfigBeans(String moduleName,
+ String instanceName) {
+ ObjectName namePattern = ObjectNameUtil.createModulePattern(moduleName,
+ instanceName, transactionIdentifier.getName());
+ return txModuleJMXRegistrator.queryNames(namePattern, null);
+ }
+
+ @Override
+ public Set<String> getAvailableModuleNames() {
+ return factoriesHolder.getModuleNames();
+ }
+
+ @Override
+ public boolean isClosed() {
+ return transactionStatus.isAbortedOrCommitted();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("transactionName=");
+ sb.append(getTransactionName());
+ return sb.toString();
+ }
+
+ // @VisibleForTesting
+
+ TransactionModuleJMXRegistrator getTxModuleJMXRegistrator() {
+ return txModuleJMXRegistrator;
+ }
+
+ @Override
+ public TransactionIdentifier getName() {
+ return transactionIdentifier;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl;
+
+import org.opendaylight.controller.config.api.ConfigTransactionController;
+
+/**
+ * Exposes optimistic locking details about transaction.
+ */
+public interface ConfigTransactionControllerImplMXBean extends
+ ConfigTransactionController {
+
+ /**
+ * Get version of config registry when this transaction was created.
+ */
+ long getParentVersion();
+
+ /**
+ * Get version of current transaction.
+ */
+ long getVersion();
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl;
+
+import java.util.List;
+
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.ObjectName;
+
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.ValidationException;
+
+/**
+ * Defines contract between {@link ConfigTransactionControllerImpl} (producer)
+ * and {@link ConfigRegistryImpl} (consumer).
+ */
+interface ConfigTransactionControllerInternal extends
+ ConfigTransactionControllerImplMXBean {
+
+ /**
+ * Copy already committed module to current transaction.
+ */
+ void copyExistingModule(ModuleInternalInfo oldConfigBeanInfo)
+ throws InstanceAlreadyExistsException;
+
+ /**
+ * Call {@link org.opendaylight.controller.config.spi.Module#validate()} on
+ * all beans in transaction. Lock transaction after successful validation.
+ * This method can be called multiple times if validation fails, but cannot
+ * be called after it did not throw exception.
+ *
+ * @throws {@link RuntimeException} if validation fails. It is safe to run
+ * this method in future
+ * @return CommitInfo
+ */
+ CommitInfo validateBeforeCommitAndLockTransaction()
+ throws ValidationException;
+
+ /**
+ * Call {@link org.opendaylight.controller.config.spi.Module#getInstance()}
+ * on all beans in transaction. This method can be only called once.
+ *
+ * @throws {@link RuntimeException} commit fails, indicates bug in config
+ * bean
+ * @return ordered list of module identifiers that respects dependency
+ * order.
+ */
+ List<ModuleIdentifier> secondPhaseCommit();
+
+ /**
+ * @return ObjectName of this transaction controller
+ */
+ ObjectName getControllerObjectName();
+
+ /**
+ * @return true iif transaction was committed or aborted.
+ */
+ boolean isClosed();
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl;
+
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.manager.impl.jmx.ModuleJMXRegistrator;
+import org.opendaylight.controller.config.manager.impl.osgi.BeanToOsgiServiceManager.OsgiRegistration;
+import org.opendaylight.protocol.concepts.NamedObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Transfer object representing already committed module that needs to be
+ * destroyed. Implements comparable in order to preserve order in which modules
+ * were created. Module instances should be closed in order defined by the
+ * compareTo method.
+ */
+public class DestroyedModule implements NamedObject<ModuleIdentifier>,
+ AutoCloseable, Comparable<DestroyedModule> {
+ private static final Logger logger = LoggerFactory
+ .getLogger(DestroyedModule.class);
+
+ private final ModuleIdentifier name;
+ private final AutoCloseable instance;
+ private final ModuleJMXRegistrator oldJMXRegistrator;
+ private final OsgiRegistration osgiRegistration;
+ private final int orderingIdx;
+
+ DestroyedModule(ModuleIdentifier name, AutoCloseable instance,
+ ModuleJMXRegistrator oldJMXRegistrator,
+ OsgiRegistration osgiRegistration, int orderingIdx) {
+ this.name = name;
+ this.instance = instance;
+ this.oldJMXRegistrator = oldJMXRegistrator;
+ this.osgiRegistration = osgiRegistration;
+ this.orderingIdx = orderingIdx;
+ }
+
+ @Override
+ public ModuleIdentifier getName() {
+ return name;
+ }
+
+ @Override
+ public void close() {
+ logger.info("Destroying {}", name);
+ try {
+ instance.close();
+ } catch (Exception e) {
+ logger.error("Error while closing instance of {}", name, e);
+ }
+ try {
+ oldJMXRegistrator.close();
+ } catch (Exception e) {
+ logger.error("Error while closing jmx registrator of {}", name, e);
+ }
+ try {
+ osgiRegistration.close();
+ } catch (Exception e) {
+ logger.error("Error while closing osgi registration of {}", name, e);
+ }
+ }
+
+ @Override
+ public int compareTo(DestroyedModule o) {
+ return Integer.compare(orderingIdx, o.orderingIdx);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl;
+
+import javax.annotation.Nullable;
+
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.manager.impl.dynamicmbean.DynamicReadableWrapper;
+import org.opendaylight.controller.config.manager.impl.jmx.ModuleJMXRegistrator;
+import org.opendaylight.controller.config.manager.impl.jmx.RootRuntimeBeanRegistratorImpl;
+import org.opendaylight.controller.config.manager.impl.osgi.BeanToOsgiServiceManager.OsgiRegistration;
+import org.opendaylight.protocol.concepts.NamedObject;
+
+/**
+ * Provides metadata about Module from controller to registry.
+ */
+public class ModuleInternalInfo implements NamedObject<ModuleIdentifier>,
+ Comparable<ModuleInternalInfo> {
+
+ private final ModuleIdentifier name;
+ // this registrator is passed to runtime bean registrator and config
+ // registry to register read only module.
+ // writable modules are registered using TransactionJMXRegistrator
+ @Nullable
+ private final DynamicReadableWrapper readableModule;
+
+ private final RootRuntimeBeanRegistratorImpl runtimeBeanRegistrator;
+ // added when bean instance is registered to Osgi
+ // can be unregistered using this registration
+ private final OsgiRegistration osgiRegistration;
+ private final ModuleJMXRegistrator moduleJMXRegistrator;
+ private final int orderingIdx;
+
+ public ModuleInternalInfo(ModuleIdentifier name,
+ @Nullable DynamicReadableWrapper readableModule,
+ OsgiRegistration osgiRegistration,
+ RootRuntimeBeanRegistratorImpl runtimeBeanRegistrator,
+ ModuleJMXRegistrator moduleJMXRegistrator, int orderingIdx) {
+
+ if (osgiRegistration == null) {
+ throw new IllegalArgumentException(
+ "Parameter 'osgiRegistration' is missing");
+ }
+ if (runtimeBeanRegistrator == null) {
+ throw new IllegalArgumentException(
+ "Parameter 'runtimeBeanRegistrator' is missing");
+ }
+ this.readableModule = readableModule;
+ this.osgiRegistration = osgiRegistration;
+ this.runtimeBeanRegistrator = runtimeBeanRegistrator;
+ this.name = name;
+ this.moduleJMXRegistrator = moduleJMXRegistrator;
+ this.orderingIdx = orderingIdx;
+ }
+
+ public DynamicReadableWrapper getReadableModule() {
+ return readableModule;
+ }
+
+ public ModuleJMXRegistrator getModuleJMXRegistrator() {
+ return moduleJMXRegistrator;
+ }
+
+ /**
+ *
+ * @return iif an running instance exists in the system.
+ */
+ public boolean hasReadableModule() {
+ return readableModule != null;
+ }
+
+ @Override
+ public String toString() {
+ return "ModuleInternalInfo [name=" + name + "]";
+ }
+
+ public RootRuntimeBeanRegistratorImpl getRuntimeBeanRegistrator() {
+ return runtimeBeanRegistrator;
+ }
+
+ public OsgiRegistration getOsgiRegistration() {
+ return osgiRegistration;
+ }
+
+ @Override
+ public ModuleIdentifier getName() {
+ return name;
+ }
+
+ /**
+ * Get index representing dependency ordering within a transaction.
+ */
+ public int getOrderingIdx() {
+ return orderingIdx;
+ }
+
+ /**
+ * Compare using orderingIdx
+ */
+ @Override
+ public int compareTo(ModuleInternalInfo o) {
+ return Integer.compare(orderingIdx, o.orderingIdx);
+ }
+
+ public DestroyedModule toDestroyedModule() {
+ return new DestroyedModule(getName(),
+ getReadableModule().getInstance(), getModuleJMXRegistrator(),
+ getOsgiRegistration(), getOrderingIdx());
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl;
+
+import javax.annotation.Nullable;
+
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.manager.impl.dynamicmbean.DynamicReadableWrapper;
+import org.opendaylight.controller.config.manager.impl.jmx.TransactionModuleJMXRegistrator;
+import org.opendaylight.controller.config.manager.impl.jmx.TransactionModuleJMXRegistrator
+ .TransactionModuleJMXRegistration;
+import org.opendaylight.controller.config.spi.Module;
+import org.opendaylight.controller.config.spi.ModuleFactory;
+import org.opendaylight.protocol.concepts.NamedObject;
+
+public class ModuleInternalTransactionalInfo implements
+ NamedObject<ModuleIdentifier> {
+ private final ModuleIdentifier name;
+ private final Module module;
+ private final ModuleFactory moduleFactory;
+ @Nullable
+ private final ModuleInternalInfo maybeOldInternalInfo;
+ private final TransactionModuleJMXRegistration transactionModuleJMXRegistration;
+
+ ModuleInternalTransactionalInfo(ModuleIdentifier name, Module module,
+ ModuleFactory moduleFactory,
+ ModuleInternalInfo maybeOldInternalInfo,
+ TransactionModuleJMXRegistration transactionModuleJMXRegistration) {
+ this.name = name;
+ this.module = module;
+ this.moduleFactory = moduleFactory;
+ this.maybeOldInternalInfo = maybeOldInternalInfo;
+ this.transactionModuleJMXRegistration = transactionModuleJMXRegistration;
+ }
+
+ @Override
+ public ModuleIdentifier getName() {
+ return name;
+ }
+
+ public boolean hasOldModule() {
+ return maybeOldInternalInfo != null;
+ }
+
+ public DestroyedModule toDestroyedModule() {
+ if (maybeOldInternalInfo == null) {
+ throw new IllegalStateException("Cannot destoy uncommitted module");
+ }
+ DynamicReadableWrapper oldModule = maybeOldInternalInfo
+ .getReadableModule();
+ return new DestroyedModule(name, oldModule.getInstance(),
+ maybeOldInternalInfo.getModuleJMXRegistrator(),
+ maybeOldInternalInfo.getOsgiRegistration(),
+ maybeOldInternalInfo.getOrderingIdx());
+ }
+
+ public Module getModule() {
+ return module;
+ }
+
+ public ModuleFactory getModuleFactory() {
+ return moduleFactory;
+ }
+
+ @Nullable
+ public ModuleInternalInfo getOldInternalInfo() {
+ if (maybeOldInternalInfo == null)
+ throw new NullPointerException();
+ return maybeOldInternalInfo;
+ }
+
+ public TransactionModuleJMXRegistration getTransactionModuleJMXRegistration() {
+ return transactionModuleJMXRegistration;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl;
+
+import org.opendaylight.protocol.concepts.Identifier;
+
+public class TransactionIdentifier implements Identifier {
+ private final String name;
+
+ public TransactionIdentifier(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String toString() {
+ return "TransactionIdentifier{" + "name='" + name + '\'' + '}';
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ TransactionIdentifier that = (TransactionIdentifier) o;
+
+ if (name != null ? !name.equals(that.name) : that.name != null)
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return name != null ? name.hashCode() : 0;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl;
+
+import javax.annotation.concurrent.GuardedBy;
+
+public class TransactionStatus {
+ @GuardedBy("this")
+ private boolean secondPhaseCommitStarted = false;
+ // switches to true during abort or commit failure
+ @GuardedBy("this")
+ private boolean aborted;
+ // switches to true during second phase commit
+ @GuardedBy("this")
+ private boolean committed;
+
+ public synchronized boolean isSecondPhaseCommitStarted() {
+ return secondPhaseCommitStarted;
+ }
+
+ synchronized void setSecondPhaseCommitStarted() {
+ this.secondPhaseCommitStarted = true;
+ }
+
+ public synchronized boolean isAborted() {
+ return aborted;
+ }
+
+ synchronized void setAborted() {
+ this.aborted = true;
+ }
+
+ public synchronized boolean isCommitted() {
+ return committed;
+ }
+
+ synchronized void setCommitted() {
+ this.committed = true;
+ }
+
+ public synchronized boolean isAbortedOrCommitted() {
+ return aborted || committed;
+ }
+
+ public synchronized void checkNotCommitStarted() {
+ if (secondPhaseCommitStarted == true)
+ throw new IllegalStateException("Commit was triggered");
+ }
+
+ public synchronized void checkCommitStarted() {
+ if (secondPhaseCommitStarted == false)
+ throw new IllegalStateException("Commit was not triggered");
+ }
+
+ public synchronized void checkNotAborted() {
+ if (aborted == true)
+ throw new IllegalStateException("Configuration was aborted");
+ }
+
+ public synchronized void checkNotCommitted() {
+ if (committed == true) {
+ throw new IllegalStateException(
+ "Cannot use this method after second phase commit");
+ }
+ }
+
+ public synchronized void checkCommitted() {
+ if (committed == false) {
+ throw new IllegalStateException(
+ "Cannot use this method before second phase commit");
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.dependencyresolver;
+
+import static java.lang.String.format;
+
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import javax.annotation.concurrent.GuardedBy;
+import javax.management.ObjectName;
+
+import org.opendaylight.controller.config.api.DependencyResolver;
+import org.opendaylight.controller.config.api.JmxAttribute;
+import org.opendaylight.controller.config.api.JmxAttributeValidationException;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.annotations.AbstractServiceInterface;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.manager.impl.TransactionStatus;
+import org.opendaylight.controller.config.spi.Module;
+import org.opendaylight.controller.config.spi.ModuleFactory;
+import org.opendaylight.protocol.concepts.NamedObject;
+
+/**
+ * Protect {@link org.opendaylight.controller.config.spi.Module#getInstance()}
+ * by creating proxy that would throw exception if those methods are called
+ * during validation. Tracks dependencies for ordering purposes.
+ */
+final class DependencyResolverImpl implements DependencyResolver,
+ NamedObject<ModuleIdentifier>, Comparable<DependencyResolverImpl> {
+ private final ModulesHolder modulesHolder;
+ private final ModuleIdentifier name;
+ private final TransactionStatus transactionStatus;
+ @GuardedBy("this")
+ private final Set<ModuleIdentifier> dependencies = new HashSet<>();
+
+ DependencyResolverImpl(ModuleIdentifier currentModule,
+ TransactionStatus transactionStatus, ModulesHolder modulesHolder) {
+ this.name = currentModule;
+ this.transactionStatus = transactionStatus;
+ this.modulesHolder = modulesHolder;
+ }
+
+ @Override
+ public ModuleIdentifier getName() {
+ return name;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void validateDependency(
+ Class<? extends AbstractServiceInterface> expectedServiceInterface,
+ ObjectName dependentModuleReadOnlyON, JmxAttribute jmxAttribute) {
+
+ transactionStatus.checkNotCommitted();
+ if (expectedServiceInterface == null) {
+ throw new NullPointerException(
+ "Parameter 'expectedServiceInterface' is null");
+ }
+ if (jmxAttribute == null)
+ throw new NullPointerException("Parameter 'jmxAttribute' is null");
+
+ JmxAttributeValidationException.checkNotNull(dependentModuleReadOnlyON,
+ "is null, " + "expected dependency implementing "
+ + expectedServiceInterface, jmxAttribute);
+
+ // check that objectName belongs to this transaction - this should be
+ // stripped
+ // in DynamicWritableWrapper
+ boolean hasTransaction = ObjectNameUtil
+ .getTransactionName(dependentModuleReadOnlyON) != null;
+ JmxAttributeValidationException.checkCondition(
+ hasTransaction == false,
+ format("ObjectName should not contain "
+ + "transaction name. %s set to %s. ", jmxAttribute,
+ dependentModuleReadOnlyON), jmxAttribute);
+
+ ModuleIdentifier moduleIdentifier = ObjectNameUtil.fromON(dependentModuleReadOnlyON, ObjectNameUtil
+ .TYPE_MODULE);
+
+ ModuleFactory foundFactory = modulesHolder.findModuleFactory(moduleIdentifier, jmxAttribute);
+
+ boolean implementsSI = foundFactory
+ .isModuleImplementingServiceInterface(expectedServiceInterface);
+ if (implementsSI == false) {
+ String message = format(
+ "Found module factory does not expose expected service interface. "
+ + "Module name is %s : %s, expected service interface %s, dependent module ON %s , "
+ + "attribute %s",
+ foundFactory.getImplementationName(), foundFactory,
+ expectedServiceInterface, dependentModuleReadOnlyON,
+ jmxAttribute);
+ throw new JmxAttributeValidationException(message, jmxAttribute);
+ }
+ synchronized (this) {
+ dependencies.add(moduleIdentifier);
+ }
+ }
+
+ @Override
+ public void validateDependency(
+ Class<? extends AbstractServiceInterface> expectedServiceInterface,
+ ObjectName objectName, String attributeNameForErrorReporting) {
+ validateDependency(expectedServiceInterface, objectName,
+ new JmxAttribute(attributeNameForErrorReporting));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public <T> T resolveInstance(Class<T> expectedType, ObjectName dependentON,
+ JmxAttribute jmxAttribute) {
+ if (expectedType == null || dependentON == null || jmxAttribute == null) {
+ throw new IllegalArgumentException(format(
+ "Null parameters not allowed, got {} {} {}", expectedType,
+ dependentON, jmxAttribute));
+ }
+
+ transactionStatus.checkCommitStarted();
+ transactionStatus.checkNotCommitted();
+
+ ModuleIdentifier dependentModuleIdentifier = ObjectNameUtil.fromON(
+ dependentON, ObjectNameUtil.TYPE_MODULE);
+ Module module = modulesHolder.findModule(dependentModuleIdentifier,
+ jmxAttribute);
+ synchronized (this) {
+ dependencies.add(dependentModuleIdentifier);
+ }
+ AutoCloseable instance = module.getInstance();
+ if (instance == null) {
+ String message = format(
+ "Error while %s resolving instance %s. getInstance() returned null. "
+ + "Expected type %s , attribute %s", name,
+ dependentModuleIdentifier, expectedType, jmxAttribute);
+ throw new JmxAttributeValidationException(message, jmxAttribute);
+ }
+ try {
+ T result = expectedType.cast(instance);
+ return result;
+ } catch (ClassCastException e) {
+ String message = format(
+ "Instance cannot be cast to expected type. Instance class is %s , "
+ + "expected type %s , attribute %s",
+ instance.getClass(), expectedType, jmxAttribute);
+ throw new JmxAttributeValidationException(message, e, jmxAttribute);
+ }
+ }
+
+ @Deprecated
+ @Override
+ public <T> T resolveInstance(Class<T> expectedType, ObjectName objectName) {
+ return resolveInstance(expectedType, objectName, new JmxAttribute(
+ "unknown attribute"));
+ }
+
+ @Override
+ public int compareTo(DependencyResolverImpl o) {
+ transactionStatus.checkCommitted();
+ return Integer.compare(getMaxDependencyDepth(),
+ o.getMaxDependencyDepth());
+ }
+
+ private Integer maxDependencyDepth;
+
+ int getMaxDependencyDepth() {
+ if (maxDependencyDepth == null) {
+ throw new IllegalStateException("Dependency depth was not computed");
+ }
+ return maxDependencyDepth;
+ }
+
+ public void countMaxDependencyDepth(DependencyResolverManager manager) {
+ transactionStatus.checkCommitted();
+ if (maxDependencyDepth == null) {
+ maxDependencyDepth = getMaxDepth(this, manager,
+ new LinkedHashSet<ModuleIdentifier>());
+ }
+ }
+
+ private static int getMaxDepth(DependencyResolverImpl impl,
+ DependencyResolverManager manager,
+ LinkedHashSet<ModuleIdentifier> chainForDetectingCycles) {
+ int maxDepth = 0;
+ LinkedHashSet<ModuleIdentifier> chainForDetectingCycles2 = new LinkedHashSet<>(
+ chainForDetectingCycles);
+ chainForDetectingCycles2.add(impl.getName());
+ for (ModuleIdentifier dependencyName : impl.dependencies) {
+ DependencyResolverImpl dependentDRI = manager
+ .getOrCreate(dependencyName);
+ if (chainForDetectingCycles2.contains(dependencyName)) {
+ throw new IllegalStateException(format(
+ "Cycle detected, {} contains {}",
+ chainForDetectingCycles2, dependencyName));
+ }
+ int subDepth;
+ if (dependentDRI.maxDependencyDepth != null) {
+ subDepth = dependentDRI.maxDependencyDepth;
+ } else {
+ subDepth = getMaxDepth(dependentDRI, manager,
+ chainForDetectingCycles2);
+ dependentDRI.maxDependencyDepth = subDepth;
+ }
+ if (subDepth + 1 > maxDepth) {
+ maxDepth = subDepth + 1;
+ }
+ }
+ impl.maxDependencyDepth = maxDepth;
+ return maxDepth;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.dependencyresolver;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.concurrent.GuardedBy;
+import javax.management.InstanceAlreadyExistsException;
+
+import org.opendaylight.controller.config.api.JmxAttribute;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.manager.impl.CommitInfo;
+import org.opendaylight.controller.config.manager.impl.ModuleInternalTransactionalInfo;
+import org.opendaylight.controller.config.manager.impl.TransactionStatus;
+import org.opendaylight.controller.config.spi.Module;
+import org.opendaylight.controller.config.spi.ModuleFactory;
+
+/**
+ * Holds information about modules being created and destroyed within this
+ * transaction. Observes usage of DependencyResolver within modules to figure
+ * out dependency tree.
+ */
+public class DependencyResolverManager implements TransactionHolder {
+ @GuardedBy("this")
+ private final Map<ModuleIdentifier, DependencyResolverImpl> moduleIdentifiersToDependencyResolverMap = new HashMap<>();
+ private final ModulesHolder modulesHolder;
+ private final TransactionStatus transactionStatus;
+
+ public DependencyResolverManager(String transactionName,
+ TransactionStatus transactionStatus) {
+ this.modulesHolder = new ModulesHolder(transactionName);
+ this.transactionStatus = transactionStatus;
+ }
+
+ public synchronized DependencyResolverImpl getOrCreate(ModuleIdentifier name) {
+ DependencyResolverImpl dependencyResolver = moduleIdentifiersToDependencyResolverMap
+ .get(name);
+ if (dependencyResolver == null) {
+ transactionStatus.checkNotCommitted();
+ dependencyResolver = new DependencyResolverImpl(name,
+ transactionStatus, modulesHolder);
+ moduleIdentifiersToDependencyResolverMap.put(name,
+ dependencyResolver);
+ }
+ return dependencyResolver;
+ }
+
+ /**
+ * Get all dependency resolvers, including those that belong to destroyed
+ * things?
+ */
+ private List<DependencyResolverImpl> getAllSorted() {
+ transactionStatus.checkCommitted();
+ List<DependencyResolverImpl> sorted = new ArrayList<>(
+ moduleIdentifiersToDependencyResolverMap.values());
+ for (DependencyResolverImpl dri : sorted) {
+ dri.countMaxDependencyDepth(this);
+ }
+ Collections.sort(sorted);
+ return sorted;
+ }
+
+ public List<ModuleIdentifier> getSortedModuleIdentifiers() {
+ List<ModuleIdentifier> result = new ArrayList<>(
+ moduleIdentifiersToDependencyResolverMap.size());
+ for (DependencyResolverImpl dri : getAllSorted()) {
+ ModuleIdentifier driName = dri.getName();
+ result.add(driName);
+ }
+ return result;
+ }
+
+ @Override
+ public ModuleInternalTransactionalInfo destroyModule(
+ ModuleIdentifier moduleIdentifier) {
+ transactionStatus.checkNotCommitted();
+ ModuleInternalTransactionalInfo found = modulesHolder
+ .destroyModule(moduleIdentifier);
+ moduleIdentifiersToDependencyResolverMap.remove(moduleIdentifier);
+ return found;
+ }
+
+ // protect write access
+ @Override
+ public void put(
+ ModuleInternalTransactionalInfo moduleInternalTransactionalInfo) {
+ transactionStatus.checkNotCommitted();
+ modulesHolder.put(moduleInternalTransactionalInfo);
+ }
+
+ // wrapped methods:
+
+ @Override
+ public CommitInfo toCommitInfo() {
+ return modulesHolder.toCommitInfo();
+ }
+
+ @Override
+ public Module findModule(ModuleIdentifier moduleIdentifier,
+ JmxAttribute jmxAttributeForReporting) {
+ return modulesHolder.findModule(moduleIdentifier,
+ jmxAttributeForReporting);
+ }
+
+ @Override
+ public ModuleFactory findModuleFactory(ModuleIdentifier moduleIdentifier,
+ JmxAttribute jmxAttributeForReporting) {
+ return modulesHolder.findModuleFactory(moduleIdentifier,
+ jmxAttributeForReporting);
+ }
+
+ @Override
+ public Map<ModuleIdentifier, Module> getAllModules() {
+ return modulesHolder.getAllModules();
+ }
+
+ @Override
+ public void assertNotExists(ModuleIdentifier moduleIdentifier)
+ throws InstanceAlreadyExistsException {
+ modulesHolder.assertNotExists(moduleIdentifier);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.dependencyresolver;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.concurrent.GuardedBy;
+import javax.management.InstanceAlreadyExistsException;
+
+import org.opendaylight.controller.config.api.JmxAttribute;
+import org.opendaylight.controller.config.api.JmxAttributeValidationException;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.manager.impl.CommitInfo;
+import org.opendaylight.controller.config.manager.impl.DestroyedModule;
+import org.opendaylight.controller.config.manager.impl.ModuleInternalTransactionalInfo;
+import org.opendaylight.controller.config.spi.Module;
+import org.opendaylight.controller.config.spi.ModuleFactory;
+
+/**
+ * Represents modules to be committed.
+ */
+class ModulesHolder implements TransactionHolder {
+ private final String transactionName;
+ @GuardedBy("this")
+ private final Map<ModuleIdentifier, ModuleInternalTransactionalInfo> commitMap = new HashMap<>();
+
+ @GuardedBy("this")
+ private final Set<ModuleInternalTransactionalInfo> unorderedDestroyedFromPreviousTransactions = new HashSet<>();
+
+ ModulesHolder(String transactionName) {
+ this.transactionName = transactionName;
+ }
+
+ @Override
+ public CommitInfo toCommitInfo() {
+ List<DestroyedModule> orderedDestroyedFromPreviousTransactions = new ArrayList<>(
+ unorderedDestroyedFromPreviousTransactions.size());
+ for (ModuleInternalTransactionalInfo toBeDestroyed : unorderedDestroyedFromPreviousTransactions) {
+ orderedDestroyedFromPreviousTransactions.add(toBeDestroyed
+ .toDestroyedModule());
+ }
+ Collections.sort(orderedDestroyedFromPreviousTransactions);
+ return new CommitInfo(orderedDestroyedFromPreviousTransactions,
+ commitMap);
+ }
+
+ private ModuleInternalTransactionalInfo findModuleInternalTransactionalInfo(
+ ModuleIdentifier moduleIdentifier,
+ JmxAttribute jmxAttributeForReporting) {
+ ModuleInternalTransactionalInfo moduleInternalTransactionalInfo = commitMap
+ .get(moduleIdentifier);
+ JmxAttributeValidationException.checkNotNull(
+ moduleInternalTransactionalInfo, "Module " + moduleIdentifier
+ + "" + " not found in transaction " + transactionName,
+ jmxAttributeForReporting);
+ return moduleInternalTransactionalInfo;
+ }
+
+ @Override
+ public Module findModule(ModuleIdentifier moduleIdentifier,
+ JmxAttribute jmxAttributeForReporting) {
+ return findModuleInternalTransactionalInfo(moduleIdentifier,
+ jmxAttributeForReporting).getModule();
+ }
+
+ @Override
+ public ModuleFactory findModuleFactory(ModuleIdentifier moduleIdentifier,
+ JmxAttribute jmxAttributeForReporting) {
+ return findModuleInternalTransactionalInfo(moduleIdentifier,
+ jmxAttributeForReporting).getModuleFactory();
+ }
+
+ @Override
+ public Map<ModuleIdentifier, Module> getAllModules() {
+ Map<ModuleIdentifier, Module> result = new HashMap<>();
+ for (ModuleInternalTransactionalInfo entry : commitMap.values()) {
+ ModuleIdentifier name = entry.getName();
+ result.put(name, entry.getModule());
+ }
+ return result;
+ }
+
+ @Override
+ public void put(
+ ModuleInternalTransactionalInfo moduleInternalTransactionalInfo) {
+ commitMap.put(moduleInternalTransactionalInfo.getName(),
+ moduleInternalTransactionalInfo);
+ }
+
+ @Override
+ public ModuleInternalTransactionalInfo destroyModule(
+ ModuleIdentifier moduleIdentifier) {
+ ModuleInternalTransactionalInfo found = commitMap
+ .remove(moduleIdentifier);
+ if (found == null)
+ throw new IllegalStateException("Not found:" + moduleIdentifier);
+ if (found.hasOldModule()) {
+ unorderedDestroyedFromPreviousTransactions.add(found);
+ }
+ return found;
+ }
+
+ @Override
+ public void assertNotExists(ModuleIdentifier moduleIdentifier)
+ throws InstanceAlreadyExistsException {
+ if (commitMap.containsKey(moduleIdentifier)) {
+ throw new InstanceAlreadyExistsException(
+ "There is an instance registered with name "
+ + moduleIdentifier);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.dependencyresolver;
+
+import java.util.Map;
+
+import javax.management.InstanceAlreadyExistsException;
+
+import org.opendaylight.controller.config.api.JmxAttribute;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.manager.impl.CommitInfo;
+import org.opendaylight.controller.config.manager.impl.ModuleInternalTransactionalInfo;
+import org.opendaylight.controller.config.spi.Module;
+import org.opendaylight.controller.config.spi.ModuleFactory;
+
+interface TransactionHolder {
+ CommitInfo toCommitInfo();
+
+ Module findModule(ModuleIdentifier moduleIdentifier,
+ JmxAttribute jmxAttributeForReporting);
+
+ ModuleFactory findModuleFactory(ModuleIdentifier moduleIdentifier,
+ JmxAttribute jmxAttributeForReporting);
+
+ Map<ModuleIdentifier, Module> getAllModules();
+
+ void put(ModuleInternalTransactionalInfo moduleInternalTransactionalInfo);
+
+ ModuleInternalTransactionalInfo destroyModule(
+ ModuleIdentifier moduleIdentifier);
+
+ void assertNotExists(ModuleIdentifier moduleIdentifier)
+ throws InstanceAlreadyExistsException;
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.dynamicmbean;
+
+import static java.lang.String.format;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.management.Attribute;
+import javax.management.AttributeList;
+import javax.management.AttributeNotFoundException;
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.InstanceNotFoundException;
+import javax.management.IntrospectionException;
+import javax.management.ListenerNotFoundException;
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanConstructorInfo;
+import javax.management.MBeanException;
+import javax.management.MBeanInfo;
+import javax.management.MBeanNotificationInfo;
+import javax.management.MBeanOperationInfo;
+import javax.management.MBeanRegistrationException;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerDelegate;
+import javax.management.MBeanServerNotification;
+import javax.management.NotCompliantMBeanException;
+import javax.management.Notification;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.management.ReflectionException;
+
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.annotations.Description;
+import org.opendaylight.controller.config.api.annotations.RequireInterface;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.manager.impl.util.InterfacesHelper;
+import org.opendaylight.controller.config.spi.Module;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Contains common code for readable/rw dynamic mbean wrappers. Routes all
+ * requests (getAttribute, setAttribute, invoke) into the actual instance, but
+ * provides additional functionality - namely it disallows setting attribute on
+ * a read only wrapper.
+ *
+ */
+abstract class AbstractDynamicWrapper implements DynamicMBeanModuleWrapper {
+ private static final Logger logger = LoggerFactory
+ .getLogger(AbstractDynamicWrapper.class);
+
+ protected final boolean writable;
+ protected final Module module;
+
+ private final MBeanInfo mbeanInfo;
+ protected final ObjectName objectNameInternal;
+ protected final Map<String, AttributeHolder> attributeHolderMap;
+ protected final ModuleIdentifier moduleIdentifier;
+ protected final MBeanServer internalServer;
+
+ public AbstractDynamicWrapper(Module module, boolean writable,
+ ModuleIdentifier moduleIdentifier,
+ ObjectName thisWrapperObjectName, MBeanOperationInfo[] dOperations,
+ MBeanServer internalServer, MBeanServer configMBeanServer) {
+
+ this.writable = writable;
+ this.module = module;
+ this.moduleIdentifier = moduleIdentifier;
+ this.internalServer = internalServer;
+ this.objectNameInternal = thisWrapperObjectName;
+ // register the actual instance into an mbean server.
+ registerActualModule(module, thisWrapperObjectName, objectNameInternal,
+ internalServer, configMBeanServer);
+ Set<Class<?>> jmxInterfaces = InterfacesHelper.getMXInterfaces(module
+ .getClass());
+ this.attributeHolderMap = buildMBeanInfo(module, writable,
+ moduleIdentifier, jmxInterfaces, internalServer,
+ objectNameInternal);
+ this.mbeanInfo = generateMBeanInfo(module.getClass().getName(), module,
+ attributeHolderMap, dOperations, jmxInterfaces);
+ }
+
+ /**
+ * Register module into an internal mbean server, attach listener to the
+ * platform mbean server. Wait until this wrapper gets unregistered, in that
+ * case unregister the module and remove listener.
+ */
+ private final NotificationListener registerActualModule(Module module,
+ final ObjectName thisWrapperObjectName,
+ final ObjectName objectNameInternal,
+ final MBeanServer internalServer,
+ final MBeanServer configMBeanServer) {
+
+ try {
+ internalServer.registerMBean(module, objectNameInternal);
+ } catch (InstanceAlreadyExistsException | MBeanRegistrationException
+ | NotCompliantMBeanException | IllegalStateException e) {
+ throw new IllegalStateException(
+ "Error occured during mbean registration ", e);
+ }
+
+ NotificationListener listener = new NotificationListener() {
+ @Override
+ public void handleNotification(Notification n, Object handback) {
+ if (n instanceof MBeanServerNotification
+ && n.getType()
+ .equals(MBeanServerNotification.UNREGISTRATION_NOTIFICATION)) {
+ if (((MBeanServerNotification) n).getMBeanName().equals(
+ thisWrapperObjectName)) {
+ try {
+ internalServer.unregisterMBean(objectNameInternal);
+ configMBeanServer.removeNotificationListener(
+ MBeanServerDelegate.DELEGATE_NAME, this);
+ } catch (MBeanRegistrationException
+ | ListenerNotFoundException
+ | InstanceNotFoundException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+ }
+ };
+ try {
+ configMBeanServer.addNotificationListener(
+ MBeanServerDelegate.DELEGATE_NAME, listener, null, null);
+ } catch (InstanceNotFoundException e) {
+ throw new RuntimeException("Could not add notification listener", e);
+ }
+ return listener;
+ }
+
+ private static MBeanInfo generateMBeanInfo(String className, Module module,
+ Map<String, AttributeHolder> attributeHolderMap,
+ MBeanOperationInfo[] dOperations, Set<Class<?>> jmxInterfaces) {
+
+ String dDescription = findDescription(module.getClass(), jmxInterfaces);
+ MBeanConstructorInfo[] dConstructors = new MBeanConstructorInfo[0];
+ List<MBeanAttributeInfo> attributes = new ArrayList<>(
+ attributeHolderMap.size());
+ for (AttributeHolder attributeHolder : attributeHolderMap.values()) {
+ attributes.add(attributeHolder.toMBeanAttributeInfo());
+ }
+ return new MBeanInfo(className, dDescription,
+ attributes.toArray(new MBeanAttributeInfo[0]), dConstructors,
+ dOperations, new MBeanNotificationInfo[0]);
+ }
+
+ static String findDescription(Class<?> clazz, Set<Class<?>> jmxInterfaces) {
+ List<Description> descriptions = AnnotationsHelper
+ .findClassAnnotationInSuperClassesAndIfcs(clazz, Description.class, jmxInterfaces);
+ return AnnotationsHelper.aggregateDescriptions(descriptions);
+ }
+
+ protected static MBeanOperationInfo[] getEmptyOperations() {
+ return new MBeanOperationInfo[0];
+ }
+
+ // inspect all exported interfaces ending with MXBean, extract getters &
+ // setters into attribute holder
+ private static Map<String, AttributeHolder> buildMBeanInfo(Module module,
+ boolean writable, ModuleIdentifier moduleIdentifier,
+ Set<Class<?>> jmxInterfaces, MBeanServer internalServer,
+ ObjectName internalObjectName) {
+
+ // internal variables for describing MBean elements
+ Set<Method> methods = new HashSet<>();
+
+ for (Class<?> exportedClass : jmxInterfaces) {
+ Method[] ifcMethods = exportedClass.getMethods();
+ methods.addAll(Arrays.asList(ifcMethods));
+ }
+ // TODO: fix reflection, not used
+ MBeanInfo internalInfo;
+ try {
+ internalInfo = internalServer.getMBeanInfo(internalObjectName);
+ } catch (InstanceNotFoundException | ReflectionException
+ | IntrospectionException e) {
+ throw new RuntimeException("MBean info not found", e);
+ }
+
+ Map<String, MBeanAttributeInfo> attributeMap = new HashMap<>();
+ for (MBeanAttributeInfo a : internalInfo.getAttributes()) {
+ attributeMap.put(a.getName(), a);
+ }
+ Map<String, AttributeHolder> attributeHolderMap = new HashMap<>();
+ for (Method method : methods) {
+
+ if (method.getParameterTypes().length == 1
+ && method.getName().startsWith("set")) {
+ Method setter;
+ String attribName = method.getName().substring(3);
+ try {
+ setter = module.getClass().getMethod(method.getName(),
+ method.getParameterTypes());
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException("No such method on "
+ + moduleIdentifier, e);
+ }
+ RequireInterface ifc = AttributeHolder
+ .findRequireInterfaceAnnotation(setter, jmxInterfaces);
+ String description = null;
+ if (ifc != null) {
+ description = AttributeHolder.findDescription(setter,
+ jmxInterfaces);
+ }
+ AttributeHolder attributeHolder = new AttributeHolder(
+ attribName, module, attributeMap.get(attribName)
+ .getType(), writable, ifc, description);
+ attributeHolderMap.put(attribName, attributeHolder);
+ }
+ }
+ return attributeHolderMap;
+ }
+
+ // DynamicMBean methods
+
+ @Override
+ public MBeanInfo getMBeanInfo() {
+ return mbeanInfo;
+ }
+
+ @Override
+ public Object getAttribute(String attributeName)
+ throws AttributeNotFoundException, MBeanException,
+ ReflectionException {
+ if (attributeName.equals("MBeanInfo")) {
+ return getMBeanInfo();
+ }
+
+ Object obj = null;
+ try {
+ obj = internalServer
+ .getAttribute(objectNameInternal, attributeName);
+ } catch (InstanceNotFoundException e) {
+ new MBeanException(e);
+ }
+ if (obj instanceof ObjectName) {
+ AttributeHolder attributeHolder = attributeHolderMap
+ .get(attributeName);
+ if (attributeHolder.getRequireInterfaceOrNull() != null) {
+ obj = fixObjectName((ObjectName) obj);
+ }
+ return obj;
+ }
+ return obj;
+
+ }
+
+ protected ObjectName fixObjectName(ObjectName on) {
+ if (!ObjectNameUtil.ON_DOMAIN.equals(on.getDomain()))
+ throw new IllegalArgumentException("Wrong domain, expected "
+ + ObjectNameUtil.ON_DOMAIN + " setter on " + on);
+ // if on contains transaction name, remove it
+ String transactionName = ObjectNameUtil.getTransactionName(on);
+ if (transactionName != null)
+ return ObjectNameUtil.withoutTransactionName(on);
+ else
+ return on;
+ }
+
+ @Override
+ public AttributeList getAttributes(String[] attributes) {
+ AttributeList result = new AttributeList();
+ for (String attributeName : attributes) {
+ try {
+ Object value = getAttribute(attributeName);
+ result.add(new Attribute(attributeName, value));
+
+ } catch (Exception e) {
+ logger.debug("Getting attribute {} failed", attributeName, e);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public Object invoke(String actionName, Object[] params, String[] signature)
+ throws MBeanException, ReflectionException {
+ if ("getAttribute".equals(actionName) && params.length == 1
+ && signature[0].equals(String.class.getName())) {
+ try {
+ return getAttribute((String) params[0]);
+ } catch (AttributeNotFoundException e) {
+ throw new MBeanException(e, "Attribute not found on "
+ + moduleIdentifier);
+ }
+ } else if ("getAttributes".equals(actionName) && params.length == 1
+ && signature[0].equals(String[].class.getName())) {
+ return getAttributes((String[]) params[0]);
+ } else if ("setAttributes".equals(actionName) && params.length == 1
+ && signature[0].equals(AttributeList.class.getName())) {
+ return setAttributes((AttributeList) params[0]);
+ } else {
+ logger.debug("Operation not found {} ", actionName);
+ throw new UnsupportedOperationException(
+ format("Operation not found on %s. Method invoke is only supported for getInstance and getAttribute(s) "
+ + "method, got actionName %s, params %s, signature %s ",
+ moduleIdentifier, actionName, params, signature));
+ }
+ }
+
+ @Override
+ public final int hashCode() {
+ return module.hashCode();
+ }
+
+ @Override
+ public final boolean equals(Object other) {
+ return module.equals(other);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.dynamicmbean;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import org.opendaylight.controller.config.api.annotations.Description;
+
+class AnnotationsHelper {
+
+ /**
+ * Look for annotation specified by annotationType on method. First observe
+ * method's class, then its super classes, then all provided interfaces.
+ * Used for finding @Description and @RequireInterface
+ *
+ * @param <T>
+ * generic type of annotation
+ * @return list of found annotations
+ */
+ static <T extends Annotation> List<T> findMethodAnnotationInSuperClassesAndIfcs(
+ final Method setter, Class<T> annotationType,
+ Set<Class<?>> inspectedInterfaces) {
+ List<T> result = new ArrayList<T>();
+ Class<?> inspectedClass = setter.getDeclaringClass();
+ do {
+ try {
+ Method foundSetter = inspectedClass.getMethod(setter.getName(),
+ setter.getParameterTypes());
+ T annotation = foundSetter.getAnnotation(annotationType);
+ if (annotation != null) {
+ result.add(annotation);
+ }
+ // we need to go deeper
+ inspectedClass = inspectedClass.getSuperclass();
+ } catch (NoSuchMethodException e) {
+ inspectedClass = Object.class; // no need to go further
+ }
+ } while (inspectedClass.equals(Object.class) == false);
+ // inspect interfaces
+ for (Class<?> ifc : inspectedInterfaces) {
+ if (ifc.isInterface() == false) {
+ throw new IllegalArgumentException(ifc + " is not an interface");
+ }
+ try {
+ Method foundSetter = ifc.getMethod(setter.getName(),
+ setter.getParameterTypes());
+ T annotation = foundSetter.getAnnotation(annotationType);
+ if (annotation != null) {
+ result.add(annotation);
+ }
+ } catch (NoSuchMethodException e) {
+
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Look for annotation specified by annotationType on type. First observe
+ * class clazz, then its super classes, then all exported interfaces with
+ * their super types. Used for finding @Description of modules.
+ *
+ * @return list of found annotations
+ */
+ static <T extends Annotation> List<T> findClassAnnotationInSuperClassesAndIfcs(
+ Class<?> clazz, Class<T> annotationType, Set<Class<?>> interfaces) {
+ List<T> result = new ArrayList<T>();
+ Class<?> declaringClass = clazz;
+ do {
+ T annotation = declaringClass.getAnnotation(annotationType);
+ if (annotation != null) {
+ result.add(annotation);
+ }
+ declaringClass = declaringClass.getSuperclass();
+ } while (declaringClass.equals(Object.class) == false);
+ // inspect interfaces
+ for (Class<?> ifc : interfaces) {
+ if (ifc.isInterface() == false) {
+ throw new IllegalArgumentException(ifc + " is not an interface");
+ }
+ T annotation = ifc.getAnnotation(annotationType);
+ if (annotation != null) {
+ result.add(annotation);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @return empty string if no annotation is found, or list of descriptions
+ * separated by newline
+ */
+ static String aggregateDescriptions(List<Description> descriptions) {
+ StringBuilder builder = new StringBuilder();
+ for (Description d : descriptions) {
+ if (builder.length() != 0) {
+ builder.append("\n");
+ }
+ builder.append(d.value());
+
+ }
+ return builder.toString();
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.dynamicmbean;
+
+import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+import javax.management.MBeanAttributeInfo;
+import javax.management.ObjectName;
+
+import org.opendaylight.controller.config.api.annotations.Description;
+import org.opendaylight.controller.config.api.annotations.RequireInterface;
+
+@Immutable
+class AttributeHolder {
+
+ private final String name;
+ private final String description;
+ private final Object object;
+ private final boolean writable;
+
+ @Nullable
+ private final RequireInterface requireInterfaceAnnotation;
+ private final String attributeType;
+
+ public AttributeHolder(String name, Object object, String returnType,
+ boolean writable,
+ @Nullable RequireInterface requireInterfaceAnnotation,
+ String description) {
+ if (name == null) {
+ throw new NullPointerException();
+ }
+ this.name = name;
+ if (object == null) {
+ throw new NullPointerException();
+ }
+ this.object = object;
+ this.writable = writable;
+ this.requireInterfaceAnnotation = requireInterfaceAnnotation;
+ this.attributeType = returnType;
+ this.description = description;
+ }
+
+ public MBeanAttributeInfo toMBeanAttributeInfo() {
+ MBeanAttributeInfo info = new MBeanAttributeInfo(name, attributeType,
+ description, true, true, false);
+ return info;
+ }
+
+ /**
+ * @return annotation if setter sets ObjectName or ObjectName[], and is
+ * annotated. Return null otherwise.
+ */
+ RequireInterface getRequireInterfaceOrNull() {
+ return requireInterfaceAnnotation;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Object getObject() {
+ return object;
+ }
+
+ public String getAttributeType() {
+ return attributeType;
+ }
+
+ public boolean isWritable() {
+ return writable;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * Find @Description annotations in method class and all its exported
+ * interfaces.
+ *
+ * @param setter
+ * @param jmxInterfaces
+ * @return empty string if no annotation is found, or list of descriptions
+ * separated by newline
+ */
+ static String findDescription(Method setter, Set<Class<?>> jmxInterfaces) {
+ List<Description> descriptions = AnnotationsHelper
+ .findMethodAnnotationInSuperClassesAndIfcs(setter, Description.class, jmxInterfaces);
+ return AnnotationsHelper.aggregateDescriptions(descriptions);
+ }
+
+ /**
+ * Find @RequireInterface annotation by searching method class and all
+ * exported interfaces.
+ *
+ * @param setter
+ * @param inspectedInterfaces
+ * @throws IllegalStateException
+ * if more than one value is specified by found annotations
+ * @throws IllegalArgumentException
+ * if set of exported interfaces contains non interface type
+ * @return null if no annotation is found, otherwise return the annotation
+ */
+ static RequireInterface findRequireInterfaceAnnotation(final Method setter,
+ Set<Class<?>> inspectedInterfaces) {
+
+ // only allow setX(ObjectName y) or setX(ObjectName[] y) to continue
+ if (setter.getParameterTypes().length != 1
+ || (setter.getParameterTypes()[0].equals(ObjectName.class) == false && setter
+ .getParameterTypes()[0].equals(ObjectName[].class) == false)) {
+ return null;
+ }
+
+ List<RequireInterface> foundRequireInterfaces = AnnotationsHelper
+ .findMethodAnnotationInSuperClassesAndIfcs(setter, RequireInterface.class, inspectedInterfaces);
+ // make sure the list if not empty contains always annotation with same
+ // value
+ Set<Class<?>> foundValues = new HashSet<Class<?>>();
+ for (RequireInterface ri : foundRequireInterfaces) {
+ foundValues.add(ri.value());
+ }
+ if (foundValues.size() == 0) {
+ return null;
+ } else if (foundValues.size() > 1) {
+ throw new IllegalStateException("Error finding @RequireInterface. "
+ + "More than one value specified as required interface "
+ + foundValues + " of " + setter + " of "
+ + setter.getDeclaringClass());
+ } else {
+ return foundRequireInterfaces.get(0);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.dynamicmbean;
+
+import javax.management.DynamicMBean;
+
+/**
+ * Each {@link org.opendaylight.controller.config.spi.Module} in JMX registry
+ * will be wrapped in this class.
+ */
+public interface DynamicMBeanModuleWrapper extends DynamicMBean {
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.dynamicmbean;
+
+import javax.management.Attribute;
+import javax.management.AttributeList;
+import javax.management.AttributeNotFoundException;
+import javax.management.InvalidAttributeValueException;
+import javax.management.MBeanException;
+import javax.management.MBeanServer;
+import javax.management.ReflectionException;
+
+import org.opendaylight.controller.config.api.DynamicMBeanWithInstance;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.spi.Module;
+
+/**
+ * Wraps {@link org.opendaylight.controller.config.spi.Module} in a
+ * {@link DynamicMBeanWithInstance}. Setting attributes is disabled.
+ */
+public class DynamicReadableWrapper extends AbstractDynamicWrapper implements
+ DynamicMBeanWithInstance {
+ private final AutoCloseable instance;
+
+ /**
+ * @param module
+ * @param instance
+ * for recreating Module.
+ *
+ */
+ public DynamicReadableWrapper(Module module, AutoCloseable instance,
+ ModuleIdentifier moduleIdentifier, MBeanServer internalServer,
+ MBeanServer configMBeanServer) {
+ super(module, false, moduleIdentifier, ObjectNameUtil
+ .createReadOnlyModuleON(moduleIdentifier),
+ getEmptyOperations(), internalServer, configMBeanServer);
+ this.instance = instance;
+ }
+
+ @Override
+ public Module getModule() {
+ return module;
+ }
+
+ @Override
+ public AutoCloseable getInstance() {
+ return instance;
+ }
+
+ @Override
+ public Object invoke(String actionName, Object[] params, String[] signature)
+ throws MBeanException, ReflectionException {
+ if ("getInstance".equals(actionName)
+ && (params == null || params.length == 0)
+ && (signature == null || signature.length == 0)) {
+ return getInstance();
+ }
+ return super.invoke(actionName, params, signature);
+ }
+
+ @Override
+ public Object getAttribute(String attributeName)
+ throws AttributeNotFoundException, MBeanException,
+ ReflectionException {
+ if (attributeName.equals("getInstance")) {
+ return getInstance();
+ }
+ return super.getAttribute(attributeName);
+ }
+
+ @Override
+ public void setAttribute(Attribute attribute)
+ throws AttributeNotFoundException, InvalidAttributeValueException,
+ MBeanException, ReflectionException {
+ throw new UnsupportedOperationException(
+ "setAttributes is not supported on " + moduleIdentifier);
+ }
+
+ @Override
+ public AttributeList setAttributes(AttributeList attributes) {
+ throw new UnsupportedOperationException(
+ "setAttributes is not supported on " + moduleIdentifier);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.dynamicmbean;
+
+import java.lang.reflect.Method;
+
+import javax.annotation.concurrent.ThreadSafe;
+import javax.management.Attribute;
+import javax.management.AttributeList;
+import javax.management.AttributeNotFoundException;
+import javax.management.DynamicMBean;
+import javax.management.InstanceNotFoundException;
+import javax.management.InvalidAttributeValueException;
+import javax.management.MBeanException;
+import javax.management.MBeanOperationInfo;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import javax.management.ReflectionException;
+
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.ValidationException;
+import org.opendaylight.controller.config.api.annotations.RequireInterface;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.manager.impl.TransactionIdentifier;
+import org.opendaylight.controller.config.spi.Module;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Wraps {@link org.opendaylight.controller.config.spi.Module} instance in a
+ * {@link DynamicMBean} interface. Inspects dependency attributes, identified by
+ * ObjectName getter/setter and {@link RequireInterface} annotation. Used to
+ * simplify client calls - to set a dependency, only instance name is needed.
+ * This class creates new writable String attribute for each dependency with
+ * 'Name' suffix backed by the actual ObjectName attribute.
+ * <p>
+ * Thread safety - setting attributes is synchronized on 'this'. Synchronization
+ * of {@link org.opendaylight.controller.config.spi.Module#validate()} and
+ * {@link org.opendaylight.controller.config.spi.Module#getInstance()} is also
+ * guaranteed by
+ * {@link org.opendaylight.controller.config.manager.impl.ConfigTransactionControllerInternal}
+ * so the actual {@link org.opendaylight.controller.config.spi.Module} needs not
+ * to be thread safe.
+ * </p>
+ */
+@ThreadSafe
+public class DynamicWritableWrapper extends AbstractDynamicWrapper {
+ private static final Logger logger = LoggerFactory
+ .getLogger(DynamicWritableWrapper.class);
+
+ private final ReadOnlyAtomicBoolean configBeanModificationDisabled;
+
+ public DynamicWritableWrapper(Module module,
+ ModuleIdentifier moduleIdentifier,
+ TransactionIdentifier transactionIdentifier,
+ ReadOnlyAtomicBoolean configBeanModificationDisabled,
+ MBeanServer internalServer, MBeanServer configMBeanServer) {
+ super(module, true, moduleIdentifier, ObjectNameUtil
+ .createTransactionModuleON(transactionIdentifier.getName(), moduleIdentifier), getOperations(moduleIdentifier),
+ internalServer, configMBeanServer);
+ this.configBeanModificationDisabled = configBeanModificationDisabled;
+ }
+
+ private static MBeanOperationInfo[] getOperations(
+ ModuleIdentifier moduleIdentifier) {
+ Method validationMethod;
+ try {
+ validationMethod = DynamicWritableWrapper.class.getMethod(
+ "validate", new Class<?>[0]);
+ } catch (NoSuchMethodException e) {
+ throw new IllegalStateException("No such method exception on "
+ + moduleIdentifier, e);
+ }
+ return new MBeanOperationInfo[] { new MBeanOperationInfo("Validation",
+ validationMethod) };
+ }
+
+ @Override
+ public synchronized void setAttribute(Attribute attribute)
+ throws AttributeNotFoundException, InvalidAttributeValueException,
+ MBeanException, ReflectionException {
+ if (configBeanModificationDisabled.get() == true)
+ throw new IllegalStateException("Operation is not allowed now");
+
+ if (attribute.getName().equals("Attribute")) {
+ setAttribute((Attribute) attribute.getValue());
+ return;
+ }
+
+ try {
+ if (attribute.getValue() instanceof ObjectName) {
+ AttributeHolder attributeHolder = attributeHolderMap
+ .get(attribute.getName());
+ if (attributeHolder.getRequireInterfaceOrNull() != null) {
+ attribute = new Attribute(attribute.getName(),
+ fixObjectName((ObjectName) attribute.getValue()));
+ } else {
+ attribute = new Attribute(attribute.getName(),
+ attribute.getValue());
+ }
+ }
+ internalServer.setAttribute(objectNameInternal, attribute);
+ } catch (InstanceNotFoundException e) {
+ throw new MBeanException(e);
+ }
+
+ }
+
+ @Override
+ public AttributeList setAttributes(AttributeList attributes) {
+ AttributeList result = new AttributeList();
+ for (Object attributeObject : attributes) {
+ Attribute attribute = (Attribute) attributeObject;
+ try {
+ setAttribute(attribute);
+ result.add(attribute);
+ } catch (Exception e) {
+ logger.warn("Setting attribute {} failed on {}",
+ attribute.getName(), moduleIdentifier, e);
+ throw new IllegalArgumentException(
+ "Setting attribute failed - " + attribute.getName()
+ + " on " + moduleIdentifier, e);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public Object invoke(String actionName, Object[] params, String[] signature)
+ throws MBeanException, ReflectionException {
+ if ("validate".equals(actionName)
+ && (params == null || params.length == 0)
+ && (signature == null || signature.length == 0)) {
+ try {
+ validate();
+ } catch (Exception e) {
+ throw ValidationException.createForSingleException(
+ moduleIdentifier, e);
+ }
+ return Void.TYPE;
+ }
+ return super.invoke(actionName, params, signature);
+ }
+
+ public void validate() {
+ module.validate();
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.dynamicmbean;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public interface ReadOnlyAtomicBoolean {
+ boolean get();
+
+ public static class ReadOnlyAtomicBooleanImpl implements
+ ReadOnlyAtomicBoolean {
+ private final AtomicBoolean atomicBoolean;
+
+ public ReadOnlyAtomicBooleanImpl(AtomicBoolean atomicBoolean) {
+ super();
+ this.atomicBoolean = atomicBoolean;
+ }
+
+ @Override
+ public boolean get() {
+ return atomicBoolean.get();
+ }
+
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.factoriesresolver;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.opendaylight.controller.config.spi.ModuleFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Hold sorted ConfigMBeanFactories by their module names. Check that module
+ * names are globally unique.
+ */
+public class HierarchicalConfigMBeanFactoriesHolder {
+ private static final Logger logger = LoggerFactory
+ .getLogger(HierarchicalConfigMBeanFactoriesHolder.class);
+
+ private final Map<String, ModuleFactory> moduleNamesToConfigBeanFactories;
+ private final Set<String> moduleNames;
+
+ /**
+ * Create instance.
+ *
+ * @throws IllegalArgumentException
+ * if unique constraint on module names is violated
+ */
+ public HierarchicalConfigMBeanFactoriesHolder(
+ List<? extends ModuleFactory> list) {
+ Map<String, ModuleFactory> moduleNamesToConfigBeanFactories = new HashMap<>();
+ StringBuffer errors = new StringBuffer();
+ for (ModuleFactory factory : list) {
+ String moduleName = factory.getImplementationName();
+ if (moduleName == null || moduleName.isEmpty()) {
+ throw new IllegalStateException(
+ "Invalid implementation name for " + factory);
+ }
+ logger.debug("Reading factory {} {}", moduleName, factory);
+ String error = null;
+ ModuleFactory conflicting = moduleNamesToConfigBeanFactories
+ .get(moduleName);
+ if (conflicting != null) {
+ error = String
+ .format("Module name is not unique. Found two conflicting factories with same name '%s': " +
+ "\n\t%s\n\t%s\n", moduleName, conflicting, factory);
+
+ }
+
+ if (error == null) {
+ moduleNamesToConfigBeanFactories.put(moduleName, factory);
+ } else {
+ errors.append(error);
+ }
+
+ }
+ if (errors.length() > 0) {
+ throw new IllegalArgumentException(errors.toString());
+ }
+ this.moduleNamesToConfigBeanFactories = Collections
+ .unmodifiableMap(moduleNamesToConfigBeanFactories);
+ moduleNames = Collections.unmodifiableSet(new TreeSet<>(
+ moduleNamesToConfigBeanFactories.keySet()));
+ }
+
+ /**
+ * Get ModuleFactory by their name.
+ *
+ * @throws IllegalArgumentException
+ * if factory is not found
+ */
+ public ModuleFactory findByModuleName(String moduleName) {
+ ModuleFactory result = moduleNamesToConfigBeanFactories.get(moduleName);
+ if (result == null) {
+ throw new IllegalArgumentException(
+ "ModuleFactory not found with module name: " + moduleName);
+ }
+ return result;
+ }
+
+ public Set<String> getModuleNames() {
+ return moduleNames;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.factoriesresolver;
+
+import java.util.List;
+
+import org.opendaylight.controller.config.spi.ModuleFactory;
+
+/**
+ * {@link org.opendaylight.controller.config.manager.impl.ConfigTransactionControllerImpl}
+ * receives list of factories using this interface. For testing, this could be
+ * implemented as hard coded list of objects, for OSGi this would look for all
+ * services in OSGi Service Registry are registered under
+ * {@link org.opendaylight.controller.config.spi.ModuleFactory} name.
+ */
+public interface ModuleFactoriesResolver {
+
+ List<? extends ModuleFactory> getAllFactories();
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.jmx;
+
+import java.util.Set;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import javax.management.QueryExp;
+
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+
+public class BaseJMXRegistrator implements AutoCloseable {
+
+ private final InternalJMXRegistrator internalJMXRegistrator;
+
+ public BaseJMXRegistrator(MBeanServer configMBeanServer) {
+ internalJMXRegistrator = new InternalJMXRegistrator(configMBeanServer);
+ }
+
+ public BaseJMXRegistrator(InternalJMXRegistrator internalJMXRegistrator) {
+ this.internalJMXRegistrator = internalJMXRegistrator;
+ }
+
+ public TransactionJMXRegistrator createTransactionJMXRegistrator(
+ String transactionName) {
+ return new TransactionJMXRegistrator(
+ internalJMXRegistrator.createChild(), transactionName);
+ }
+
+ public ModuleJMXRegistrator createModuleJMXRegistrator() {
+ return new ModuleJMXRegistrator(internalJMXRegistrator.createChild());
+ }
+
+ public RootRuntimeBeanRegistratorImpl createRuntimeBeanRegistrator(
+ ModuleIdentifier moduleIdentifier) {
+ return new RootRuntimeBeanRegistratorImpl(internalJMXRegistrator,
+ moduleIdentifier);
+ }
+
+ public Set<ObjectName> queryNames(ObjectName name, QueryExp query) {
+ return internalJMXRegistrator.queryNames(name, query);
+ }
+
+ public Set<ObjectName> getRegisteredObjectNames() {
+ return internalJMXRegistrator.getRegisteredObjectNames();
+ }
+
+ @Override
+ public void close() {
+ internalJMXRegistrator.close();
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.jmx;
+
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.MBeanServer;
+
+import org.opendaylight.controller.config.api.jmx.ConfigRegistryMXBean;
+import org.opendaylight.controller.config.manager.impl.ConfigRegistryImplMXBean;
+
+/**
+ * This registrator is used only to register Config Registry to JMX.
+ *
+ */
+public class ConfigRegistryJMXRegistrator implements AutoCloseable {
+ private final InternalJMXRegistrator internalJMXRegistrator;
+
+ public ConfigRegistryJMXRegistrator(MBeanServer configMBeanServer) {
+ internalJMXRegistrator = new InternalJMXRegistrator(configMBeanServer);
+ }
+
+ public AutoCloseable registerToJMX(ConfigRegistryImplMXBean configRegistry)
+ throws InstanceAlreadyExistsException {
+ return internalJMXRegistrator.registerMBean(configRegistry,
+ ConfigRegistryMXBean.OBJECT_NAME);
+ }
+
+ @Override
+ public void close() {
+ internalJMXRegistrator.close();
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.jmx;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.ObjectName;
+
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.api.runtime.HierarchicalRuntimeBeanRegistration;
+import org.opendaylight.controller.config.api.runtime.RuntimeBean;
+
+public class HierarchicalRuntimeBeanRegistrationImpl implements
+ HierarchicalRuntimeBeanRegistration {
+ private final ModuleIdentifier moduleIdentifier;
+ private final InternalJMXRegistrator internalJMXRegistrator;
+ private final Map<String, String> properties;
+
+ public HierarchicalRuntimeBeanRegistrationImpl(
+ ModuleIdentifier moduleIdentifier,
+ InternalJMXRegistrator internalJMXRegistrator,
+ Map<String, String> properties) {
+ this.moduleIdentifier = moduleIdentifier;
+ this.internalJMXRegistrator = internalJMXRegistrator;
+ this.properties = properties;
+ }
+
+ @Override
+ public ObjectName getObjectName() {
+ return ObjectNameUtil.createRuntimeBeanName(
+ moduleIdentifier.getFactoryName(),
+ moduleIdentifier.getInstanceName(), properties);
+ }
+
+ @Override
+ public HierarchicalRuntimeBeanRegistrationImpl register(String key,
+ String value, RuntimeBean mxBean) {
+ Map<String, String> currentProperties = new HashMap<>(properties);
+ currentProperties.put(key, value);
+ ObjectName on = ObjectNameUtil.createRuntimeBeanName(
+ moduleIdentifier.getFactoryName(),
+ moduleIdentifier.getInstanceName(), currentProperties);
+ InternalJMXRegistrator child = internalJMXRegistrator.createChild();
+ try {
+ child.registerMBean(mxBean, on);
+ } catch (InstanceAlreadyExistsException e) {
+ throw RootRuntimeBeanRegistratorImpl.sanitize(e, moduleIdentifier,
+ on);
+ }
+ return new HierarchicalRuntimeBeanRegistrationImpl(moduleIdentifier,
+ child, currentProperties);
+ }
+
+ @Override
+ public void close() {
+ internalJMXRegistrator.close();
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.jmx;
+
+import java.io.Closeable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.concurrent.GuardedBy;
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.JMX;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import javax.management.QueryExp;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class InternalJMXRegistrator implements Closeable {
+ private static final Logger logger = LoggerFactory
+ .getLogger(InternalJMXRegistrator.class);
+ private final MBeanServer configMBeanServer;
+
+ public InternalJMXRegistrator(MBeanServer configMBeanServer) {
+ this.configMBeanServer = configMBeanServer;
+ }
+
+ static class InternalJMXRegistration implements AutoCloseable {
+ private final InternalJMXRegistrator internalJMXRegistrator;
+ private final ObjectName on;
+
+ InternalJMXRegistration(InternalJMXRegistrator internalJMXRegistrator,
+ ObjectName on) {
+ this.internalJMXRegistrator = internalJMXRegistrator;
+ this.on = on;
+ }
+
+ @Override
+ public void close() {
+ internalJMXRegistrator.unregisterMBean(on);
+ }
+ }
+
+ @GuardedBy("this")
+ private final Set<ObjectName> registeredObjectNames = new HashSet<>();
+ private final List<InternalJMXRegistrator> children = new ArrayList<>();
+
+ public synchronized InternalJMXRegistration registerMBean(Object object,
+ ObjectName on) throws InstanceAlreadyExistsException {
+ try {
+ configMBeanServer.registerMBean(object, on);
+ } catch (InstanceAlreadyExistsException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ registeredObjectNames.add(on);
+ return new InternalJMXRegistration(this, on);
+ }
+
+ private synchronized void unregisterMBean(ObjectName on) {
+ // first check that on was registered using this instance
+ boolean removed = registeredObjectNames.remove(on);
+ if (!removed)
+ throw new IllegalStateException(
+ "Cannot unregister - ObjectName not found in 'registeredObjectNames': "
+ + on);
+ try {
+ configMBeanServer.unregisterMBean(on);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public InternalJMXRegistrator createChild() {
+ InternalJMXRegistrator child = new InternalJMXRegistrator(
+ configMBeanServer);
+ children.add(child);
+ return child;
+ }
+
+ /**
+ * Allow close to be called multiple times.
+ */
+ @Override
+ public synchronized void close() {
+ // close children
+ for (InternalJMXRegistrator child : children) {
+ child.close();
+ }
+ // close registered ONs
+ for (ObjectName on : registeredObjectNames) {
+ try {
+ configMBeanServer.unregisterMBean(on);
+ } catch (Exception e) {
+ logger.warn("Ignoring error while unregistering {}", on, e);
+ }
+ }
+ registeredObjectNames.clear();
+ }
+
+ public <T> T newMBeanProxy(ObjectName objectName, Class<T> interfaceClass) {
+ return JMX.newMBeanProxy(configMBeanServer, objectName, interfaceClass);
+ }
+
+ public <T> T newMBeanProxy(ObjectName objectName, Class<T> interfaceClass,
+ boolean notificationBroadcaster) {
+ return JMX.newMBeanProxy(configMBeanServer, objectName, interfaceClass,
+ notificationBroadcaster);
+ }
+
+ public <T> T newMXBeanProxy(ObjectName objectName, Class<T> interfaceClass) {
+ return JMX
+ .newMXBeanProxy(configMBeanServer, objectName, interfaceClass);
+ }
+
+ public <T> T newMXBeanProxy(ObjectName objectName, Class<T> interfaceClass,
+ boolean notificationBroadcaster) {
+ return JMX.newMXBeanProxy(configMBeanServer, objectName,
+ interfaceClass, notificationBroadcaster);
+ }
+
+ public Set<ObjectName> getRegisteredObjectNames() {
+ return Collections.unmodifiableSet(registeredObjectNames);
+ }
+
+ public Set<ObjectName> queryNames(ObjectName name, QueryExp query) {
+ Set<ObjectName> result = configMBeanServer.queryNames(name, query);
+ // keep only those that were registered using this instance
+ return getSameNames(result);
+ }
+
+ private Set<ObjectName> getSameNames(Set<ObjectName> superSet) {
+ Set<ObjectName> result = new HashSet<>(superSet);
+ result.retainAll(registeredObjectNames);
+ for (InternalJMXRegistrator child : children) {
+ result.addAll(child.getSameNames(superSet));
+ }
+ return result;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.jmx;
+
+import java.io.Closeable;
+
+import javax.annotation.concurrent.ThreadSafe;
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.ObjectName;
+
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.manager.impl.jmx.InternalJMXRegistrator.InternalJMXRegistration;
+
+/**
+ * This subclass is used for registering readable module into JMX, it is also
+ * used as underlying provider in {@link RuntimeBeanRegistratorImpl}. Closing
+ * the instance thus unregisters all JMX beans related to the module excluding
+ * currently open transactions.
+ */
+@ThreadSafe
+public class ModuleJMXRegistrator implements Closeable {
+ private final InternalJMXRegistrator childJMXRegistrator;
+
+ public ModuleJMXRegistrator(InternalJMXRegistrator internalJMXRegistrator) {
+ this.childJMXRegistrator = internalJMXRegistrator.createChild();
+ }
+
+ static class ModuleJMXRegistration implements AutoCloseable {
+ private final InternalJMXRegistration internalJMXRegistration;
+
+ ModuleJMXRegistration(InternalJMXRegistration registration) {
+ this.internalJMXRegistration = registration;
+ }
+
+ @Override
+ public void close() {
+ internalJMXRegistration.close();
+ }
+ }
+
+ public ModuleJMXRegistration registerMBean(Object object, ObjectName on)
+ throws InstanceAlreadyExistsException {
+ ObjectNameUtil.checkType(on, ObjectNameUtil.TYPE_MODULE);
+ if (ObjectNameUtil.getTransactionName(on) != null)
+ throw new IllegalArgumentException(
+ "Transaction name not expected in " + on);
+ return new ModuleJMXRegistration(childJMXRegistrator.registerMBean(
+ object, on));
+ }
+
+ @Override
+ public void close() {
+ childJMXRegistrator.close();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.jmx;
+
+import java.util.Collections;
+
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.ObjectName;
+
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.api.runtime.RootRuntimeBeanRegistrator;
+import org.opendaylight.controller.config.api.runtime.RuntimeBean;
+
+public class RootRuntimeBeanRegistratorImpl implements
+ RootRuntimeBeanRegistrator {
+ private final InternalJMXRegistrator internalJMXRegistrator;
+ private final ModuleIdentifier moduleIdentifier;
+ private final ObjectName defaultRuntimeON;
+
+ public RootRuntimeBeanRegistratorImpl(
+ InternalJMXRegistrator internalJMXRegistrator,
+ ModuleIdentifier moduleIdentifier) {
+ this.internalJMXRegistrator = internalJMXRegistrator;
+ this.moduleIdentifier = moduleIdentifier;
+ defaultRuntimeON = ObjectNameUtil.createRuntimeBeanName(
+ moduleIdentifier.getFactoryName(),
+ moduleIdentifier.getInstanceName(),
+ Collections.<String, String> emptyMap());
+ }
+
+ @Override
+ public HierarchicalRuntimeBeanRegistrationImpl registerRoot(
+ RuntimeBean mxBean) {
+ try {
+ internalJMXRegistrator.registerMBean(mxBean, defaultRuntimeON);
+ } catch (InstanceAlreadyExistsException e) {
+ throw sanitize(e, moduleIdentifier, defaultRuntimeON);
+ }
+ return new HierarchicalRuntimeBeanRegistrationImpl(moduleIdentifier,
+ internalJMXRegistrator, Collections.<String, String> emptyMap());
+ }
+
+ @Override
+ public void close() {
+ internalJMXRegistrator.close();
+ }
+
+ static IllegalStateException sanitize(InstanceAlreadyExistsException e,
+ ModuleIdentifier moduleIdentifier, ObjectName on) {
+ throw new IllegalStateException("Could not register runtime bean in "
+ + moduleIdentifier + " under name " + on, e);
+
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.jmx;
+
+import java.io.Closeable;
+import java.util.Set;
+
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.ObjectName;
+import javax.management.QueryExp;
+
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.manager.impl.jmx.InternalJMXRegistrator.InternalJMXRegistration;
+
+/**
+ * Contains constraints on passed {@link ObjectName} parameters. Only allow (un)
+ * registration of ObjectNames that have expected transaction name.
+ */
+public class TransactionJMXRegistrator implements Closeable {
+ private final InternalJMXRegistrator childJMXRegistrator;
+ private final String transactionName;
+
+ TransactionJMXRegistrator(InternalJMXRegistrator internalJMXRegistrator,
+ String transactionName) {
+ this.childJMXRegistrator = internalJMXRegistrator.createChild();
+ this.transactionName = transactionName;
+ }
+
+ public static class TransactionJMXRegistration implements AutoCloseable {
+ private final InternalJMXRegistration registration;
+
+ TransactionJMXRegistration(InternalJMXRegistration registration) {
+ this.registration = registration;
+ }
+
+ @Override
+ public void close() {
+ registration.close();
+ }
+ }
+
+ public TransactionJMXRegistration registerMBean(Object object, ObjectName on)
+ throws InstanceAlreadyExistsException {
+ if (!transactionName.equals(ObjectNameUtil.getTransactionName(on)))
+ throw new IllegalArgumentException(
+ "Transaction name mismatch between expected "
+ + transactionName + " " + "and " + on);
+ ObjectNameUtil.checkType(on, ObjectNameUtil.TYPE_CONFIG_TRANSACTION);
+ return new TransactionJMXRegistration(
+ childJMXRegistrator.registerMBean(object, on));
+ }
+
+ public Set<ObjectName> queryNames(ObjectName name, QueryExp query) {
+ return childJMXRegistrator.queryNames(name, query);
+ }
+
+ public TransactionModuleJMXRegistrator createTransactionModuleJMXRegistrator() {
+ return new TransactionModuleJMXRegistrator(childJMXRegistrator,
+ transactionName);
+ }
+
+ @Override
+ public void close() {
+ childJMXRegistrator.close();
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.jmx;
+
+import java.io.Closeable;
+import java.util.Set;
+
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.ObjectName;
+import javax.management.QueryExp;
+
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.manager.impl.jmx.InternalJMXRegistrator.InternalJMXRegistration;
+
+public class TransactionModuleJMXRegistrator implements Closeable {
+ private final InternalJMXRegistrator childJMXRegistrator;
+ private final String transactionName;
+
+ public TransactionModuleJMXRegistrator(
+ InternalJMXRegistrator internalJMXRegistrator,
+ String transactionName) {
+ this.childJMXRegistrator = internalJMXRegistrator.createChild();
+ this.transactionName = transactionName;
+ }
+
+ public static class TransactionModuleJMXRegistration implements
+ AutoCloseable {
+ private final InternalJMXRegistration registration;
+
+ TransactionModuleJMXRegistration(InternalJMXRegistration registration) {
+ this.registration = registration;
+ }
+
+ @Override
+ public void close() {
+ registration.close();
+ }
+ }
+
+ public TransactionModuleJMXRegistration registerMBean(Object object,
+ ObjectName on) throws InstanceAlreadyExistsException {
+ if (!transactionName.equals(ObjectNameUtil.getTransactionName(on)))
+ throw new IllegalArgumentException(
+ "Transaction name mismatch between expected "
+ + transactionName + " " + "and " + on);
+ ObjectNameUtil.checkType(on, ObjectNameUtil.TYPE_MODULE);
+ return new TransactionModuleJMXRegistration(
+ childJMXRegistrator.registerMBean(object, on));
+ }
+
+ public Set<ObjectName> queryNames(ObjectName name, QueryExp query) {
+ return childJMXRegistrator.queryNames(name, query);
+ }
+
+ @Override
+ public void close() {
+ childJMXRegistrator.close();
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.osgi;
+
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Set;
+
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.annotations.ServiceInterfaceAnnotation;
+import org.opendaylight.controller.config.manager.impl.util.InterfacesHelper;
+import org.opendaylight.controller.config.spi.Module;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * Registers instantiated beans as OSGi services and unregisters these services
+ * if beans are destroyed.
+ */
+public class BeanToOsgiServiceManager {
+ // name of properties submitted to osgi
+ static final String INSTANCE_NAME_OSGI_PROP = "instanceName";
+ static final String IMPLEMENTATION_NAME_OSGI_PROP = "implementationName";
+
+ private final BundleContext bundleContext;
+
+ public BeanToOsgiServiceManager(BundleContext context) {
+ this.bundleContext = context;
+ }
+
+ /**
+ * To be called for every created, reconfigured and recreated config bean.
+ * It is expected that before using this method OSGi service registry will
+ * be cleaned from previous registrations.
+ */
+ public OsgiRegistration registerToOsgi(
+ Class<? extends Module> configBeanClass, AutoCloseable instance,
+ ModuleIdentifier moduleIdentifier) {
+ try {
+ final Set<Class<?>> configuresInterfaces = InterfacesHelper
+ .getOsgiRegistrationTypes(configBeanClass);
+ checkInstanceImplementing(instance, configuresInterfaces);
+
+ // bundleContext.registerService blows up with empty 'clazzes'
+ if (configuresInterfaces.isEmpty() == false) {
+ final Dictionary<String, ?> propertiesForOsgi = getPropertiesForOsgi(moduleIdentifier);
+ final ServiceRegistration<?> serviceRegistration = bundleContext
+ .registerService(classesToNames(configuresInterfaces), instance, propertiesForOsgi);
+ return new OsgiRegistration(serviceRegistration);
+ } else {
+ return new OsgiRegistration();
+ }
+ } catch (IllegalStateException e) {
+ throw new IllegalStateException(
+ "Error while registering instance into OSGi Service Registry: "
+ + moduleIdentifier, e);
+ }
+ }
+
+ private static String[] classesToNames(Set<Class<?>> cfgs) {
+ String[] result = new String[cfgs.size()];
+ int i = 0;
+ for (Class<?> cfg : cfgs) {
+ result[i] = cfg.getName();
+ i++;
+ }
+ return result;
+ }
+
+ private void checkInstanceImplementing(AutoCloseable instance,
+ Set<Class<?>> configures) {
+ Set<Class<?>> missing = new HashSet<>();
+ for (Class<?> requiredIfc : configures) {
+ if (requiredIfc.isInstance(instance) == false) {
+ missing.add(requiredIfc);
+ }
+ }
+ if (missing.isEmpty() == false) {
+ throw new IllegalStateException(
+ instance.getClass()
+ + " does not implement following interfaces as announced by "
+ + ServiceInterfaceAnnotation.class.getName()
+ + " annotation :" + missing);
+ }
+ }
+
+ private static Dictionary<String, ?> getPropertiesForOsgi(
+ ModuleIdentifier moduleIdentifier) {
+ Hashtable<String, String> table = new Hashtable<>();
+ table.put(IMPLEMENTATION_NAME_OSGI_PROP,
+ moduleIdentifier.getFactoryName());
+ table.put(INSTANCE_NAME_OSGI_PROP, moduleIdentifier.getInstanceName());
+ return table;
+ }
+
+ public static class OsgiRegistration implements AutoCloseable {
+ private final ServiceRegistration<?> serviceRegistration;
+
+ public OsgiRegistration(ServiceRegistration<?> serviceRegistration) {
+ this.serviceRegistration = serviceRegistration;
+ }
+
+ public OsgiRegistration() {
+ this.serviceRegistration = null;
+ }
+
+ @Override
+ public void close() {
+ if (serviceRegistration != null) {
+ serviceRegistration.unregister();
+ }
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.osgi;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.opendaylight.controller.config.manager.impl.factoriesresolver.ModuleFactoriesResolver;
+import org.opendaylight.controller.config.spi.ModuleFactory;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * Retrieves list of currently registered Module Factories using bundlecontext.
+ */
+public class BundleContextBackedModuleFactoriesResolver implements
+ ModuleFactoriesResolver {
+ private final BundleContext bundleContext;
+
+ public BundleContextBackedModuleFactoriesResolver(
+ BundleContext bundleContext) {
+ this.bundleContext = bundleContext;
+ }
+
+ @Override
+ public List<? extends ModuleFactory> getAllFactories() {
+ Collection<ServiceReference<ModuleFactory>> serviceReferences;
+ try {
+ serviceReferences = bundleContext.getServiceReferences(
+ ModuleFactory.class, null);
+ } catch (InvalidSyntaxException e) {
+ throw new IllegalStateException(e);
+ }
+ List<ModuleFactory> result = new ArrayList<>(serviceReferences.size());
+ for (ServiceReference<ModuleFactory> serviceReference : serviceReferences) {
+ ModuleFactory service = bundleContext.getService(serviceReference);
+ // null if the service is not registered, the service object
+ // returned by a ServiceFactory does not
+ // implement the classes under which it was registered or the
+ // ServiceFactory threw an exception.
+ if (service != null) {
+ result.add(service);
+ }
+ }
+ return result;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.osgi;
+
+import java.lang.management.ManagementFactory;
+
+import javax.management.MBeanServer;
+
+import org.opendaylight.controller.config.manager.impl.ConfigRegistryImpl;
+import org.opendaylight.controller.config.manager.impl.jmx.ConfigRegistryJMXRegistrator;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ConfigManagerActivator implements BundleActivator {
+ private static final Logger logger = LoggerFactory
+ .getLogger(ConfigManagerActivator.class);
+
+ private ExtenderBundleTracker extenderBundleTracker;
+ private ConfigRegistryImpl configRegistry;
+ private ConfigRegistryJMXRegistrator configRegistryJMXRegistrator;
+
+ @Override
+ public void start(BundleContext context) throws Exception {
+ extenderBundleTracker = new ExtenderBundleTracker(context);
+ extenderBundleTracker.open();
+ BundleContextBackedModuleFactoriesResolver bundleContextBackedModuleFactoriesResolver = new BundleContextBackedModuleFactoriesResolver(
+ context);
+
+ MBeanServer configMBeanServer = ManagementFactory
+ .getPlatformMBeanServer();
+ configRegistry = new ConfigRegistryImpl(
+ bundleContextBackedModuleFactoriesResolver, context,
+ configMBeanServer);
+ // register config registry to jmx
+
+ configRegistryJMXRegistrator = new ConfigRegistryJMXRegistrator(
+ configMBeanServer);
+ configRegistryJMXRegistrator.registerToJMX(configRegistry);
+ }
+
+ @Override
+ public void stop(BundleContext context) throws Exception {
+ try {
+ configRegistry.close();
+ } catch (Exception e) {
+ logger.warn("Exception while closing config registry", e);
+ }
+ try {
+ extenderBundleTracker.close();
+ } catch (Exception e) {
+ logger.warn("Exception while closing extender", e);
+ }
+ try {
+ configRegistryJMXRegistrator.close();
+ } catch (Exception e) {
+ logger.warn(
+ "Exception while closing config registry jmx registrator",
+ e);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.osgi;
+
+import static java.lang.String.format;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.util.List;
+
+import org.apache.commons.io.IOUtils;
+import org.opendaylight.controller.config.spi.ModuleFactory;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.util.tracker.BundleTracker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * OSGi extender that listens for bundle activation events. Reads file
+ * META-INF/services/org.opendaylight.controller.config.spi.ModuleFactory, each
+ * line should contain an implementation of ModuleFactory interface. Creates new
+ * instance with default constructor and registers it into OSGi service
+ * registry. There is no need for listening for implementing removedBundle as
+ * the services are unregistered automatically. Code based on
+ * http://www.toedter.com/blog/?p=236
+ */
+
+public class ExtenderBundleTracker extends BundleTracker<Object> {
+
+ private static final Logger logger = LoggerFactory
+ .getLogger(ExtenderBundleTracker.class);
+
+ public ExtenderBundleTracker(BundleContext context) {
+ super(context, Bundle.ACTIVE, null);
+ logger.trace("Registered as extender with context {}", context);
+ }
+
+ @Override
+ public Object addingBundle(Bundle bundle, BundleEvent event) {
+ URL resource = bundle.getEntry("META-INF/services/"
+ + ModuleFactory.class.getName());
+ logger.trace(
+ "Got addingBundle event of bundle {}, resource {}, event {}",
+ bundle, resource, event);
+ if (resource != null) {
+ try (InputStream inputStream = resource.openStream()) {
+ List<String> lines = IOUtils.readLines(inputStream);
+ for (String factoryClassName : lines) {
+ registerFactory(factoryClassName, bundle);
+ }
+ } catch (Exception e) {
+ logger.error("Error while reading {}, stopping bundle {}",
+ resource, bundle, e);
+ stopBundleQuietly(bundle);
+ throw new RuntimeException(e);
+ }
+
+ }
+ return bundle;
+ }
+
+ private static void stopBundleQuietly(Bundle bundle) {
+ try {
+ bundle.stop();
+ } catch (BundleException e2) {
+ logger.warn(
+ "Ignoring fact that bundle.stop failed on {}, reason {}",
+ bundle, e2.toString());
+ }
+ }
+
+ // TODO:test
+ private static ServiceRegistration<?> registerFactory(
+ String factoryClassName, Bundle bundle) {
+ String errorMessage;
+ try {
+ Class<?> clazz = bundle.loadClass(factoryClassName);
+ if (ModuleFactory.class.isAssignableFrom(clazz)) {
+ try {
+ logger.debug("Registering {} in bundle {}",
+ clazz.getName(), bundle);
+ return bundle.getBundleContext().registerService(
+ ModuleFactory.class.getName(), clazz.newInstance(),
+ null);
+ } catch (InstantiationException e) {
+ errorMessage = logMessage(
+ "Could not instantiate {} in bundle {}, reason {}",
+ factoryClassName, bundle, e);
+ } catch (IllegalAccessException e) {
+ errorMessage = logMessage(
+ "Illegal access during instatiation of class {} in bundle {}, reason {}",
+ factoryClassName, bundle, e);
+ }
+ } else {
+ errorMessage = logMessage(
+ "Class {} does not implement {} in bundle {}", clazz,
+ ModuleFactory.class, bundle);
+ }
+ } catch (ClassNotFoundException e) {
+ errorMessage = logMessage(
+ "Could not find class {} in bunde {}, reason {}",
+ factoryClassName, bundle, e);
+ }
+ throw new IllegalStateException(errorMessage);
+ }
+
+ public static String logMessage(String slfMessage, Object... params) {
+ logger.info(slfMessage, params);
+ String formatMessage = slfMessage.replaceAll("\\{\\}", "%s");
+ return format(formatMessage, params);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.util;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import javax.management.JMX;
+
+import org.opendaylight.controller.config.api.annotations.AbstractServiceInterface;
+import org.opendaylight.controller.config.api.annotations.ServiceInterfaceAnnotation;
+import org.opendaylight.controller.config.spi.Module;
+
+public class InterfacesHelper {
+
+ public static Set<Class<?>> getAllInterfaces(Class<?> clazz) {
+ if (clazz.isInterface()) {
+ throw new IllegalArgumentException(clazz
+ + " should not be an interface");
+ }
+ // getInterfaces gets interfaces implemented directly by this class
+ Set<Class<?>> toBeInspected = new HashSet<>();
+ while (clazz.equals(Object.class) == false) {
+ toBeInspected.addAll(Arrays.asList(clazz.getInterfaces()));
+ // get parent class
+ clazz = clazz.getSuperclass();
+ }
+ // each interface can extend other interfaces
+ Set<Class<?>> inspected = new HashSet<>();
+ while (toBeInspected.size() > 0) {
+ Iterator<Class<?>> iterator = toBeInspected.iterator();
+ Class<?> ifc = iterator.next();
+ iterator.remove();
+ toBeInspected.addAll(Arrays.asList(ifc.getInterfaces()));
+ inspected.add(ifc);
+ }
+ return inspected;
+ }
+
+ /**
+ * Get interfaces that this class is derived from that are JMX interfaces.
+ */
+ public static Set<Class<?>> getMXInterfaces(
+ Class<? extends Module> configBeanClass) {
+ Set<Class<?>> allInterfaces = getAllInterfaces(configBeanClass);
+ Set<Class<?>> result = new HashSet<>();
+ for (Class<?> clazz : allInterfaces) {
+ if (JMX.isMXBeanInterface(clazz)) {
+ result.add(clazz);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Get all implemented interfaces that have
+ * {@link org.opendaylight.controller.config.api.annotations.ServiceInterfaceAnnotation}
+ * annotation.
+ */
+ public static Set<Class<?>> getServiceInterfaces(
+ Class<? extends Module> configBeanClass) {
+ Set<Class<?>> allInterfaces = getAllInterfaces(configBeanClass);
+ Set<Class<?>> result = new HashSet<>();
+ for (Class<?> clazz : allInterfaces) {
+ if (AbstractServiceInterface.class.isAssignableFrom(clazz)) {
+ ServiceInterfaceAnnotation annotation = clazz
+ .getAnnotation(ServiceInterfaceAnnotation.class);
+ if (annotation != null) {
+ result.add(clazz);
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Get OSGi registration types under which config bean instance should be
+ * registered. This is specified in
+ * {@link org.opendaylight.controller.config.api.annotations.ServiceInterfaceAnnotation#osgiRegistrationType()}
+ */
+ public static Set<Class<?>> getOsgiRegistrationTypes(
+ Class<? extends Module> configBeanClass) {
+ Set<Class<?>> serviceInterfaces = getServiceInterfaces(configBeanClass);
+ Set<Class<?>> result = new HashSet<>();
+ for (Class<?> clazz : serviceInterfaces) {
+ ServiceInterfaceAnnotation annotation = clazz
+ .getAnnotation(ServiceInterfaceAnnotation.class);
+ result.add(annotation.osgiRegistrationType());
+ }
+ return result;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.util;
+
+import java.util.Set;
+
+import javax.management.InstanceNotFoundException;
+import javax.management.ObjectName;
+
+import org.opendaylight.controller.config.api.LookupRegistry;
+
+public class LookupBeansUtil {
+
+ public static ObjectName lookupConfigBean(LookupRegistry lookupRegistry,
+ String moduleName, String instanceName)
+ throws InstanceNotFoundException {
+ Set<ObjectName> objectNames = lookupRegistry.lookupConfigBeans(
+ moduleName, instanceName);
+ if (objectNames.size() == 0) {
+ throw new InstanceNotFoundException("No instance found");
+ } else if (objectNames.size() > 1) {
+ throw new InstanceNotFoundException("Too many instances found");
+ }
+ return objectNames.iterator().next();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import java.lang.management.ManagementFactory;
+
+import org.junit.Test;
+import org.opendaylight.controller.config.manager.impl.AbstractLockedPlatformMBeanServerTest;
+import org.opendaylight.controller.config.manager.impl.ConfigRegistryImpl;
+import org.opendaylight.controller.config.manager.impl.factoriesresolver.HardcodedModuleFactoriesResolver;
+import org.opendaylight.controller.config.manager.impl.factoriesresolver.ModuleFactoriesResolver;
+import org.opendaylight.controller.config.manager.testingservices.threadpool.TestingFixedThreadPoolModuleFactory;
+import org.opendaylight.controller.config.spi.ModuleFactory;
+import org.osgi.framework.BundleContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ConfigRegistryImplTest extends
+ AbstractLockedPlatformMBeanServerTest {
+ private static final Logger logger = LoggerFactory
+ .getLogger(ConfigRegistryImplTest.class);
+
+ @Test
+ public void testFailOnTwoFactoriesExportingSameImpl() {
+ ModuleFactory factory = new TestingFixedThreadPoolModuleFactory();
+ ModuleFactoriesResolver resolver = new HardcodedModuleFactoriesResolver(
+ factory, factory);
+
+ BundleContext context = mock(BundleContext.class);
+
+ ConfigRegistryImpl configRegistry = new ConfigRegistryImpl(resolver,
+ context, ManagementFactory.getPlatformMBeanServer());
+ try {
+ configRegistry.beginConfig();
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertTrue(
+ e.getMessage(),
+ e.getMessage()
+ .startsWith("Module name is not unique. Found two conflicting factories with same name " +
+ "'fixed':"));
+ verifyZeroInteractions(context);
+ } finally {
+ try {
+ configRegistry.close();
+ } catch (Exception e) {
+ // ignore
+ logger.warn("Ignoring exception", e);
+ }
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import java.io.Closeable;
+import java.lang.management.ManagementFactory;
+import java.util.Dictionary;
+import java.util.Set;
+
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import org.junit.After;
+import org.mockito.Matchers;
+import org.opendaylight.controller.config.api.jmx.CommitStatus;
+import org.opendaylight.controller.config.manager.impl.factoriesresolver.ModuleFactoriesResolver;
+import org.opendaylight.controller.config.manager.impl.jmx.BaseJMXRegistrator;
+import org.opendaylight.controller.config.manager.impl.jmx.ConfigRegistryJMXRegistrator;
+import org.opendaylight.controller.config.manager.impl.jmx.InternalJMXRegistrator;
+import org.opendaylight.controller.config.spi.Module;
+import org.opendaylight.controller.config.util.ConfigRegistryJMXClient;
+import org.opendaylight.controller.config.util.ConfigTransactionJMXClient;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * Each test that relies on
+ * {@link org.opendaylight.controller.config.manager.impl.ConfigRegistryImpl}
+ * needs to subclass this test.
+ * {@link org.opendaylight.controller.config.manager.impl.ConfigRegistryImpl} is
+ * registered to platform MBean Server using
+ * {@link #initConfigTransactionManagerImpl(org.opendaylight.controller.config.manager.impl.factoriesresolver.ModuleFactoriesResolver)}
+ * typically during setting up the each test.
+ */
+public abstract class AbstractConfigTest extends
+ AbstractLockedPlatformMBeanServerTest {
+ protected ConfigRegistryJMXRegistrator configRegistryJMXRegistrator;
+ protected ConfigRegistryImpl configRegistry;
+ protected ConfigRegistryJMXClient configRegistryClient;
+ protected BaseJMXRegistrator baseJmxRegistrator;
+ protected InternalJMXRegistrator internalJmxRegistrator;
+
+ // this method should be called in @Before
+ protected void initConfigTransactionManagerImpl(
+ ModuleFactoriesResolver resolver) {
+ final MBeanServer platformMBeanServer = ManagementFactory
+ .getPlatformMBeanServer();
+
+ configRegistryJMXRegistrator = new ConfigRegistryJMXRegistrator(
+ platformMBeanServer);
+ BundleContext context = mock(BundleContext.class);
+ ServiceRegistration<?> mockedServiceRegistration = mock(ServiceRegistration.class);
+ doNothing().when(mockedServiceRegistration).unregister();
+ doReturn(mockedServiceRegistration).when(context).registerService(
+ Matchers.any(String[].class), any(Closeable.class),
+ any(Dictionary.class));
+ internalJmxRegistrator = new InternalJMXRegistrator(platformMBeanServer);
+ baseJmxRegistrator = new BaseJMXRegistrator(internalJmxRegistrator);
+
+ configRegistry = new ConfigRegistryImpl(resolver, context,
+ platformMBeanServer, baseJmxRegistrator);
+ try {
+ configRegistryJMXRegistrator.registerToJMX(configRegistry);
+ } catch (InstanceAlreadyExistsException e) {
+ throw new RuntimeException(e);
+ }
+ configRegistryClient = new ConfigRegistryJMXClient(platformMBeanServer);
+ }
+
+ @After
+ public final void cleanUpConfigTransactionManagerImpl() {
+ configRegistryJMXRegistrator.close();
+ configRegistry.close();
+ }
+
+ /**
+ * Can be called in @After of tests if some other cleanup is needed that
+ * would be discarded by closing config beans in this method
+ */
+ protected void destroyAllConfigBeans() throws Exception {
+ ConfigTransactionJMXClient transaction = configRegistryClient
+ .createTransaction();
+ Set<ObjectName> all = transaction.lookupConfigBeans();
+ // workaround for getting same Module more times
+ while (all.size() > 0) {
+ transaction.destroyModule(all.iterator().next());
+ all = transaction.lookupConfigBeans();
+ }
+ transaction.commit();
+ }
+
+ protected void assertSame(ObjectName oN1, ObjectName oN2) {
+ assertEquals(oN1.getKeyProperty("instanceName"),
+ oN2.getKeyProperty("instanceName"));
+ assertEquals(oN1.getKeyProperty("interfaceName"),
+ oN2.getKeyProperty("interfaceName"));
+ }
+
+ protected void assertStatus(CommitStatus status, int expectedNewInstances,
+ int expectedRecreatedInstances, int expectedReusedInstances) {
+ assertEquals(expectedNewInstances, status.getNewInstances().size());
+ assertEquals(expectedRecreatedInstances, status.getRecreatedInstances()
+ .size());
+ assertEquals(expectedReusedInstances, status.getReusedInstances()
+ .size());
+ }
+
+ @Deprecated
+ protected ObjectName createTestConfigBean(
+ ConfigTransactionJMXClient transaction, String implementationName,
+ String name, Class<?> clz) throws InstanceAlreadyExistsException {
+ ObjectName nameCreated = transaction.createModule(implementationName,
+ name);
+ transaction.newMXBeanProxy(nameCreated, clz);
+ return nameCreated;
+ }
+
+ protected ObjectName createTestConfigBean(
+ ConfigTransactionJMXClient transaction, String implementationName,
+ String name) throws InstanceAlreadyExistsException {
+ ObjectName nameCreated = transaction.createModule(implementationName,
+ name);
+ return nameCreated;
+ }
+
+ protected void assertBeanCount(int i, String configMXBeanName) {
+ assertEquals(i, configRegistry.lookupConfigBeans(configMXBeanName)
+ .size());
+ }
+
+ protected void assertBeanExists(int count, String moduleName,
+ String instanceName) {
+ assertEquals(1,
+ configRegistry.lookupConfigBeans(moduleName, instanceName)
+ .size());
+ }
+
+ /**
+ *
+ * @param configBeanClass
+ * Empty constructor class of config bean to be instantiated
+ * whenever create
+ * @param implementationName
+ * @return
+ */
+ protected ClassBasedModuleFactory createClassBasedCBF(
+ Class<? extends Module> configBeanClass, String implementationName) {
+ return new ClassBasedModuleFactory(implementationName, configBeanClass);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl;
+
+import org.junit.After;
+import org.junit.Before;
+import org.opendaylight.controller.config.manager.impl.factoriesresolver.ModuleFactoriesResolver;
+import org.opendaylight.controller.config.util.JolokiaHelper;
+import org.opendaylight.controller.config.util.jolokia.ConfigRegistryJolokiaClient;
+
+public class AbstractConfigWithJolokiaTest extends AbstractConfigTest {
+ protected String jolokiaURL;
+ protected ConfigRegistryJolokiaClient configRegistryJolokiaClient;
+
+ @Before
+ public void initJolokia() {
+ jolokiaURL = JolokiaHelper.startTestingJolokia();
+ }
+
+ // this method should be called in @Before
+ @Override
+ protected void initConfigTransactionManagerImpl(
+ ModuleFactoriesResolver resolver) {
+ super.initConfigTransactionManagerImpl(resolver);
+ configRegistryJolokiaClient = new ConfigRegistryJolokiaClient(
+ jolokiaURL);
+ }
+
+ @After
+ public void cleanUpJolokia() {
+ JolokiaHelper.stopJolokia();
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl;
+
+import java.lang.management.ManagementFactory;
+import java.util.concurrent.locks.ReentrantLock;
+
+import javax.management.MBeanServer;
+
+import org.junit.After;
+import org.junit.Before;
+
+/**
+ * Each test that works with platform MBeanServer should extend this class.
+ */
+public abstract class AbstractLockedPlatformMBeanServerTest {
+ private static final ReentrantLock lock = new ReentrantLock();
+ protected static MBeanServer platformMBeanServer = ManagementFactory
+ .getPlatformMBeanServer();
+
+ @Before
+ public void acquireLock() {
+ lock.lock();
+ }
+
+ @After
+ public void unlock() {
+ lock.unlock();
+ }
+
+ public static class SimpleBean implements SimpleBeanMBean {
+
+ }
+
+ public static interface SimpleBeanMBean {
+
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl;
+
+import org.opendaylight.controller.config.api.DynamicMBeanWithInstance;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.spi.Module;
+
+public abstract class AbstractMockedModule implements Module {
+
+ protected final AutoCloseable instance;
+
+ public AbstractMockedModule() throws Exception {
+ instance = prepareMockedInstance();
+ }
+
+ protected abstract AutoCloseable prepareMockedInstance() throws Exception;
+
+ public AbstractMockedModule(DynamicMBeanWithInstance old) {
+ instance = old.getInstance();
+ }
+
+ @Override
+ public void validate() {
+ }
+
+ @Override
+ public AutoCloseable getInstance() {
+ return instance;
+ }
+
+ @Override
+ public ModuleIdentifier getName() {
+ return new ModuleIdentifier(getClass().getCanonicalName(), "mock");
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl;
+
+import java.lang.reflect.Constructor;
+import java.util.Arrays;
+import java.util.List;
+
+import org.opendaylight.controller.config.api.DependencyResolver;
+import org.opendaylight.controller.config.api.DynamicMBeanWithInstance;
+import org.opendaylight.controller.config.api.annotations.AbstractServiceInterface;
+import org.opendaylight.controller.config.spi.Module;
+import org.opendaylight.controller.config.spi.ModuleFactory;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Throwables;
+
+/**
+ * Creates new Config beans by calling {@link Class#newInstance()} on provided
+ * config bean class.
+ *
+ */
+public class ClassBasedModuleFactory implements ModuleFactory {
+ private final String implementationName;
+ private final Class<? extends Module> configBeanClass;
+
+ /**
+ * @param implementationName
+ * @param configBeanClass
+ * class that will be instantiated when createModule is called.
+ * This class must implement Module interface and all exported
+ * interfaces.
+ */
+ public ClassBasedModuleFactory(String implementationName,
+ Class<? extends Module> configBeanClass) {
+ this.implementationName = implementationName;
+ this.configBeanClass = configBeanClass;
+ }
+
+ @Override
+ public String getImplementationName() {
+ return implementationName;
+ }
+
+ @Override
+ public Module createModule(String instanceName,
+ DependencyResolver dependencyResolver, DynamicMBeanWithInstance old)
+ throws Exception {
+ Preconditions.checkNotNull(dependencyResolver);
+ Preconditions.checkNotNull(old);
+ Constructor<? extends Module> declaredConstructor;
+ try {
+ declaredConstructor = configBeanClass
+ .getDeclaredConstructor(DynamicMBeanWithInstance.class);
+ } catch (NoSuchMethodException e) {
+ throw new IllegalStateException(
+ "Did not find constructor with parameters (DynamicMBeanWithInstance) in "
+ + configBeanClass, e);
+ }
+ Preconditions.checkState(declaredConstructor != null);
+ return declaredConstructor.newInstance(old);
+ }
+
+ @Override
+ public Module createModule(String instanceName,
+ DependencyResolver dependencyResolver) {
+ try {
+ return configBeanClass.newInstance();
+ } catch (Exception e) {
+ throw Throwables.propagate(e);
+ }
+ }
+
+ @Override
+ public boolean isModuleImplementingServiceInterface(
+ Class<? extends AbstractServiceInterface> serviceInterface) {
+ Class<?>[] classes = configBeanClass.getInterfaces();
+ List<Class<?>> ifc = Arrays.asList(classes);
+ if (ifc.contains(serviceInterface)) {
+ return true;
+ }
+ for (Class<?> c : classes) {
+ ifc = Arrays.asList(c.getInterfaces());
+ if (ifc.contains(serviceInterface)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import java.lang.management.ManagementFactory;
+import java.lang.reflect.Field;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.ObjectName;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.api.runtime.RuntimeBean;
+import org.opendaylight.controller.config.manager.impl.jmx.BaseJMXRegistrator;
+import org.opendaylight.controller.config.manager.impl.jmx.RootRuntimeBeanRegistratorImpl;
+import org.opendaylight.controller.config.manager.impl.runtimembean.TestingRuntimeBean;
+
+import com.google.common.collect.Sets;
+
+public class ConfigRegistryImplLookupTest extends
+ AbstractLockedPlatformMBeanServerTest {
+
+ private ConfigRegistryImpl configRegistryImpl;
+ private BaseJMXRegistrator baseJMXRegistrator;
+
+ private static final String moduleNameA = "moduleA";
+ private static final String moduleNameB = "moduleB";
+
+ private static final String instanceNameA = "instA";
+ private static final String instanceNameB = "instB";
+ private static final String instanceNameC = "instC";
+
+ private static final ObjectName name1 = ObjectNameUtil
+ .createReadOnlyModuleON(moduleNameA, instanceNameA);
+ private static final ObjectName name2 = ObjectNameUtil
+ .createReadOnlyModuleON(moduleNameA, instanceNameB);
+ private static final ObjectName name3 = ObjectNameUtil
+ .createReadOnlyModuleON(moduleNameA, instanceNameC);
+ private static final ObjectName name4 = ObjectNameUtil
+ .createReadOnlyModuleON(moduleNameB, instanceNameA);
+
+ private static final ObjectName name5 = ObjectNameUtil
+ .createRuntimeBeanName(moduleNameA, instanceNameA, Collections.<String, String>emptyMap());
+ private static final ObjectName name6 = ObjectNameUtil
+ .createRuntimeBeanName(moduleNameA, instanceNameB, Collections.<String, String>emptyMap());
+ private static final ObjectName name8 = ObjectNameUtil
+ .createRuntimeBeanName(moduleNameB, instanceNameA, Collections.<String, String>emptyMap());
+
+ private static final ObjectName name9 = ObjectNameUtil
+ .createTransactionModuleON("transaction", moduleNameA, instanceNameA);
+
+ @Before
+ public void setUp() throws Exception {
+ configRegistryImpl = new ConfigRegistryImpl(null, null,
+ ManagementFactory.getPlatformMBeanServer());
+ Field field = configRegistryImpl.getClass().getDeclaredField(
+ "baseJMXRegistrator");
+ field.setAccessible(true);
+ baseJMXRegistrator = (BaseJMXRegistrator) field.get(configRegistryImpl);
+
+ registerModuleBean(new TestingRuntimeBean(), baseJMXRegistrator, name1);
+ registerModuleBean(new TestingRuntimeBean(), baseJMXRegistrator, name2);
+ registerModuleBean(new TestingRuntimeBean(), baseJMXRegistrator, name3);
+ registerModuleBean(new TestingRuntimeBean(), baseJMXRegistrator, name4);
+
+ registerRuntimeBean(new TestingRuntimeBean(), baseJMXRegistrator, name5);
+ registerRuntimeBean(new TestingRuntimeBean(), baseJMXRegistrator, name6);
+ registerRuntimeBean(new TestingRuntimeBean(), baseJMXRegistrator, name8);
+
+ baseJMXRegistrator.createTransactionJMXRegistrator("transaction")
+ .createTransactionModuleJMXRegistrator()
+ .registerMBean(new TestingRuntimeBean(), name9);
+
+ }
+
+ private void registerModuleBean(TestingRuntimeBean testingRuntimeBean,
+ BaseJMXRegistrator baseJMXRegistrator, ObjectName objectName)
+ throws InstanceAlreadyExistsException {
+ baseJMXRegistrator.createModuleJMXRegistrator().registerMBean(
+ testingRuntimeBean, objectName);
+ }
+
+ private void registerRuntimeBean(RuntimeBean object,
+ BaseJMXRegistrator baseJMXRegistrator, ObjectName runtimeON)
+ throws InstanceAlreadyExistsException {
+ String factoryName = ObjectNameUtil.getFactoryName(runtimeON);
+ String instanceName = ObjectNameUtil.getInstanceName(runtimeON);
+ Map<String, String> properties = ObjectNameUtil
+ .getAdditionalPropertiesOfRuntimeBeanName(runtimeON);
+
+ RootRuntimeBeanRegistratorImpl runtimeBeanRegistrator = baseJMXRegistrator
+ .createRuntimeBeanRegistrator(new ModuleIdentifier(factoryName, instanceName));
+
+ assertThat(properties.isEmpty(), is(true));
+
+ runtimeBeanRegistrator.registerRoot(object);
+ }
+
+ @After
+ public void cleanUp() {
+ baseJMXRegistrator.close();
+ }
+
+ @Test
+ public void testLookupConfigBeans() throws Exception {
+ Set<ObjectName> beans = configRegistryImpl.lookupConfigBeans();
+ assertEquals(Sets.newHashSet(name1, name2, name3, name4), beans);
+ beans = configRegistryImpl.lookupConfigBeans();
+ assertEquals(Sets.newHashSet(name1, name2, name3, name4), beans);
+ }
+
+ @Test
+ public void testLookupConfigBeanWithModuleName() throws Exception {
+ Set<ObjectName> bean = configRegistryImpl
+ .lookupConfigBeans(moduleNameA);
+ assertEquals(Sets.newHashSet(name1, name2, name3), bean);
+ }
+
+ @Test
+ public void testLookupConfigBeanWithModuleNameAndInstanceName()
+ throws Exception {
+ Set<ObjectName> bean = configRegistryImpl.lookupConfigBeans(
+ moduleNameA, instanceNameA);
+ assertEquals(Sets.newHashSet(name1), bean);
+ }
+
+ @Test
+ public void testLookupRuntimeBeans() throws Exception {
+ Set<ObjectName> beans = configRegistryImpl.lookupRuntimeBeans();
+ assertEquals(Sets.newHashSet(name5, name6, name8), beans);
+ beans = configRegistryImpl.lookupRuntimeBeans(null, null);
+ assertEquals(Sets.newHashSet(name5, name6, name8), beans);
+ }
+
+ @Test
+ public void testLookupRuntimeBeansWithIFcNameAndImplName() throws Exception {
+ Set<ObjectName> beans = configRegistryImpl.lookupRuntimeBeans(
+ moduleNameA, instanceNameA);
+ assertEquals(Sets.newHashSet(name5), beans);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl;
+
+import static org.junit.Assert.assertEquals;
+
+import java.lang.management.ManagementFactory;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import javax.management.MBeanServer;
+import javax.management.MBeanServerFactory;
+import javax.management.ObjectName;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.manager.impl.jmx.BaseJMXRegistrator;
+import org.opendaylight.controller.config.manager.impl.jmx.TransactionJMXRegistrator;
+import org.opendaylight.controller.config.manager.impl.jmx.TransactionModuleJMXRegistrator;
+import org.opendaylight.controller.config.manager.impl.runtimembean.TestingRuntimeBean;
+import org.opendaylight.controller.config.spi.ModuleFactory;
+
+import com.google.common.collect.Sets;
+
+public class ConfigTransactionControllerImplTest extends
+ AbstractLockedPlatformMBeanServerTest {
+
+ private BaseJMXRegistrator baseJMXRegistrator;
+
+ private ConfigTransactionControllerImpl testedTxController;
+ private MBeanServer transactionsMBeanServer;
+
+ private static final String transactionName123 = "testTX1";
+ private static final String transactionName4 = "testTX2";
+
+ private static final String moduleName124 = "module124";
+ private static final String moduleName3 = "module3";
+
+ private static final String instanceName134 = "instA";
+ private static final String instanceName2 = "instB";
+
+ private static final ObjectName name1 = ObjectNameUtil
+ .createTransactionModuleON(transactionName123, moduleName124, instanceName134);
+ private static final ObjectName name2 = ObjectNameUtil
+ .createTransactionModuleON(transactionName123, moduleName124, instanceName2);
+ private static final ObjectName name3 = ObjectNameUtil
+ .createTransactionModuleON(transactionName123, moduleName3, instanceName134);
+ private static final ObjectName name4 = ObjectNameUtil
+ .createTransactionModuleON(transactionName4, moduleName124, instanceName134);
+
+ @Before
+ public void setUp() throws Exception {
+ baseJMXRegistrator = new BaseJMXRegistrator(
+ ManagementFactory.getPlatformMBeanServer());
+ transactionsMBeanServer = MBeanServerFactory.createMBeanServer();
+ List<? extends ModuleFactory> currentlyRegisteredFactories = new ArrayList<>();
+ TransactionJMXRegistrator jmxRegistrator123 = baseJMXRegistrator
+ .createTransactionJMXRegistrator(transactionName123);
+
+ testedTxController = new ConfigTransactionControllerImpl(
+ transactionName123, jmxRegistrator123, 1, 1,
+ currentlyRegisteredFactories, transactionsMBeanServer,
+ ManagementFactory.getPlatformMBeanServer());
+ TransactionModuleJMXRegistrator transactionModuleJMXRegistrator123 = testedTxController
+ .getTxModuleJMXRegistrator();
+ transactionModuleJMXRegistrator123.registerMBean(
+ new TestingRuntimeBean(), name1);
+ transactionModuleJMXRegistrator123.registerMBean(
+ new TestingRuntimeBean(), name2);
+ transactionModuleJMXRegistrator123.registerMBean(
+ new TestingRuntimeBean(), name3);
+ TransactionJMXRegistrator jmxRegistrator4 = baseJMXRegistrator
+ .createTransactionJMXRegistrator(transactionName4);
+ jmxRegistrator4.createTransactionModuleJMXRegistrator().registerMBean(
+ new TestingRuntimeBean(), name4);
+ }
+
+ @After
+ public void cleanUp() {
+ baseJMXRegistrator.close();
+ MBeanServerFactory.releaseMBeanServer(transactionsMBeanServer);
+ }
+
+ /**
+ * Tests if lookup method returns all beans with defined transaction name
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testLookupConfigBeans() {
+ Set<ObjectName> beans = testedTxController.lookupConfigBeans();
+ assertEquals(Sets.newHashSet(name1, name2, name3), beans);
+ }
+
+ @Test
+ public void testLookupConfigBeansWithModuleName() {
+ Set<ObjectName> beans = testedTxController
+ .lookupConfigBeans(moduleName124);
+ assertEquals(Sets.newHashSet(name1, name2), beans);
+ }
+
+ @Test
+ public void lookupConfigBeansWithModuleNameAndImplName() throws Exception {
+ Set<ObjectName> beans = testedTxController.lookupConfigBeans(
+ moduleName124, instanceName134);
+ assertEquals(Sets.newHashSet(name1), beans);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+
+import javax.management.InstanceAlreadyExistsException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.api.jmx.constants.ConfigRegistryConstants;
+import org.opendaylight.controller.config.manager.impl.factoriesresolver.HardcodedModuleFactoriesResolver;
+import org.opendaylight.controller.config.util.ConfigTransactionJMXClient;
+import org.opendaylight.controller.config.util.jolokia.ConfigTransactionJolokiaClient;
+
+public class ConfigTransactionManagerImplTest extends
+ AbstractConfigWithJolokiaTest {
+
+ @Before
+ public void setUp() {
+ super.initConfigTransactionManagerImpl(new HardcodedModuleFactoriesResolver());
+
+ }
+
+ @Test
+ public void testSingleton() {
+ ConfigRegistryImpl mockedRegistry = mock(ConfigRegistryImpl.class);
+ try {
+ configRegistryJMXRegistrator.registerToJMX(mockedRegistry);
+ fail();
+ } catch (Exception e) {
+ assertTrue(e instanceof InstanceAlreadyExistsException);
+ }
+ }
+
+ @Test
+ public void testCleanUp() {
+ super.cleanUpConfigTransactionManagerImpl();
+ setUp();
+ }
+
+ @Test
+ public void testRemoteCallsUsingJMX() throws Exception {
+ ConfigTransactionJMXClient transaction = configRegistryClient
+ .createTransaction();
+ transaction.commit();
+ }
+
+ @Test
+ public void testRemoteCallsUsingJolokia() throws Exception {
+
+ ConfigTransactionJolokiaClient transactionClient = configRegistryJolokiaClient
+ .createTransaction();
+
+ assertEquals("ConfigTransaction-0-1",
+ ObjectNameUtil.getTransactionName(transactionClient
+ .getTransactionON()));
+
+ assertEquals(
+ ConfigRegistryConstants.ON_DOMAIN
+ + ":TransactionName=ConfigTransaction-0-1,type=ConfigTransaction",
+ transactionClient.getTransactionON().getCanonicalName());
+
+ // commit
+ transactionClient.commit();
+
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.dependencyresolver;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.config.api.JmxAttribute;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.manager.impl.ModuleInternalTransactionalInfo;
+import org.opendaylight.controller.config.manager.impl.TransactionStatus;
+import org.opendaylight.controller.config.spi.Module;
+
+public class DependencyResolverManagerTest {
+
+ final ModuleIdentifier apspName = new ModuleIdentifier("apsp", "apsp"); // depends
+ // on:
+ final ModuleIdentifier threadPoolName = new ModuleIdentifier("threadpool",
+ "threadpool"); // depends on:
+ final ModuleIdentifier threadFactoryName = new ModuleIdentifier(
+ "threadfactory", "threadfactory");
+
+ private DependencyResolverManager tested;
+ TransactionStatus transactionStatus;
+
+ @Before
+ public void setUp() {
+ transactionStatus = mock(TransactionStatus.class);
+ tested = new DependencyResolverManager("txName", transactionStatus);
+ doNothing().when(transactionStatus).checkCommitStarted();
+ doNothing().when(transactionStatus).checkNotCommitted();
+ }
+
+ @Test
+ public void testOrdering() {
+ DependencyResolverImpl apspDRI = tested.getOrCreate(apspName);
+ mockGetInstance(tested, apspName);
+ DependencyResolverImpl threadPoolDRI = tested
+ .getOrCreate(threadPoolName);
+ mockGetInstance(tested, threadPoolName);
+ tested.getOrCreate(threadFactoryName);
+ mockGetInstance(tested, threadFactoryName);
+
+ // set threadfactory as dependency of threadpool
+ declareDependency(threadPoolDRI, threadFactoryName);
+ // set threadpool as dependency of apsp
+ declareDependency(apspDRI, threadPoolName);
+
+ // switch to second phase committed
+ reset(transactionStatus);
+ doNothing().when(transactionStatus).checkCommitted();
+ List<ModuleIdentifier> sortedModuleIdentifiers = tested
+ .getSortedModuleIdentifiers();
+ assertEquals(
+ Arrays.asList(threadFactoryName, threadPoolName, apspName),
+ sortedModuleIdentifiers);
+
+ }
+
+ /**
+ * Simulate dependentResolver resolving its dependency identified by
+ * dependentName.
+ */
+ private void declareDependency(DependencyResolverImpl dependerResolver,
+ ModuleIdentifier dependentName) {
+ JmxAttribute dummyAttribute = new JmxAttribute("dummy");
+ dependerResolver.resolveInstance(Object.class,
+ ObjectNameUtil.createReadOnlyModuleON(dependentName),
+ dummyAttribute);
+ }
+
+ private static void mockGetInstance(DependencyResolverManager tested,
+ ModuleIdentifier moduleIdentifier) {
+ ModuleInternalTransactionalInfo mock = mock(ModuleInternalTransactionalInfo.class);
+ doReturn(moduleIdentifier).when(mock).getName();
+ doReturn(mockedModule()).when(mock).getModule();
+ tested.put(mock);
+ }
+
+ private static Module mockedModule() {
+ Module mockedModule = mock(Module.class);
+ doReturn(mock(AutoCloseable.class)).when(mockedModule).getInstance();
+ return mockedModule;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.dynamicmbean;
+
+import static org.junit.Assert.assertEquals;
+
+import java.lang.management.ManagementFactory;
+
+import javax.management.Attribute;
+import javax.management.AttributeList;
+import javax.management.DynamicMBean;
+import javax.management.JMX;
+import javax.management.MBeanInfo;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerFactory;
+import javax.management.ObjectName;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.manager.impl.AbstractLockedPlatformMBeanServerTest;
+import org.opendaylight.controller.config.manager.testingservices.threadpool.TestingFixedThreadPool;
+import org.opendaylight.controller.config.manager.testingservices.threadpool.TestingFixedThreadPoolConfigMXBean;
+import org.opendaylight.controller.config.manager.testingservices.threadpool.TestingFixedThreadPoolModule;
+import org.opendaylight.controller.config.manager.testingservices.threadpool.TestingFixedThreadPoolModuleFactory;
+import org.opendaylight.controller.config.spi.Module;
+
+public abstract class AbstractDynamicWrapperTest extends
+ AbstractLockedPlatformMBeanServerTest {
+ protected final MBeanServer platformMBeanServer = ManagementFactory
+ .getPlatformMBeanServer();
+ private static final String moduleName = "impl";
+ protected final ObjectName threadPoolDynamicWrapperON = ObjectNameUtil
+ .createReadOnlyModuleON(moduleName, "fixed1");
+ protected static final String THREAD_COUNT = "ThreadCount";
+ protected static final String TRIGGER_NEW_INSTANCE_CREATION = "TriggerNewInstanceCreation";
+
+ protected final int threadCount = 5;
+ protected TestingFixedThreadPoolModule threadPoolConfigBean;
+ private static final ModuleIdentifier moduleIdentifier = new ModuleIdentifier(
+ moduleName, "clientname2");
+
+ protected MBeanServer internalServer;
+
+ @Before
+ public void registerToJMX() throws Exception {
+ internalServer = MBeanServerFactory.createMBeanServer();
+ TestingFixedThreadPoolModuleFactory testingFixedThreadPoolConfigBeanFactory = new TestingFixedThreadPoolModuleFactory();
+ threadPoolConfigBean = testingFixedThreadPoolConfigBeanFactory
+ .createModule("", null);
+
+ threadPoolConfigBean.setThreadCount(threadCount);
+ AbstractDynamicWrapper dynamicWrapper = getDynamicWrapper(
+ threadPoolConfigBean, moduleIdentifier);
+ platformMBeanServer.registerMBean(dynamicWrapper,
+ threadPoolDynamicWrapperON);
+ }
+
+ @After
+ public void unregisterFromJMX() throws Exception {
+ TestingFixedThreadPool.cleanUp();
+ platformMBeanServer.unregisterMBean(threadPoolDynamicWrapperON);
+ MBeanServerFactory.releaseMBeanServer(internalServer);
+ }
+
+ protected abstract AbstractDynamicWrapper getDynamicWrapper(Module module,
+ ModuleIdentifier moduleIdentifier);
+
+ @Test
+ public void testReadAttributes() throws Exception {
+
+ DynamicMBean proxy = JMX.newMBeanProxy(platformMBeanServer,
+ threadPoolDynamicWrapperON, DynamicMBean.class);
+
+ assertEquals(threadCount, proxy.getAttribute(THREAD_COUNT));
+
+ assertEquals(threadPoolConfigBean.isTriggerNewInstanceCreation(),
+ proxy.getAttribute(TRIGGER_NEW_INSTANCE_CREATION));
+
+ AttributeList attributes = proxy.getAttributes(new String[] {
+ THREAD_COUNT, TRIGGER_NEW_INSTANCE_CREATION });
+ assertEquals(2, attributes.size());
+ Attribute threadCountAttr = (Attribute) attributes.get(0);
+ assertEquals(THREAD_COUNT, threadCountAttr.getName());
+ assertEquals(threadCount, threadCountAttr.getValue());
+ Attribute boolTestAttr = (Attribute) attributes.get(1);
+ assertEquals(TRIGGER_NEW_INSTANCE_CREATION, boolTestAttr.getName());
+ assertEquals(threadPoolConfigBean.isTriggerNewInstanceCreation(),
+ boolTestAttr.getValue());
+
+ MBeanInfo mBeanInfo = proxy.getMBeanInfo();
+ assertEquals(2, mBeanInfo.getAttributes().length);
+
+ }
+
+ @Test
+ public void testGettersWithMXBeanProxy() {
+ TestingFixedThreadPoolConfigMXBean proxy = JMX.newMXBeanProxy(
+ platformMBeanServer, threadPoolDynamicWrapperON,
+ TestingFixedThreadPoolConfigMXBean.class);
+ assertEquals(threadCount, proxy.getThreadCount());
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.dynamicmbean;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+
+import javax.management.ObjectName;
+
+import org.junit.Test;
+import org.opendaylight.controller.config.api.annotations.AbstractServiceInterface;
+import org.opendaylight.controller.config.api.annotations.Description;
+import org.opendaylight.controller.config.api.annotations.RequireInterface;
+import org.opendaylight.controller.config.api.annotations.ServiceInterfaceAnnotation;
+
+import com.google.common.base.Throwables;
+import com.google.common.collect.Sets;
+
+public class AnnotationsTest {
+
+ private final String setSomethingString = "setSomething";
+
+ private void assertRequireInterfaceAnnotationHasCorrectValue(
+ Class<?> clazz, String methodName,
+ Set<Class<?>> inspectedInterfaces,
+ Class<? extends AbstractServiceInterface> expectedValue) {
+ Method setter = findMethod(clazz, methodName);
+ RequireInterface found = AttributeHolder
+ .findRequireInterfaceAnnotation(setter, inspectedInterfaces);
+ if (expectedValue == null) {
+ assertNull(found);
+ } else {
+ assertNotNull(found);
+ assertEquals(expectedValue, found.value());
+ }
+ }
+
+ private Method findMethod(Class<?> clazz, String methodName) {
+ Method setter;
+ try {
+ setter = clazz.getMethod(methodName,
+ new Class[] { ObjectName.class });
+ } catch (Exception e) {
+ throw Throwables.propagate(e);
+ }
+ return setter;
+ }
+
+ private void assertDescription(Class<?> clazz, String methodName,
+ Set<Class<?>> exportedInterfaces, String expectedValue) {
+ Method setter = findMethod(clazz, methodName);
+ String found = AttributeHolder.findDescription(setter,
+ exportedInterfaces);
+ if (expectedValue == null) {
+ assertNull(found);
+ } else {
+ assertNotNull(found);
+ assertEquals(expectedValue, found);
+ }
+ }
+
+ private void assertDescriptionOnClass(Class<?> clazz,
+ Set<Class<?>> jmxInterfaces, String expectedValue) {
+ String found = AbstractDynamicWrapper.findDescription(clazz,
+ jmxInterfaces);
+ if (expectedValue == null) {
+ assertNull(found);
+ } else {
+ assertNotNull(found);
+ assertEquals(expectedValue, found);
+ }
+ }
+
+ private void assertNoDescriptionOnClass(Class<?> clazz,
+ Set<Class<?>> jmxInterfaces) {
+ String found = AbstractDynamicWrapper.findDescription(clazz,
+ jmxInterfaces);
+ assertTrue(found.isEmpty());
+ }
+
+ static final String SIMPLE = "simple";
+ static final String SUBCLASS2 = "subclass2";
+
+ @ServiceInterfaceAnnotation(value = SIMPLE, osgiRegistrationType = Executor.class)
+ static interface SimpleSI extends AbstractServiceInterface {
+
+ }
+
+ @Description("class")
+ public static class SuperClass {
+ @RequireInterface(SimpleSI.class)
+ @Description("descr")
+ public void setSomething(ObjectName objectName) {
+
+ }
+ }
+
+ private static Set<Class<?>> emptySetOfInterfaces() {
+ return Collections.emptySet();
+ }
+
+ @Test
+ public void testFindAnnotation_directly() throws Exception {
+ assertRequireInterfaceAnnotationHasCorrectValue(SuperClass.class,
+ setSomethingString, emptySetOfInterfaces(), SimpleSI.class);
+ assertDescription(SuperClass.class, setSomethingString,
+ emptySetOfInterfaces(), "descr");
+ assertDescriptionOnClass(SuperClass.class, emptySetOfInterfaces(),
+ "class");
+ }
+
+ public static class SubClassWithout extends SuperClass {
+
+ }
+
+ @Test
+ public void testFindAnnotation_subclassWithout() throws Exception {
+ assertRequireInterfaceAnnotationHasCorrectValue(SubClassWithout.class,
+ setSomethingString, emptySetOfInterfaces(), SimpleSI.class);
+ assertDescription(SubClassWithout.class, setSomethingString,
+ emptySetOfInterfaces(), "descr");
+ assertDescriptionOnClass(SuperClass.class, emptySetOfInterfaces(),
+ "class");
+ }
+
+ public static class SubClassWithEmptyMethod extends SuperClass {
+ @Override
+ public void setSomething(ObjectName objectName) {
+
+ }
+ }
+
+ @Test
+ public void testOverridingWithoutAnnotation() throws Exception {
+ assertRequireInterfaceAnnotationHasCorrectValue(
+ SubClassWithEmptyMethod.class, setSomethingString,
+ emptySetOfInterfaces(), SimpleSI.class);
+ assertDescription(SubClassWithEmptyMethod.class, setSomethingString,
+ emptySetOfInterfaces(), "descr");
+ assertDescriptionOnClass(SubClassWithEmptyMethod.class,
+ emptySetOfInterfaces(), "class");
+ }
+
+ static interface SubSI extends SimpleSI {
+
+ }
+
+ @ServiceInterfaceAnnotation(value = SUBCLASS2, osgiRegistrationType = ExecutorService.class)
+ static interface SubSI2 extends SubSI {
+
+ }
+
+ public static class SubClassWithAnnotation extends SuperClass {
+ @Override
+ @RequireInterface(SubSI2.class)
+ @Description("descr2")
+ public void setSomething(ObjectName objectName) {
+
+ }
+ }
+
+ @Test
+ public void testFindAnnotation_SubClassWithAnnotation() throws Exception {
+ assertDescription(SubClassWithAnnotation.class, setSomethingString,
+ emptySetOfInterfaces(), "descr2\ndescr");
+ try {
+ assertRequireInterfaceAnnotationHasCorrectValue(
+ SubClassWithAnnotation.class, setSomethingString,
+ emptySetOfInterfaces(), SubSI2.class);
+ fail();
+ } catch (IllegalStateException e) {
+ assertTrue(
+ e.getMessage(),
+ e.getMessage()
+ .startsWith("Error finding @RequireInterface. More than one value specified"));
+ }
+ }
+
+ public static interface HasSomeMethod {
+ void setSomething(ObjectName objectName);
+ }
+
+ public static class SubClassWithoutMethodWithInterface extends SuperClass
+ implements HasSomeMethod {
+
+ }
+
+ @Test
+ public void testFindAnnotation_SubClassWithoutMethodWithInterface()
+ throws Exception {
+ assertRequireInterfaceAnnotationHasCorrectValue(
+ SubClassWithoutMethodWithInterface.class, setSomethingString,
+ emptySetOfInterfaces(), SimpleSI.class);
+ assertDescription(SubClassWithoutMethodWithInterface.class,
+ setSomethingString, emptySetOfInterfaces(), "descr");
+ }
+
+ static abstract class SuperClassWithInterface implements HasSomeMethod {
+ @Override
+ @RequireInterface(SubSI2.class)
+ @Description("descr")
+ public void setSomething(ObjectName objectName) {
+
+ }
+ }
+
+ @Description("class")
+ public static class SubClassOfSuperClassWithInterface extends
+ SuperClassWithInterface {
+
+ }
+
+ @Test
+ public void testFindAnnotation_SubClassOfSuperClassWithInterface()
+ throws Exception {
+ assertRequireInterfaceAnnotationHasCorrectValue(
+ SubClassOfSuperClassWithInterface.class, setSomethingString,
+ emptySetOfInterfaces(), SubSI2.class);
+ assertDescription(SubClassOfSuperClassWithInterface.class,
+ setSomethingString, emptySetOfInterfaces(), "descr");
+ assertDescriptionOnClass(SubClassOfSuperClassWithInterface.class,
+ emptySetOfInterfaces(), "class");
+ }
+
+ @Test
+ public void testFindAnnotation2() throws Exception {
+ assertNoDescriptionOnClass(SuperClassWithInterface.class,
+ emptySetOfInterfaces());
+ }
+
+ @Description("class")
+ static interface HasSomeMethodWithAnnotations {
+
+ @RequireInterface(SubSI2.class)
+ @Description("descr")
+ void setSomething(ObjectName objectName);
+ }
+
+ static class HasSomeMethodWithAnnotationsImpl implements
+ HasSomeMethodWithAnnotations {
+ @Override
+ public void setSomething(ObjectName objectName) {
+ }
+
+ }
+
+ @Test
+ public void testHasSomeMethodWithAnnotationsImpl() {
+ HashSet<Class<?>> exportedInterfaces = Sets
+ .<Class<?>> newHashSet(HasSomeMethodWithAnnotations.class);
+ assertRequireInterfaceAnnotationHasCorrectValue(
+ HasSomeMethodWithAnnotationsImpl.class, setSomethingString,
+ exportedInterfaces, SubSI2.class);
+
+ assertDescription(HasSomeMethodWithAnnotationsImpl.class,
+ setSomethingString, exportedInterfaces, "descr");
+
+ assertDescriptionOnClass(
+ HasSomeMethodWithAnnotationsImpl.class,
+ new HashSet<Class<?>>(Arrays
+ .asList(HasSomeMethodWithAnnotations.class)), "class");
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.dynamicmbean;
+
+import java.lang.management.ManagementFactory;
+
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.spi.Module;
+
+public class DynamicReadableWrapperTest extends AbstractDynamicWrapperTest {
+
+ @Override
+ protected AbstractDynamicWrapper getDynamicWrapper(Module module,
+ ModuleIdentifier moduleIdentifier) {
+ return new DynamicReadableWrapper(module, null, moduleIdentifier,
+ internalServer, ManagementFactory.getPlatformMBeanServer());
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.dynamicmbean;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.management.Attribute;
+import javax.management.AttributeList;
+import javax.management.DynamicMBean;
+import javax.management.JMX;
+import javax.management.MBeanServerFactory;
+import javax.management.ObjectName;
+
+import org.junit.Test;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.manager.impl.TransactionIdentifier;
+import org.opendaylight.controller.config.manager.impl.dynamicmbean.ReadOnlyAtomicBoolean.ReadOnlyAtomicBooleanImpl;
+import org.opendaylight.controller.config.manager.testingservices.parallelapsp.TestingParallelAPSPConfigMXBean;
+import org.opendaylight.controller.config.manager.testingservices.parallelapsp.TestingParallelAPSPModule;
+import org.opendaylight.controller.config.manager.testingservices.parallelapsp.TestingParallelAPSPModuleFactory;
+import org.opendaylight.controller.config.manager.testingservices.threadpool.TestingFixedThreadPoolConfigMXBean;
+import org.opendaylight.controller.config.spi.Module;
+
+public class DynamicWritableWrapperTest extends AbstractDynamicWrapperTest {
+ private final int newThreadCount = 10;
+ private final AtomicBoolean atomicBoolean = new AtomicBoolean();
+ private final ReadOnlyAtomicBoolean readOnlyAtomicBoolean = new ReadOnlyAtomicBooleanImpl(
+ atomicBoolean);
+
+ @Override
+ protected AbstractDynamicWrapper getDynamicWrapper(Module module,
+ ModuleIdentifier moduleIdentifier) {
+ return new DynamicWritableWrapper(module, moduleIdentifier,
+ new TransactionIdentifier("transaction-1"),
+ readOnlyAtomicBoolean, MBeanServerFactory.createMBeanServer(),
+ platformMBeanServer);
+ }
+
+ @Test
+ public void testSetAttribute() throws Exception {
+ DynamicMBean proxy = JMX.newMBeanProxy(platformMBeanServer,
+ threadPoolDynamicWrapperON, DynamicMBean.class);
+
+ proxy.setAttribute(new Attribute(THREAD_COUNT, newThreadCount));
+
+ assertEquals(newThreadCount, proxy.getAttribute(THREAD_COUNT));
+ assertEquals(newThreadCount, threadPoolConfigBean.getThreadCount());
+
+ AttributeList attributeList = new AttributeList();
+ attributeList.add(new Attribute(THREAD_COUNT, threadCount));
+ boolean bool = true;
+ attributeList.add(new Attribute(TRIGGER_NEW_INSTANCE_CREATION, bool));
+ proxy.setAttributes(attributeList);
+
+ assertEquals(threadCount, threadPoolConfigBean.getThreadCount());
+ assertEquals(bool, threadPoolConfigBean.isTriggerNewInstanceCreation());
+ }
+
+ @Test
+ public void testSettersWithMXBeanProxy() {
+ TestingFixedThreadPoolConfigMXBean proxy = JMX.newMXBeanProxy(
+ platformMBeanServer, threadPoolDynamicWrapperON,
+ TestingFixedThreadPoolConfigMXBean.class);
+ proxy.setThreadCount(newThreadCount);
+ assertEquals(newThreadCount, threadPoolConfigBean.getThreadCount());
+ }
+
+ /*
+ * Try to call setter with ObjectName containing transaction name. Verify
+ * that ObjectName without transaction name was actually passed on the
+ * config bean.
+ */
+ @Test
+ public void testObjectNameSetterWithONContainingTransaction_shouldBeTranslatedToReadOnlyON()
+ throws Exception {
+ TestingParallelAPSPModuleFactory testingParallelAPSPConfigBeanFactory = new TestingParallelAPSPModuleFactory();
+ TestingParallelAPSPModule apspConfigBean = testingParallelAPSPConfigBeanFactory
+ .createModule("", null);
+ ModuleIdentifier moduleIdentifier2 = new ModuleIdentifier("apsp",
+ "parallel");
+ ObjectName dynON2 = ObjectNameUtil
+ .createReadOnlyModuleON(moduleIdentifier2);
+ AbstractDynamicWrapper dyn = getDynamicWrapper(apspConfigBean,
+ moduleIdentifier2);
+ platformMBeanServer.registerMBean(dyn, dynON2);
+ try {
+ TestingParallelAPSPConfigMXBean proxy = JMX.newMBeanProxy(
+ platformMBeanServer, dynON2,
+ TestingParallelAPSPConfigMXBean.class);
+ ObjectName withTransactionName = ObjectNameUtil
+ .createTransactionModuleON("transaction1", "moduleName", "instanceName");
+ proxy.setThreadPool(withTransactionName);
+ ObjectName withoutTransactionName = ObjectNameUtil
+ .withoutTransactionName(withTransactionName);
+ assertEquals(withoutTransactionName, proxy.getThreadPool());
+ } finally {
+ platformMBeanServer.unregisterMBean(dynON2);
+ }
+ }
+
+ private void setNumberOfThreads(int numberOfThreads) throws Exception {
+ DynamicMBean proxy = JMX.newMBeanProxy(platformMBeanServer,
+ threadPoolDynamicWrapperON, DynamicMBean.class);
+
+ proxy.setAttribute(new Attribute(THREAD_COUNT, numberOfThreads));
+
+ }
+
+ @Test
+ public void testDisablingOfWriteOperations() throws Exception {
+ setNumberOfThreads(newThreadCount);
+ atomicBoolean.set(true);
+ try {
+ setNumberOfThreads(newThreadCount);
+ fail();
+ } catch (IllegalStateException e) {
+ assertEquals("Operation is not allowed now", e.getMessage());
+ } finally {
+ atomicBoolean.set(false);
+ }
+
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.factoriesresolver;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.opendaylight.controller.config.spi.ModuleFactory;
+
+public class HardcodedModuleFactoriesResolver implements
+ ModuleFactoriesResolver {
+ private final List<? extends ModuleFactory> list;
+
+ public HardcodedModuleFactoriesResolver(ModuleFactory... list) {
+ this.list = Arrays.asList(list);
+ }
+
+ @Override
+ public List<? extends ModuleFactory> getAllFactories() {
+ return list;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.factoriesresolver;
+
+public class HierarchicalConfigMBeanFactoriesHolderTest {
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.runtimembean;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+import static org.junit.internal.matchers.StringContains.containsString;
+
+import java.lang.management.ManagementFactory;
+import java.util.Map;
+
+import javax.management.InstanceNotFoundException;
+import javax.management.ObjectName;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.api.runtime.HierarchicalRuntimeBeanRegistration;
+import org.opendaylight.controller.config.manager.impl.AbstractLockedPlatformMBeanServerTest;
+import org.opendaylight.controller.config.manager.impl.jmx.BaseJMXRegistrator;
+import org.opendaylight.controller.config.manager.impl.jmx.HierarchicalRuntimeBeanRegistrationImpl;
+import org.opendaylight.controller.config.manager.impl.jmx.RootRuntimeBeanRegistratorImpl;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+
+public class RuntimeBeanRegistratorImplTest extends
+ AbstractLockedPlatformMBeanServerTest {
+ static final String module1 = "module1";
+ static final String INSTANCE_NAME = "instanceName";
+ String additionalKey = "key";
+ String additionalValue = "value";
+ Map<String, String> additionalProperties = ImmutableMap.of(additionalKey,
+ additionalValue);
+
+ private BaseJMXRegistrator baseJMXRegistrator;
+ private RootRuntimeBeanRegistratorImpl tested;
+ private final ModuleIdentifier moduleIdentifier = new ModuleIdentifier(
+ module1, INSTANCE_NAME);
+
+ @Before
+ public void setUp() {
+ baseJMXRegistrator = new BaseJMXRegistrator(
+ ManagementFactory.getPlatformMBeanServer());
+ tested = baseJMXRegistrator
+ .createRuntimeBeanRegistrator(moduleIdentifier);
+ }
+
+ @After
+ public void tearDown() {
+ tested.close();
+ assertEquals(0, baseJMXRegistrator.getRegisteredObjectNames().size());
+ }
+
+ protected void checkExists(ObjectName on) throws Exception {
+ platformMBeanServer.getMBeanInfo(on);
+ }
+
+ protected void checkNotExists(ObjectName on) throws Exception {
+ try {
+ platformMBeanServer.getMBeanInfo(on);
+ fail();
+ } catch (InstanceNotFoundException e) {
+
+ }
+ }
+
+ @Test
+ public void testRegisterMBeanWithoutAdditionalProperties() throws Exception {
+ createRoot();
+ }
+
+ private HierarchicalRuntimeBeanRegistrationImpl createRoot()
+ throws Exception {
+ HierarchicalRuntimeBeanRegistrationImpl rootRegistration = tested
+ .registerRoot(new TestingRuntimeBean());
+
+ ObjectName expectedON1 = ObjectNameUtil.createRuntimeBeanName(module1,
+ INSTANCE_NAME, Maps.<String, String> newHashMap());
+
+ assertEquals(expectedON1, rootRegistration.getObjectName());
+ checkExists(rootRegistration.getObjectName());
+ return rootRegistration;
+ }
+
+ @Test
+ public void testRegisterMBeanWithAdditionalProperties() throws Exception {
+ HierarchicalRuntimeBeanRegistrationImpl rootRegistration = createRoot();
+ createAdditional(rootRegistration);
+ }
+
+ private HierarchicalRuntimeBeanRegistration createAdditional(
+ HierarchicalRuntimeBeanRegistrationImpl rootRegistration)
+ throws Exception {
+
+ HierarchicalRuntimeBeanRegistrationImpl registration = rootRegistration
+ .register(additionalKey, additionalValue, new TestingRuntimeBean());
+
+ ObjectName expectedON1 = ObjectNameUtil.createRuntimeBeanName(module1,
+ INSTANCE_NAME, additionalProperties);
+
+ assertEquals(expectedON1, registration.getObjectName());
+ checkExists(registration.getObjectName());
+ return registration;
+ }
+
+ @Test
+ public void testCloseRegistration() throws Exception {
+ HierarchicalRuntimeBeanRegistrationImpl rootRegistration = createRoot();
+ rootRegistration.close();
+ checkNotExists(rootRegistration.getObjectName());
+ }
+
+ @Test
+ public void testCloseRegistrator() throws Exception {
+ HierarchicalRuntimeBeanRegistrationImpl rootRegistration = createRoot();
+ HierarchicalRuntimeBeanRegistration childRegistration = createAdditional(rootRegistration);
+ tested.close();
+ checkNotExists(rootRegistration.getObjectName());
+ checkNotExists(childRegistration.getObjectName());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testRegistration_overrideType() throws Exception {
+ HierarchicalRuntimeBeanRegistrationImpl rootRegistration = createRoot();
+ rootRegistration.register("type", "xxx", new TestingRuntimeBean());
+ }
+
+ @Test
+ public void testRegistrationException() throws Exception {
+ HierarchicalRuntimeBeanRegistrationImpl rootRegistration = createRoot();
+ try {
+ createRoot();
+ fail();
+ } catch (IllegalStateException e) {
+ assertThat(e.getMessage(), containsString(rootRegistration
+ .getObjectName().toString()));
+ assertThat(e.getMessage(),
+ containsString("Could not register runtime bean"));
+ assertThat(e.getMessage(),
+ containsString(moduleIdentifier.toString()));
+ }
+ }
+
+ @Test
+ public void testIgnoringExceptionInClose() throws Exception {
+ HierarchicalRuntimeBeanRegistrationImpl rootRegistration = createRoot();
+ platformMBeanServer.unregisterMBean(rootRegistration.getObjectName());
+ rootRegistration.close();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.runtimembean;
+
+public class TestingRuntimeBean implements TestingRuntimeBeanMXBean {
+
+ @Override
+ public int getStat() {
+ return 0;
+ }
+
+ @Override
+ public void setStat() {
+
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.runtimembean;
+
+import org.opendaylight.controller.config.api.runtime.RuntimeBean;
+
+public interface TestingRuntimeBeanMXBean extends RuntimeBean {
+ int getStat();
+
+ void setStat();
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.util;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Set;
+
+import javax.management.MXBean;
+
+import org.junit.Test;
+import org.opendaylight.controller.config.spi.Module;
+
+import com.google.common.collect.Sets;
+
+public class InterfacesHelperTest {
+
+ interface SuperA {
+
+ }
+
+ interface SuperBMXBean {
+
+ }
+
+ interface SuperC extends SuperA, SuperBMXBean {
+
+ }
+
+ class SuperClass implements SuperC {
+
+ }
+
+ @MXBean
+ interface SubA {
+
+ }
+
+ abstract class SubClass extends SuperClass implements SubA, Module {
+
+ }
+
+ @Test
+ public void testGetAllInterfaces() {
+ Set<Class<?>> expected = Sets.<Class<?>> newHashSet(SuperA.class, SuperBMXBean.class, SuperC.class,
+ SubA.class, Module.class, org.opendaylight.protocol.concepts.NamedObject.class);
+ assertEquals(expected,
+ InterfacesHelper.getAllInterfaces(SubClass.class));
+ }
+
+ @Test
+ public void testGetMXInterfaces() {
+ Set<Class<?>> expected = Sets.<Class<?>> newHashSet(SuperBMXBean.class, SubA.class);
+ assertEquals(expected, InterfacesHelper.getMXInterfaces(SubClass.class));
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.impl.util;
+
+import java.util.Set;
+
+import javax.management.ObjectName;
+
+import org.junit.After;
+import org.junit.Before;
+import org.opendaylight.controller.config.manager.impl.AbstractLockedPlatformMBeanServerTest;
+
+import com.google.common.base.Throwables;
+import com.google.common.collect.Sets;
+
+public class ObjectNameUtilTest extends AbstractLockedPlatformMBeanServerTest {
+ private Set<ObjectName> unregisterONs;
+
+ @Before
+ public void initUnregisterList() {
+ unregisterONs = Sets.newHashSet();
+ }
+
+ @After
+ public void unregisterONs() {
+ Exception lastException = null;
+ for (ObjectName on : unregisterONs) {
+ try {
+ platformMBeanServer.unregisterMBean(on);
+ } catch (Exception e) {
+ lastException = e;
+ }
+ }
+ if (lastException != null) {
+ Throwables.propagate(lastException);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.testingservices.parallelapsp;
+
+public interface TestingAPSP {
+
+ int getMaxNumberOfThreads();
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.testingservices.parallelapsp;
+
+import javax.management.ObjectName;
+
+import org.opendaylight.controller.config.api.annotations.ServiceInterfaceAnnotation;
+
+@ServiceInterfaceAnnotation(value = TestingParallelAPSPConfigMXBean.NAME, osgiRegistrationType = TestingAPSP.class)
+public interface TestingParallelAPSPConfigMXBean {
+
+ static final String NAME = "apsp";
+
+ ObjectName getThreadPool();
+
+ void setThreadPool(ObjectName threadPoolName);
+
+ String getSomeParam();
+
+ void setSomeParam(String s);
+
+ // for reporting. this should be moved to runtime jmx bean
+ Integer getMaxNumberOfThreads();
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.testingservices.parallelapsp;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+import javax.annotation.concurrent.NotThreadSafe;
+
+import org.opendaylight.controller.config.manager.testingservices.threadpool.TestingThreadPoolIfc;
+
+import com.google.common.base.Strings;
+
+@NotThreadSafe
+public class TestingParallelAPSPImpl implements TestingAPSP, Closeable {
+ public static final int MINIMAL_NUMBER_OF_THREADS = 10;
+ private TestingThreadPoolIfc threadPool;
+ private String someParam;
+
+ public TestingParallelAPSPImpl(TestingThreadPoolIfc threadPool,
+ String someParam) {
+ checkArgument(
+ threadPool.getMaxNumberOfThreads() >= MINIMAL_NUMBER_OF_THREADS,
+ "Parameter 'threadPool' has not enough threads");
+ checkArgument(Strings.isNullOrEmpty(someParam) == false,
+ "Parameter 'someParam' is blank");
+ this.threadPool = threadPool;
+ this.someParam = someParam;
+ }
+
+ @Override
+ public int getMaxNumberOfThreads() {
+ return threadPool.getMaxNumberOfThreads();
+ }
+
+ @Override
+ public void close() throws IOException {
+
+ }
+
+ TestingThreadPoolIfc getThreadPool() {
+ return threadPool;
+ }
+
+ void setSomeParam(String s) {
+ checkArgument(Strings.isNullOrEmpty(someParam) == false,
+ "Parameter 'someParam' is blank");
+ this.someParam = s;
+ }
+
+ public String getSomeParam() {
+ return someParam;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.testingservices.parallelapsp;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import java.io.Closeable;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.NotThreadSafe;
+import javax.management.ObjectName;
+
+import org.opendaylight.controller.config.api.DependencyResolver;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.annotations.RequireInterface;
+import org.opendaylight.controller.config.manager.testingservices.seviceinterface.TestingThreadPoolServiceInterface;
+import org.opendaylight.controller.config.manager.testingservices.threadpool.TestingThreadPoolIfc;
+import org.opendaylight.controller.config.spi.Module;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Strings;
+
+/**
+ * Represents service that has dependency to thread pool.
+ */
+@NotThreadSafe
+public class TestingParallelAPSPModule implements Module,
+ TestingParallelAPSPConfigMXBean {
+ private static final Logger logger = LoggerFactory
+ .getLogger(TestingParallelAPSPModule.class);
+
+ private final DependencyResolver dependencyResolver;
+ private final AutoCloseable oldCloseable;
+ private final TestingParallelAPSPImpl oldInstance;
+ private final ModuleIdentifier name;
+ private ObjectName threadPoolON;
+ private TestingParallelAPSPImpl instance;
+ private String someParam;
+
+ public TestingParallelAPSPModule(ModuleIdentifier name,
+ DependencyResolver dependencyResolver,
+ @Nullable AutoCloseable oldCloseable,
+ @Nullable TestingParallelAPSPImpl oldInstance) {
+ this.name = name;
+ this.dependencyResolver = dependencyResolver;
+ this.oldCloseable = oldCloseable;
+ this.oldInstance = oldInstance;
+ }
+
+ @Override
+ public ModuleIdentifier getName() {
+ return name;
+ }
+
+ @Override
+ public ObjectName getThreadPool() {
+ return threadPoolON;
+ }
+
+ @RequireInterface(TestingThreadPoolServiceInterface.class)
+ @Override
+ public void setThreadPool(ObjectName threadPoolName) {
+ this.threadPoolON = threadPoolName;
+ }
+
+ @Override
+ public String getSomeParam() {
+ return someParam;
+ }
+
+ @Override
+ public void setSomeParam(String someParam) {
+ this.someParam = someParam;
+ }
+
+ @Override
+ public Integer getMaxNumberOfThreads() {
+ if (instance == null)
+ return null;
+ return instance.getMaxNumberOfThreads();
+ }
+
+ @Override
+ public void validate() {
+ checkNotNull(threadPoolON, "Parameter 'threadPool' must be set");
+ dependencyResolver.validateDependency(
+ TestingThreadPoolServiceInterface.class, threadPoolON,
+ "threadPoolON");
+
+ checkState(Strings.isNullOrEmpty(someParam) == false,
+ "Parameter 'SomeParam' is blank");
+ // check that calling resolveInstance fails
+ try {
+ dependencyResolver.resolveInstance(TestingThreadPoolIfc.class,
+ threadPoolON);
+ throw new RuntimeException("fail");
+ } catch (IllegalStateException e) {
+ checkState("Commit was not triggered".equals(e.getMessage()),
+ e.getMessage());
+ }
+ }
+
+ @Override
+ public Closeable getInstance() {
+ if (instance == null) {
+ TestingThreadPoolIfc threadPoolInstance = dependencyResolver
+ .resolveInstance(TestingThreadPoolIfc.class, threadPoolON);
+
+ if (oldInstance != null) {
+ // changing thread pool is not supported
+ boolean reuse = threadPoolInstance.equals(oldInstance
+ .getThreadPool());
+ if (reuse) {
+ logger.debug("Reusing old instance");
+ instance = oldInstance;
+ instance.setSomeParam(someParam);
+ }
+ }
+ if (instance == null) {
+ logger.debug("Creating new instance");
+ if (oldCloseable != null) {
+ try {
+ oldCloseable.close();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ instance = new TestingParallelAPSPImpl(threadPoolInstance,
+ someParam);
+ }
+ }
+ return instance;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.testingservices.parallelapsp;
+
+import javax.annotation.concurrent.ThreadSafe;
+import javax.management.ObjectName;
+
+import org.opendaylight.controller.config.api.DependencyResolver;
+import org.opendaylight.controller.config.api.DynamicMBeanWithInstance;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.annotations.AbstractServiceInterface;
+import org.opendaylight.controller.config.spi.ModuleFactory;
+
+@ThreadSafe
+public class TestingParallelAPSPModuleFactory implements ModuleFactory {
+
+ public static final String NAME = "parallel";
+
+ @Override
+ public String getImplementationName() {
+ return NAME;
+ }
+
+ @Override
+ public TestingParallelAPSPModule createModule(String instanceName,
+ DependencyResolver dependencyResolver) {
+ return new TestingParallelAPSPModule(new ModuleIdentifier(NAME,
+ instanceName), dependencyResolver, null, null);
+ }
+
+ @Override
+ public TestingParallelAPSPModule createModule(String instanceName,
+ DependencyResolver dependencyResolver, DynamicMBeanWithInstance old)
+ throws Exception {
+ TestingParallelAPSPImpl oldInstance;
+ try {
+ oldInstance = (TestingParallelAPSPImpl) old.getInstance();
+ } catch (ClassCastException e) {
+ oldInstance = null;
+ }
+ TestingParallelAPSPModule result = new TestingParallelAPSPModule(
+ new ModuleIdentifier(NAME, instanceName), dependencyResolver,
+ old.getInstance(), oldInstance);
+ // copy attributes
+ String someParam = (String) old.getAttribute("SomeParam");
+ result.setSomeParam(someParam);
+ ObjectName threadPool = (ObjectName) old.getAttribute("ThreadPool");
+ result.setThreadPool(threadPool);
+ return result;
+ }
+
+ @Override
+ public boolean isModuleImplementingServiceInterface(
+ Class<? extends AbstractServiceInterface> serviceInterface) {
+ return false;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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
+ */
+/**
+ * Creates simple bean that has dependency on
+ * {@link org.opendaylight.controller.config.manager.testingservices.threadpool.TestingThreadPoolIfc}.
+ */
+package org.opendaylight.controller.config.manager.testingservices.parallelapsp;
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.testingservices.parallelapsp.test;
+
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.ObjectName;
+
+import org.opendaylight.controller.config.manager.impl.AbstractConfigWithJolokiaTest;
+import org.opendaylight.controller.config.manager.testingservices.parallelapsp.TestingParallelAPSPConfigMXBean;
+import org.opendaylight.controller.config.manager.testingservices.parallelapsp.TestingParallelAPSPModuleFactory;
+import org.opendaylight.controller.config.manager.testingservices.threadpool.TestingFixedThreadPoolConfigMXBean;
+import org.opendaylight.controller.config.util.ConfigTransactionJMXClient;
+
+abstract class AbstractParallelAPSPTest extends AbstractConfigWithJolokiaTest {
+ protected final String fixed1 = "fixed1";
+ protected final String apsp1 = "apsp-parallel";
+
+ abstract String getThreadPoolImplementationName();
+
+ protected ObjectName createParallelAPSP(
+ ConfigTransactionJMXClient transaction, ObjectName threadPoolON)
+ throws InstanceAlreadyExistsException {
+ ObjectName apspName = transaction.createModule(
+ TestingParallelAPSPModuleFactory.NAME, apsp1);
+ TestingParallelAPSPConfigMXBean parallelAPSPConfigProxy = transaction
+ .newMXBeanProxy(apspName, TestingParallelAPSPConfigMXBean.class);
+ parallelAPSPConfigProxy.setSomeParam("ahoj");
+ parallelAPSPConfigProxy.setThreadPool(threadPoolON);
+ return apspName;
+ }
+
+ protected ObjectName createFixed1(ConfigTransactionJMXClient transaction,
+ int numberOfThreads) throws InstanceAlreadyExistsException {
+ ObjectName name = transaction.createModule(
+ getThreadPoolImplementationName(), fixed1);
+
+ TestingFixedThreadPoolConfigMXBean fixedConfigProxy = transaction
+ .newMXBeanProxy(name, TestingFixedThreadPoolConfigMXBean.class);
+ fixedConfigProxy.setThreadCount(numberOfThreads);
+
+ return name;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.testingservices.parallelapsp.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.internal.matchers.StringContains.containsString;
+
+import java.util.Map;
+
+import javax.management.ObjectName;
+
+import org.json.simple.JSONObject;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.config.api.ValidationException;
+import org.opendaylight.controller.config.api.ValidationException.ExceptionMessageWithStackTrace;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.manager.impl.factoriesresolver.HardcodedModuleFactoriesResolver;
+import org.opendaylight.controller.config.manager.testingservices.parallelapsp.TestingParallelAPSPConfigMXBean;
+import org.opendaylight.controller.config.manager.testingservices.parallelapsp.TestingParallelAPSPImpl;
+import org.opendaylight.controller.config.manager.testingservices.parallelapsp.TestingParallelAPSPModuleFactory;
+import org.opendaylight.controller.config.manager.testingservices.threadpool.TestingFixedThreadPool;
+import org.opendaylight.controller.config.manager.testingservices.threadpool.TestingFixedThreadPoolConfigMXBean;
+import org.opendaylight.controller.config.manager.testingservices.threadpool.TestingFixedThreadPoolModuleFactory;
+import org.opendaylight.controller.config.util.ConfigTransactionJMXClient;
+import org.opendaylight.controller.config.util.jolokia.ConfigTransactionJolokiaClient;
+
+public class DependentWiringTest extends AbstractParallelAPSPTest {
+ private final String fixed1 = "fixed1";
+ private final String apsp1 = "apsp-parallel";
+
+ @Before
+ public void setUp() {
+ super.initConfigTransactionManagerImpl(new HardcodedModuleFactoriesResolver(
+ new TestingFixedThreadPoolModuleFactory(),
+ new TestingParallelAPSPModuleFactory()));
+ }
+
+ @After
+ public void tearDown() {
+ TestingFixedThreadPool.cleanUp();
+ }
+
+ @Override
+ String getThreadPoolImplementationName() {
+ return TestingFixedThreadPoolModuleFactory.NAME;
+ }
+
+ @Test
+ public void testDependencies() throws Exception {
+ ObjectName apspON;
+ {
+ ConfigTransactionJMXClient transaction = configRegistryClient
+ .createTransaction();
+ // create fixed1
+ ObjectName threadPoolTransactionON = createFixed1(transaction,
+ TestingParallelAPSPImpl.MINIMAL_NUMBER_OF_THREADS);
+ // create apsp-parallel
+ ObjectName apspNameTransactionON = createParallelAPSP(transaction,
+ threadPoolTransactionON);
+ TestingParallelAPSPConfigMXBean parallelAPSPConfigProxy = transaction
+ .newMXBeanProxy(apspNameTransactionON, TestingParallelAPSPConfigMXBean.class);
+ parallelAPSPConfigProxy.setSomeParam("");// trigger validation
+ // failure
+ try {
+ transaction.validateConfig();
+ fail();
+ } catch (ValidationException e) {
+ for (Map.Entry<String, Map<String, ExceptionMessageWithStackTrace>> exception : e
+ .getFailedValidations().entrySet()) {
+ for (Map.Entry<String, ExceptionMessageWithStackTrace> entry : exception
+ .getValue().entrySet()) {
+ assertThat(
+ entry.getValue().getMessage(),
+ containsString("Parameter 'SomeParam' is blank"));
+ }
+ }
+ }
+
+ // try committing (validation fails)
+ try {
+ transaction.commit();
+ fail();
+ } catch (ValidationException e) {
+ for (Map.Entry<String, Map<String, ExceptionMessageWithStackTrace>> exception : e
+ .getFailedValidations().entrySet()) {
+ for (Map.Entry<String, ExceptionMessageWithStackTrace> entry : exception
+ .getValue().entrySet()) {
+ String err = entry.getValue().getMessage();
+ assertTrue("Unexpected error message: " + err,
+ err.contains("Parameter 'SomeParam' is blank"));
+ }
+ }
+ }
+
+ parallelAPSPConfigProxy.setSomeParam("abc");// fix validation
+ // failure
+ transaction.commit();
+ apspON = ObjectNameUtil
+ .withoutTransactionName(apspNameTransactionON);
+ }
+
+ // test reported apsp number of threads
+ TestingParallelAPSPConfigMXBean parallelAPSPRuntimeProxy = configRegistryClient
+ .newMBeanProxy(apspON, TestingParallelAPSPConfigMXBean.class);
+ assertEquals(
+ (Integer) TestingParallelAPSPImpl.MINIMAL_NUMBER_OF_THREADS,
+ parallelAPSPRuntimeProxy.getMaxNumberOfThreads());
+
+ // next transaction - recreate new thread pool
+ int newNumberOfThreads = TestingParallelAPSPImpl.MINIMAL_NUMBER_OF_THREADS * 2;
+ {
+ // start new transaction
+ ConfigTransactionJMXClient transaction = configRegistryClient
+ .createTransaction();
+ ObjectName threadPoolNames_newTx = transaction.lookupConfigBean(
+ getThreadPoolImplementationName(), fixed1);
+ TestingFixedThreadPoolConfigMXBean fixedConfigTransactionProxy = transaction
+ .newMXBeanProxy(threadPoolNames_newTx, TestingFixedThreadPoolConfigMXBean.class);
+ fixedConfigTransactionProxy.setThreadCount(newNumberOfThreads);
+
+ transaction.commit();
+ }
+ // new reference should be copied to apsp-parallel
+ assertEquals((Integer) newNumberOfThreads,
+ parallelAPSPRuntimeProxy.getMaxNumberOfThreads());
+
+ }
+
+ @Test
+ public void testUsingJolokia() throws Exception {
+
+ ConfigTransactionJolokiaClient transactionClient = configRegistryJolokiaClient
+ .createTransaction();
+ // fixed1
+ ObjectName fixed1ON = transactionClient.createModule(
+ getThreadPoolImplementationName(), fixed1);
+ transactionClient.setAttribute(fixed1ON, "ThreadCount",
+ TestingParallelAPSPImpl.MINIMAL_NUMBER_OF_THREADS);
+
+ // apsp-parallel with syntetic attrib
+ String threadPoolString = "ThreadPool";
+ ObjectName apsp1ON = transactionClient.createModule(
+ TestingParallelAPSPModuleFactory.NAME, apsp1);
+ transactionClient.setAttribute(apsp1ON, threadPoolString, fixed1ON);
+ // check
+ assertEquals(ObjectNameUtil.withoutTransactionName(fixed1ON),
+ transactionClient.getAttributeON(apsp1ON, threadPoolString));
+ transactionClient.setAttribute(apsp1ON, "SomeParam", "ahoj");
+
+ // commit
+ transactionClient.commit();
+ // check thread pool
+ assertEquals(1, TestingFixedThreadPool.allExecutors.size());
+ // check platform MBeanServer
+ ObjectName apspReadOnlyON = ObjectNameUtil
+ .withoutTransactionName(apsp1ON);
+ JSONObject threadPoolONJson = (JSONObject) configRegistryJolokiaClient
+ .getAttribute(apspReadOnlyON, threadPoolString);
+ ObjectName fixed1ReadOnlyON = ObjectNameUtil
+ .withoutTransactionName(fixed1ON);
+ assertEquals(fixed1ReadOnlyON, ObjectNameUtil.createON(threadPoolONJson
+ .get("objectName").toString()));
+ assertEquals(fixed1ReadOnlyON,
+ configRegistryJolokiaClient.getAttributeON(apspReadOnlyON,
+ threadPoolString));
+
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.testingservices.parallelapsp.test;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.concurrent.Executor;
+
+import javax.management.ObjectName;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.config.api.DynamicMBeanWithInstance;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.manager.impl.ClassBasedModuleFactory;
+import org.opendaylight.controller.config.manager.impl.factoriesresolver.HardcodedModuleFactoriesResolver;
+import org.opendaylight.controller.config.manager.testingservices.parallelapsp.TestingParallelAPSPImpl;
+import org.opendaylight.controller.config.manager.testingservices.parallelapsp.TestingParallelAPSPModuleFactory;
+import org.opendaylight.controller.config.manager.testingservices.seviceinterface.TestingThreadPoolServiceInterface;
+import org.opendaylight.controller.config.manager.testingservices.threadpool.TestingThreadPoolConfigMXBean;
+import org.opendaylight.controller.config.manager.testingservices.threadpool.TestingThreadPoolIfc;
+import org.opendaylight.controller.config.spi.Module;
+import org.opendaylight.controller.config.util.ConfigTransactionJMXClient;
+
+public class MockedDependenciesTest extends AbstractParallelAPSPTest {
+ private final String threadPoolImplementationName = "mockedthreadpool";
+
+ @Before
+ public void setUp() {
+
+ ClassBasedModuleFactory mockedThreadPoolConfigFactory = new ClassBasedModuleFactory(
+ threadPoolImplementationName, MockedThreadPoolModule.class);
+
+ super.initConfigTransactionManagerImpl(new HardcodedModuleFactoriesResolver(
+ new TestingParallelAPSPModuleFactory(),
+ mockedThreadPoolConfigFactory));
+ }
+
+ public static interface MockedTestingThreadPoolConfigMXBean extends
+ TestingThreadPoolConfigMXBean {
+ public void setThreadCount(int threadCount);
+ }
+
+ public static class MockedThreadPoolModule implements Module,
+ MockedTestingThreadPoolConfigMXBean,
+ TestingThreadPoolServiceInterface {
+ int threadCount;
+
+ public MockedThreadPoolModule() {
+ }
+
+ public MockedThreadPoolModule(
+ DynamicMBeanWithInstance dynamicMBeanWithInstance) {
+ // no reconfiguration / reuse is supported
+ }
+
+ @Override
+ public ModuleIdentifier getName() {
+ return new ModuleIdentifier("a", "b");
+ }
+
+ @Override
+ public int getThreadCount() {
+ return threadCount;
+ }
+
+ @Override
+ public void setThreadCount(int threadCount) {
+ this.threadCount = threadCount;
+ }
+
+ @Override
+ public void validate() {
+
+ }
+
+ @Override
+ public Closeable getInstance() {
+ return new MockedThreadPool(threadCount);
+ }
+ }
+
+ public static class MockedThreadPool implements TestingThreadPoolIfc,
+ Closeable {
+ private final int threadCount;
+
+ public MockedThreadPool(int threadCount) {
+ this.threadCount = threadCount;
+ }
+
+ @Override
+ public Executor getExecutor() {
+ return null;
+ }
+
+ @Override
+ public int getMaxNumberOfThreads() {
+ return threadCount;
+ }
+
+ @Override
+ public void close() throws IOException {
+
+ }
+ }
+
+ @Override
+ String getThreadPoolImplementationName() {
+ return threadPoolImplementationName;
+ }
+
+ @Test
+ public void testDependencies() throws Exception {
+ ConfigTransactionJMXClient transaction = configRegistryClient
+ .createTransaction();
+ // create fixed1
+ ObjectName threadPoolTransactionON = createFixed1(transaction,
+ TestingParallelAPSPImpl.MINIMAL_NUMBER_OF_THREADS);
+ // create apsp-parallel
+ createParallelAPSP(transaction, threadPoolTransactionON);
+
+ transaction.commit();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.testingservices.scheduledthreadpool;
+
+import org.opendaylight.controller.config.manager.testingservices.threadpool.TestingThreadPoolConfigMXBean;
+
+public interface TestingScheduledThreadPoolConfigBeanMXBean extends
+ TestingThreadPoolConfigMXBean {
+
+ public boolean isRecreate();
+
+ public void setRecreate(boolean recreate);
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.testingservices.scheduledthreadpool;
+
+import java.util.concurrent.ScheduledExecutorService;
+
+public interface TestingScheduledThreadPoolIfc {
+
+ ScheduledExecutorService getScheduledExecutor();
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.testingservices.scheduledthreadpool;
+
+import java.io.Closeable;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+
+import org.opendaylight.controller.config.api.runtime.HierarchicalRuntimeBeanRegistration;
+import org.opendaylight.controller.config.api.runtime.RootRuntimeBeanRegistrator;
+import org.opendaylight.controller.config.manager.testingservices.scheduledthreadpool.runtimebeans
+ .TestingScheduledRuntimeBean;
+import org.opendaylight.controller.config.manager.testingservices.threadpool.TestingThreadPoolIfc;
+
+import com.google.common.collect.Lists;
+
+public class TestingScheduledThreadPoolImpl implements TestingThreadPoolIfc,
+ TestingScheduledThreadPoolIfc, Closeable {
+ private static volatile int numberOfCloseMethodCalls = 0;
+ private final ScheduledThreadPoolExecutor executor;
+ private final RootRuntimeBeanRegistrator runtimeBeanRegistrator;
+
+ public static final List<ScheduledThreadPoolExecutor> allExecutors = Lists
+ .newLinkedList();
+
+ public TestingScheduledThreadPoolImpl(
+ RootRuntimeBeanRegistrator runtimeBeanRegistrator, int corePoolSize) {
+ this.runtimeBeanRegistrator = runtimeBeanRegistrator;
+ executor = new ScheduledThreadPoolExecutor(corePoolSize);
+ allExecutors.add(executor);
+ HierarchicalRuntimeBeanRegistration hierarchicalRuntimeBeanRegistration = runtimeBeanRegistrator
+ .registerRoot(new TestingScheduledRuntimeBean());
+ hierarchicalRuntimeBeanRegistration.register("a", "b",
+ new TestingScheduledRuntimeBean());
+ }
+
+ @Override
+ public void close() {
+ numberOfCloseMethodCalls++;
+ runtimeBeanRegistrator.close();
+ executor.shutdown();
+ }
+
+ @Override
+ public ScheduledExecutorService getScheduledExecutor() {
+ return executor;
+ }
+
+ @Override
+ public Executor getExecutor() {
+ return executor;
+ }
+
+ @Override
+ public int getMaxNumberOfThreads() {
+ return executor.getCorePoolSize();
+ }
+
+ public static void cleanUp() {
+ for (ScheduledThreadPoolExecutor executor : allExecutors) {
+ executor.shutdown();
+ }
+ allExecutors.clear();
+ numberOfCloseMethodCalls = 0;
+ }
+
+ public static int getNumberOfCloseMethodCalls() {
+ return numberOfCloseMethodCalls;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.testingservices.scheduledthreadpool;
+
+import static com.google.common.base.Preconditions.checkState;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.io.Closeable;
+
+import javax.annotation.Nullable;
+
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.RuntimeBeanRegistratorAwareModule;
+import org.opendaylight.controller.config.api.runtime.RootRuntimeBeanRegistrator;
+import org.opendaylight.controller.config.manager.testingservices.seviceinterface.TestingScheduledThreadPoolServiceInterface;
+import org.opendaylight.controller.config.spi.Module;
+
+/**
+ * This class has two exported interfaces and two runtime beans. Recreation is
+ * triggered by setting Recreate attribute to true.
+ */
+public class TestingScheduledThreadPoolModule implements Module,
+ TestingScheduledThreadPoolConfigBeanMXBean,
+ RuntimeBeanRegistratorAwareModule,
+ TestingScheduledThreadPoolServiceInterface {
+
+ private final ModuleIdentifier name;
+ @Nullable
+ private final AutoCloseable oldCloseable;
+ @Nullable
+ private final TestingScheduledThreadPoolImpl oldInstance;
+
+ private final int threadCount = 10;
+ private TestingScheduledThreadPoolImpl instance;
+ private RootRuntimeBeanRegistrator runtimeBeanRegistrator;
+ private boolean recreate;
+
+ public TestingScheduledThreadPoolModule(ModuleIdentifier name,
+ @Nullable AutoCloseable oldCloseable,
+ @Nullable TestingScheduledThreadPoolImpl oldInstance) {
+ this.name = name;
+ this.oldCloseable = oldCloseable;
+ this.oldInstance = oldInstance;
+ }
+
+ @Override
+ public ModuleIdentifier getName() {
+ return name;
+ }
+
+ @Override
+ public void setRuntimeBeanRegistrator(
+ RootRuntimeBeanRegistrator runtimeBeanRegistrator) {
+ this.runtimeBeanRegistrator = runtimeBeanRegistrator;
+ }
+
+ @Override
+ public void validate() {
+ assertNull(runtimeBeanRegistrator);
+ // check thread count
+ checkState(threadCount > 0,
+ "Parameter 'ThreadCount' must be greater than 0");
+ }
+
+ @Override
+ public int getThreadCount() {
+ return threadCount;
+ }
+
+ @Override
+ public Closeable getInstance() {
+ assertNotNull(runtimeBeanRegistrator);
+ if (instance == null) {
+ if (oldInstance != null && recreate == false) {
+ // reuse old instance
+ instance = oldInstance;
+ }
+ if (instance == null) {
+ if (oldCloseable != null) {
+ try {
+ oldCloseable.close();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ // close old threadpool and esp. unregister runtime beans
+ instance = new TestingScheduledThreadPoolImpl(
+ runtimeBeanRegistrator, threadCount);
+ }
+ }
+ return instance;
+ }
+
+ // getters and setters
+ @Override
+ public boolean isRecreate() {
+ return recreate;
+ }
+
+ @Override
+ public void setRecreate(boolean recreate) {
+ this.recreate = recreate;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.testingservices.scheduledthreadpool;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.opendaylight.controller.config.api.DependencyResolver;
+import org.opendaylight.controller.config.api.DynamicMBeanWithInstance;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.annotations.AbstractServiceInterface;
+import org.opendaylight.controller.config.manager.testingservices.seviceinterface
+ .TestingScheduledThreadPoolServiceInterface;
+import org.opendaylight.controller.config.manager.testingservices.seviceinterface.TestingThreadPoolServiceInterface;
+import org.opendaylight.controller.config.spi.Module;
+import org.opendaylight.controller.config.spi.ModuleFactory;
+
+public class TestingScheduledThreadPoolModuleFactory implements ModuleFactory {
+ public static final String NAME = "scheduled";
+
+ private static List<Class<? extends TestingThreadPoolServiceInterface>> ifc = Arrays
+ .asList(TestingScheduledThreadPoolServiceInterface.class, TestingThreadPoolServiceInterface.class);
+
+ @Override
+ public boolean isModuleImplementingServiceInterface(
+ Class<? extends AbstractServiceInterface> serviceInterface) {
+ return ifc.contains(serviceInterface);
+ }
+
+ @Override
+ public String getImplementationName() {
+ return NAME;
+ }
+
+ @Override
+ public Module createModule(String instanceName,
+ DependencyResolver dependencyResolver) {
+ return new TestingScheduledThreadPoolModule(new ModuleIdentifier(NAME,
+ instanceName), null, null);
+ }
+
+ @Override
+ public Module createModule(String instanceName,
+ DependencyResolver dependencyResolver, DynamicMBeanWithInstance old)
+ throws Exception {
+ TestingScheduledThreadPoolImpl oldInstance;
+ try {
+ oldInstance = (TestingScheduledThreadPoolImpl) old.getInstance();
+ } catch (ClassCastException e) {// happens after OSGi update
+ oldInstance = null;
+ }
+
+ TestingScheduledThreadPoolModule configBean = new TestingScheduledThreadPoolModule(
+ new ModuleIdentifier(NAME, instanceName), old.getInstance(),
+ oldInstance);
+ // copy attributes
+ configBean.setRecreate((Boolean) old.getAttribute("Recreate"));
+ return configBean;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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
+ */
+/**
+ * Tests config bean that exports two independent interfaces - {@link org.opendaylight.controller.config.manager.testingservices.scheduledthreadpool.TestingScheduledThreadPoolIfc} and
+ * {@link org.opendaylight.controller.config.manager.testingservices.threadpool.TestingThreadPoolIfc}.
+ */
+package org.opendaylight.controller.config.manager.testingservices.scheduledthreadpool;
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.testingservices.scheduledthreadpool.runtimebeans;
+
+public class TestingScheduledRuntimeBean implements
+ TestingScheduledRuntimeMXBean {
+
+ public TestingScheduledRuntimeBean() {
+ }
+
+ @Override
+ public int getActualNumberOfThreads() {
+ return 0;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.testingservices.scheduledthreadpool.runtimebeans;
+
+import org.opendaylight.controller.config.api.runtime.RuntimeBean;
+
+public interface TestingScheduledRuntimeMXBean extends RuntimeBean {
+
+ int getActualNumberOfThreads();
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.testingservices.scheduledthreadpool.test;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.After;
+import org.junit.Before;
+import org.opendaylight.controller.config.manager.impl.AbstractConfigTest;
+import org.opendaylight.controller.config.manager.impl.factoriesresolver.HardcodedModuleFactoriesResolver;
+import org.opendaylight.controller.config.manager.testingservices.parallelapsp.TestingParallelAPSPModuleFactory;
+import org.opendaylight.controller.config.manager.testingservices.scheduledthreadpool.TestingScheduledThreadPoolImpl;
+import org.opendaylight.controller.config.manager.testingservices.scheduledthreadpool
+ .TestingScheduledThreadPoolModuleFactory;
+import org.opendaylight.controller.config.manager.testingservices.threadpool.TestingFixedThreadPoolModuleFactory;
+
+public abstract class AbstractScheduledTest extends AbstractConfigTest {
+ protected static final String scheduled1 = "scheduled1";
+
+ @Before
+ public final void setUp() {
+ assertEquals(0,
+ TestingScheduledThreadPoolImpl.getNumberOfCloseMethodCalls());
+ super.initConfigTransactionManagerImpl(new HardcodedModuleFactoriesResolver(
+ new TestingScheduledThreadPoolModuleFactory(),
+ new TestingFixedThreadPoolModuleFactory(),
+ new TestingParallelAPSPModuleFactory()));
+ }
+
+ @After
+ public final void cleanUp() throws Exception {
+ destroyAllConfigBeans();
+ TestingScheduledThreadPoolImpl.cleanUp();
+ assertEquals(0,
+ TestingScheduledThreadPoolImpl.getNumberOfCloseMethodCalls());
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.testingservices.scheduledthreadpool.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.util.Collections;
+import java.util.List;
+
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.InstanceNotFoundException;
+import javax.management.ObjectName;
+
+import org.junit.Test;
+import org.opendaylight.controller.config.api.ConflictingVersionException;
+import org.opendaylight.controller.config.api.ValidationException;
+import org.opendaylight.controller.config.api.jmx.CommitStatus;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.manager.testingservices.scheduledthreadpool
+ .TestingScheduledThreadPoolConfigBeanMXBean;
+import org.opendaylight.controller.config.manager.testingservices.scheduledthreadpool
+ .TestingScheduledThreadPoolModuleFactory;
+import org.opendaylight.controller.config.util.ConfigTransactionJMXClient;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+/**
+ * TestingScheduledThreadPool exports 2 interfaces: <br>
+ * {@link org.opendaylight.controller.config.manager.testingservices.scheduledthreadpool.TestingScheduledThreadPoolModuleFactory#NAME}
+ * ,<br>
+ * {@link org.opendaylight.controller.config.manager.testingservices.threadpool.TestingFixedThreadPoolModuleFactory#NAME}
+ * <br>
+ * <br>
+ * It also exports 2 runtime beans, one default and one with additional
+ * properties of {'a':'b'}.
+ */
+public class RuntimeBeanTest extends AbstractScheduledTest {
+
+ ObjectName ifc1runtimeON1 = ObjectNameUtil.createRuntimeBeanName(
+ TestingScheduledThreadPoolModuleFactory.NAME, scheduled1,
+ Maps.<String, String> newHashMap());
+ // additional runtime bean
+ ObjectName ifc1runtimeON2 = ObjectNameUtil.createRuntimeBeanName(
+ TestingScheduledThreadPoolModuleFactory.NAME, scheduled1,
+ ImmutableMap.of("a", "b"));
+
+ List<ObjectName> allObjectNames = Lists.newArrayList(ifc1runtimeON1,
+ ifc1runtimeON2);
+
+ private ObjectName createScheduled() throws InstanceAlreadyExistsException,
+ ConflictingVersionException, ValidationException {
+ ConfigTransactionJMXClient transaction = configRegistryClient
+ .createTransaction();
+
+ // create using TestingThreadPoolIfc:
+ ObjectName createdConfigBean = transaction.createModule(
+ TestingScheduledThreadPoolModuleFactory.NAME, scheduled1);
+ // commit
+ transaction.commit();
+ return createdConfigBean;
+ }
+
+ @Test
+ public void testCreateScheduled() throws Exception {
+ createScheduled();
+ checkRuntimeBeans();
+ }
+
+ private void checkRuntimeBeans() throws Exception {
+ // check runtime bean - on 2 places
+ for (ObjectName on : allObjectNames)
+ checkRuntimeBean(on);
+ }
+
+ private void checkRuntimeBean(ObjectName on) throws Exception {
+ assertEquals(0,
+ platformMBeanServer.getAttribute(on, "ActualNumberOfThreads"));
+ }
+
+ private void checkRuntimeBeanDoesNotExist(ObjectName on) throws Exception {
+ try {
+ checkRuntimeBean(on);
+ fail();
+ } catch (InstanceNotFoundException e) {
+
+ }
+ }
+
+ @Test
+ public void testLookup() throws Exception {
+ createScheduled();
+ assertEquals(Sets.newHashSet(ifc1runtimeON1, ifc1runtimeON2),
+ configRegistryClient.lookupRuntimeBeans());
+ }
+
+ @Test
+ public void testReuse() throws Exception {
+ ObjectName createdConfigBean = createScheduled();
+ // empty transaction
+ CommitStatus commitInfo = configRegistryClient.createTransaction()
+ .commit();
+
+ // check that it was reused
+ ObjectName readableConfigBean = ObjectNameUtil
+ .withoutTransactionName(createdConfigBean);
+ List<ObjectName> newInstances = Collections.<ObjectName> emptyList();
+ List<ObjectName> reusedInstances = Lists
+ .newArrayList(readableConfigBean);
+ List<ObjectName> recreatedInstaces = Collections
+ .<ObjectName> emptyList();
+ assertEquals(new CommitStatus(newInstances, reusedInstances,
+ recreatedInstaces), commitInfo);
+ checkRuntimeBeans();
+ }
+
+ @Test
+ public void testRecreate() throws Exception {
+ ObjectName createdConfigBean = createScheduled();
+ // empty transaction
+ ConfigTransactionJMXClient configTransaction = configRegistryClient
+ .createTransaction();
+ ObjectName scheduledWritableON = configTransaction.lookupConfigBean(
+ TestingScheduledThreadPoolModuleFactory.NAME, scheduled1);
+ TestingScheduledThreadPoolConfigBeanMXBean scheduledWritableProxy = configTransaction
+ .newMXBeanProxy(scheduledWritableON, TestingScheduledThreadPoolConfigBeanMXBean.class);
+ scheduledWritableProxy.setRecreate(true);
+ CommitStatus commitInfo = configTransaction.commit();
+ // check that it was recreated
+ ObjectName readableConfigBean = ObjectNameUtil
+ .withoutTransactionName(createdConfigBean);
+ List<ObjectName> newInstances = Collections.<ObjectName> emptyList();
+ List<ObjectName> reusedInstances = Collections.<ObjectName> emptyList();
+ List<ObjectName> recreatedInstaces = Lists
+ .newArrayList(readableConfigBean);
+ assertEquals(new CommitStatus(newInstances, reusedInstances,
+ recreatedInstaces), commitInfo);
+ checkRuntimeBeans();
+ }
+
+ @Test
+ public void testDestroy_shouldUnregisterRuntimeBeans() throws Exception {
+ ObjectName createdConfigBean = createScheduled();
+ ConfigTransactionJMXClient configTransaction = configRegistryClient
+ .createTransaction();
+ configTransaction.destroyModule(ObjectNameUtil
+ .createTransactionModuleON(configTransaction.getTransactionName(), createdConfigBean));
+ configTransaction.commit();
+ for (ObjectName on : allObjectNames)
+ checkRuntimeBeanDoesNotExist(on);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.testingservices.scheduledthreadpool.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.matchers.JUnitMatchers.containsString;
+
+import javax.annotation.Nullable;
+import javax.management.DynamicMBean;
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.InstanceNotFoundException;
+import javax.management.ObjectName;
+
+import org.junit.Test;
+import org.opendaylight.controller.config.api.ValidationException;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.manager.testingservices.parallelapsp.TestingParallelAPSPConfigMXBean;
+import org.opendaylight.controller.config.manager.testingservices.parallelapsp.TestingParallelAPSPModuleFactory;
+import org.opendaylight.controller.config.manager.testingservices.scheduledthreadpool.TestingScheduledThreadPoolImpl;
+import org.opendaylight.controller.config.manager.testingservices.scheduledthreadpool
+ .TestingScheduledThreadPoolModuleFactory;
+import org.opendaylight.controller.config.util.ConfigTransactionJMXClient;
+
+public class TwoInterfacesExportTest extends AbstractScheduledTest {
+
+ private void assertExists(String moduleName, String instanceName)
+ throws Exception {
+ assertExists(null, moduleName, instanceName);
+ }
+
+ private void assertExists(@Nullable ConfigTransactionJMXClient transaction,
+ String moduleName, String instanceName)
+ throws InstanceNotFoundException {
+ if (transaction != null) {
+ transaction.lookupConfigBean(moduleName, instanceName);
+ // make a dummy call
+ configRegistryClient.newMBeanProxy(
+ ObjectNameUtil.createTransactionModuleON(
+ transaction.getTransactionName(), moduleName,
+ instanceName), DynamicMBean.class).getMBeanInfo();
+ } else {
+ configRegistryClient.lookupConfigBean(moduleName, instanceName);
+ // make a dummy call
+ configRegistryClient.newMBeanProxy(
+ ObjectNameUtil.createReadOnlyModuleON(moduleName,
+ instanceName), DynamicMBean.class).getMBeanInfo();
+ }
+ }
+
+ private void assertNotExists(String moduleName, String instanceName) {
+ assertNotExists(null, moduleName, instanceName);
+ }
+
+ private void assertNotExists(
+ @Nullable ConfigTransactionJMXClient transaction,
+ String moduleName, String instanceName) {
+
+ if (transaction != null) {
+ try {
+ transaction.lookupConfigBean(moduleName, instanceName);
+ fail();
+ } catch (InstanceNotFoundException e) {
+
+ }
+ } else {
+ try {
+ configRegistryClient.lookupConfigBean(moduleName, instanceName);
+ fail();
+ } catch (InstanceNotFoundException e) {
+
+ }
+ }
+ }
+
+ @Test
+ public void twoInterfaceNamesAfterCreatingConfigBean() throws Exception {
+ ConfigTransactionJMXClient transaction = configRegistryClient
+ .createTransaction();
+
+ // create using TestingThreadPoolIfc:
+ ObjectName scheduled1name = transaction.createModule(
+ TestingScheduledThreadPoolModuleFactory.NAME, scheduled1);
+
+ ObjectName retrievedName = transaction.lookupConfigBean(
+ TestingScheduledThreadPoolModuleFactory.NAME, scheduled1);
+ assertEquals(scheduled1name, retrievedName);
+
+ // getExistingConfigBean should resolve moduleName
+ String moduleName = TestingScheduledThreadPoolModuleFactory.NAME;
+ retrievedName = transaction.lookupConfigBean(moduleName, scheduled1);
+ ObjectName expected = ObjectNameUtil.createTransactionModuleON(
+ transaction.getTransactionName(), moduleName, scheduled1);
+ assertEquals(expected, retrievedName);
+
+ // commit
+ transaction.commit();
+ assertEquals(1, TestingScheduledThreadPoolImpl.allExecutors.size());
+ assertFalse(TestingScheduledThreadPoolImpl.allExecutors.get(0)
+ .isTerminated());
+ assertEquals(0,
+ TestingScheduledThreadPoolImpl.getNumberOfCloseMethodCalls());
+
+ assertExists(moduleName, scheduled1);
+
+ // destroy using ThreadPool ifc
+ transaction = configRegistryClient.createTransaction();
+ transaction.destroyModule(ObjectNameUtil.createTransactionModuleON(
+ transaction.getTransactionName(), moduleName, scheduled1));
+ transaction.commit();
+ assertEquals(1, TestingScheduledThreadPoolImpl.allExecutors.size());
+ assertTrue(TestingScheduledThreadPoolImpl.allExecutors.get(0)
+ .isTerminated());
+ assertEquals(1,
+ TestingScheduledThreadPoolImpl.getNumberOfCloseMethodCalls());
+
+ // should not be in platform:
+
+ assertNotExists(moduleName, scheduled1);
+
+ transaction = configRegistryClient.createTransaction();
+ // should not be in transaction
+ assertNotExists(transaction, moduleName, scheduled1);
+ }
+
+ @Test
+ public void tryToRegisterThreadPoolWithSameName()
+ throws InstanceAlreadyExistsException {
+ ConfigTransactionJMXClient transaction = configRegistryClient
+ .createTransaction();
+
+ transaction.createModule(TestingScheduledThreadPoolModuleFactory.NAME,
+ scheduled1);
+ try {
+ transaction.createModule(
+ TestingScheduledThreadPoolModuleFactory.NAME, scheduled1);
+ fail();
+ } catch (InstanceAlreadyExistsException e) {
+ assertThat(
+ e.getMessage(),
+ containsString("There is an instance registered with name ModuleIdentifier{factoryName='scheduled', instanceName='scheduled1'}"));
+ }
+ }
+
+ // --
+ @Test
+ public void testRegisteringAllIfcNames() throws Exception {
+ ConfigTransactionJMXClient transaction = configRegistryClient
+ .createTransaction();
+ transaction.createModule(TestingScheduledThreadPoolModuleFactory.NAME,
+ scheduled1);
+ transaction.commit();
+ assertExists(TestingScheduledThreadPoolModuleFactory.NAME, scheduled1);
+ // another transaction
+ transaction = configRegistryClient.createTransaction();
+ assertExists(transaction, TestingScheduledThreadPoolModuleFactory.NAME,
+ scheduled1);
+ }
+
+ @Test
+ public void testWithAPSP_useScheduledNames()
+ throws InstanceAlreadyExistsException, ValidationException {
+ ConfigTransactionJMXClient transaction = configRegistryClient
+ .createTransaction();
+ ObjectName scheduledName = transaction.createModule(
+ TestingScheduledThreadPoolModuleFactory.NAME, scheduled1);
+
+ ObjectName apspName = transaction.createModule(
+ TestingParallelAPSPModuleFactory.NAME, "apsp1");
+ TestingParallelAPSPConfigMXBean apspProxy = transaction.newMBeanProxy(
+ apspName, TestingParallelAPSPConfigMXBean.class);
+ apspProxy.setThreadPool(scheduledName);
+ apspProxy.setSomeParam("someParam");
+ transaction.validateConfig();
+
+ }
+
+ @Test
+ public void testWithAPSP_useIfcNameMismatch() throws Exception {
+ ConfigTransactionJMXClient transaction = configRegistryClient
+ .createTransaction();
+ transaction.createModule(TestingScheduledThreadPoolModuleFactory.NAME,
+ scheduled1);
+
+ ObjectName apspName = transaction.createModule(
+ TestingParallelAPSPModuleFactory.NAME, "apsp1");
+ TestingParallelAPSPConfigMXBean apspProxy = transaction.newMBeanProxy(
+ apspName, TestingParallelAPSPConfigMXBean.class);
+ apspProxy.setThreadPool(ObjectNameUtil.createReadOnlyModuleON(
+ TestingScheduledThreadPoolModuleFactory.NAME, scheduled1));
+ apspProxy.setSomeParam("someParam");
+ transaction.validateConfig();
+ transaction.commit();
+
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.testingservices.seviceinterface;
+
+import org.opendaylight.controller.config.api.annotations.ServiceInterfaceAnnotation;
+import org.opendaylight.controller.config.manager.testingservices.threadpool.TestingModifiableThreadPoolIfc;
+
+@ServiceInterfaceAnnotation(value = "modifiable-threadpool", osgiRegistrationType = TestingModifiableThreadPoolIfc.class)
+public interface ModifiableThreadPoolServiceInterface extends
+ TestingThreadPoolServiceInterface {
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.testingservices.seviceinterface;
+
+import org.opendaylight.controller.config.api.annotations.ServiceInterfaceAnnotation;
+import org.opendaylight.controller.config.manager.testingservices.scheduledthreadpool.TestingScheduledThreadPoolIfc;
+
+@ServiceInterfaceAnnotation(value = "threadpool-scheduled", osgiRegistrationType = TestingScheduledThreadPoolIfc.class)
+public interface TestingScheduledThreadPoolServiceInterface extends
+ TestingThreadPoolServiceInterface {
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.testingservices.seviceinterface;
+
+import org.opendaylight.controller.config.api.annotations.AbstractServiceInterface;
+import org.opendaylight.controller.config.api.annotations.ServiceInterfaceAnnotation;
+import org.opendaylight.controller.config.manager.testingservices.threadpool.TestingThreadPoolIfc;
+
+@ServiceInterfaceAnnotation(value = "testing-threadpool", osgiRegistrationType = TestingThreadPoolIfc.class)
+public interface TestingThreadPoolServiceInterface extends
+ AbstractServiceInterface {
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.testingservices.threadpool;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadPoolExecutor;
+
+import com.google.common.collect.Lists;
+
+public class TestingFixedThreadPool implements TestingThreadPoolIfc, Closeable,
+ TestingModifiableThreadPoolIfc {
+ private final ThreadPoolExecutor executorService;
+ private final String uniqueName;
+
+ public static void cleanUp() {
+ for (ExecutorService executorService : allExecutors) {
+ executorService.shutdown();
+ }
+ allExecutors.clear();
+ }
+
+ // for verification purposes:
+ public static final List<ThreadPoolExecutor> allExecutors = Collections
+ .synchronizedList(Lists.<ThreadPoolExecutor>newLinkedList());
+
+ public TestingFixedThreadPool(int threadCount, String uniqueName) {
+ checkNotNull(uniqueName);
+ this.uniqueName = uniqueName;
+ executorService = (ThreadPoolExecutor) Executors
+ .newFixedThreadPool(threadCount);
+ allExecutors.add(executorService);
+ }
+
+ @Override
+ public Executor getExecutor() {
+ return executorService;
+ }
+
+ @Override
+ public void close() throws IOException {
+ executorService.shutdown();
+
+ }
+
+ @Override
+ public int getMaxNumberOfThreads() {
+ return executorService.getMaximumPoolSize();
+ }
+
+ public String getUniqueName() {
+ return uniqueName;
+ }
+
+ @Override
+ public void setMaximumNumberOfThreads(int activeCount) {
+ checkArgument(activeCount > 0);
+ executorService.setMaximumPoolSize(activeCount);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.testingservices.threadpool;
+
+public interface TestingFixedThreadPoolConfigMXBean extends
+ TestingThreadPoolConfigMXBean {
+
+ public void setThreadCount(int threadCount);
+
+ public boolean isTriggerNewInstanceCreation();
+
+ public void setTriggerNewInstanceCreation(boolean boolTest);
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.testingservices.threadpool;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import java.io.Closeable;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.NotThreadSafe;
+
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.manager.testingservices.seviceinterface.ModifiableThreadPoolServiceInterface;
+import org.opendaylight.controller.config.spi.Module;
+
+@NotThreadSafe
+public class TestingFixedThreadPoolModule implements
+ TestingFixedThreadPoolConfigMXBean, Module,
+ TestingThreadPoolConfigMXBean, ModifiableThreadPoolServiceInterface {
+ private final AutoCloseable oldCloseable;
+ private final TestingFixedThreadPool oldInstance;
+ private final ModuleIdentifier name;
+ private TestingFixedThreadPool instance;
+ private int threadCount = 0;
+ private boolean triggerNewInstanceCreation;
+
+ TestingFixedThreadPoolModule(ModuleIdentifier name,
+ @Nullable AutoCloseable oldCloseable,
+ @Nullable TestingFixedThreadPool oldInstance) {
+ this.name = name;
+ this.oldCloseable = oldCloseable;
+ this.oldInstance = oldInstance;
+ }
+
+ @Override
+ public ModuleIdentifier getName() {
+ return name;
+ }
+
+ // attributes
+ @Override
+ public void setThreadCount(int threadCount) {
+ this.threadCount = threadCount;
+ }
+
+ @Override
+ public int getThreadCount() {
+ return threadCount;
+ }
+
+ @Override
+ public boolean isTriggerNewInstanceCreation() {
+ return triggerNewInstanceCreation;
+ }
+
+ @Override
+ public void setTriggerNewInstanceCreation(boolean triggerNewInstanceCreation) {
+ this.triggerNewInstanceCreation = triggerNewInstanceCreation;
+ }
+
+ // operations
+
+ private boolean isReusable() {
+ return oldInstance != null;
+ }
+
+ @Override
+ public void validate() {
+ checkState(threadCount > 0,
+ "Parameter 'threadCount' must be greater than 0");
+ }
+
+ @Override
+ public Closeable getInstance() {
+ if (instance == null) {
+ if (isReusable() && triggerNewInstanceCreation == false) { // simulate
+ // big
+ // change
+ // using
+ // triggerNewInstanceCreation
+ oldInstance.setMaximumNumberOfThreads(threadCount);
+ instance = oldInstance;
+ } else {
+ if (oldCloseable != null) {
+ try {
+ oldCloseable.close();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ instance = new TestingFixedThreadPool(threadCount,
+ name.toString());
+ }
+ }
+ return instance;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.testingservices.threadpool;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.opendaylight.controller.config.api.DependencyResolver;
+import org.opendaylight.controller.config.api.DynamicMBeanWithInstance;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.annotations.AbstractServiceInterface;
+import org.opendaylight.controller.config.manager.testingservices.seviceinterface.ModifiableThreadPoolServiceInterface;
+import org.opendaylight.controller.config.manager.testingservices.seviceinterface.TestingThreadPoolServiceInterface;
+import org.opendaylight.controller.config.spi.Module;
+import org.opendaylight.controller.config.spi.ModuleFactory;
+
+public class TestingFixedThreadPoolModuleFactory implements ModuleFactory {
+ public static final String NAME = "fixed";
+ private static List<Class<? extends TestingThreadPoolServiceInterface>> ifc = Arrays
+ .asList(ModifiableThreadPoolServiceInterface.class, TestingThreadPoolServiceInterface.class);
+
+ @Override
+ public String getImplementationName() {
+ return NAME;
+ }
+
+ @Override
+ public TestingFixedThreadPoolModule createModule(String instanceName,
+ DependencyResolver dependencyResolver) {
+ return new TestingFixedThreadPoolModule(new ModuleIdentifier(NAME,
+ instanceName), null, null);
+ }
+
+ @Override
+ public Module createModule(String instanceName,
+ DependencyResolver dependencyResolver, DynamicMBeanWithInstance old)
+ throws Exception {
+ int threadCount = (Integer) old.getAttribute("ThreadCount");
+ // is the instance compatible?
+ TestingFixedThreadPool oldInstance;
+ try {
+ // reconfigure existing instance
+ oldInstance = (TestingFixedThreadPool) old.getInstance();
+ } catch (ClassCastException e) {
+ // old instance will be closed, new needs to be created
+ oldInstance = null;
+ }
+ TestingFixedThreadPoolModule result = new TestingFixedThreadPoolModule(
+ new ModuleIdentifier(NAME, instanceName), old.getInstance(),
+ oldInstance);
+ result.setThreadCount(threadCount);
+ return result;
+ }
+
+ @Override
+ public boolean isModuleImplementingServiceInterface(
+ Class<? extends AbstractServiceInterface> serviceInterface) {
+ return ifc.contains(serviceInterface);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.testingservices.threadpool;
+
+public interface TestingModifiableThreadPoolIfc extends TestingThreadPoolIfc {
+ void setMaximumNumberOfThreads(int activeCount);
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.testingservices.threadpool;
+
+public interface TestingThreadPoolConfigMXBean {
+
+ public int getThreadCount();
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.testingservices.threadpool;
+
+import java.util.concurrent.Executor;
+
+public interface TestingThreadPoolIfc {
+
+ Executor getExecutor();
+
+ int getMaxNumberOfThreads();
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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
+ */
+/**
+ * Represents simple service that has getters and setters, but no dependencies.
+ * Creates fixed thread pool wrapped in {@link org.opendaylight.controller.config.manager.testingservices.threadpool.TestingThreadPoolIfc}.
+ * Supports changing the number of threads on 'live' executor.
+ */
+package org.opendaylight.controller.config.manager.testingservices.threadpool;
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.manager.testingservices.threadpool.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.matchers.JUnitMatchers.containsString;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ThreadPoolExecutor;
+
+import javax.management.DynamicMBean;
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.InstanceNotFoundException;
+import javax.management.MBeanException;
+import javax.management.ObjectName;
+import javax.management.ReflectionException;
+import javax.management.RuntimeMBeanException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.config.api.ConflictingVersionException;
+import org.opendaylight.controller.config.api.ValidationException;
+import org.opendaylight.controller.config.api.ValidationException.ExceptionMessageWithStackTrace;
+import org.opendaylight.controller.config.api.jmx.CommitStatus;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.manager.impl.AbstractConfigWithJolokiaTest;
+import org.opendaylight.controller.config.manager.impl.factoriesresolver.HardcodedModuleFactoriesResolver;
+import org.opendaylight.controller.config.manager.testingservices.threadpool.TestingFixedThreadPool;
+import org.opendaylight.controller.config.manager.testingservices.threadpool.TestingFixedThreadPoolConfigMXBean;
+import org.opendaylight.controller.config.manager.testingservices.threadpool.TestingFixedThreadPoolModuleFactory;
+import org.opendaylight.controller.config.manager.testingservices.threadpool.TestingThreadPoolIfc;
+import org.opendaylight.controller.config.util.ConfigTransactionClient;
+import org.opendaylight.controller.config.util.ConfigTransactionJMXClient;
+import org.opendaylight.controller.config.util.jolokia.ConfigTransactionJolokiaClient;
+
+/**
+ * Tests basic functionality of configuration registry:
+ * <ol>
+ * <li>Creation of instance</li>
+ * <li>Destruction of instance</li>
+ * <li>Reconfiguration of live object</li>
+ * <li>Reconfiguration that triggers new object creation</li>
+ * <li>Replacement of running instance with different one with same name</li>
+ * </ol>
+ * Only one bean is being configured - {@link TestingThreadPoolIfc} which has no
+ * dependencies.
+ */
+public class SimpleConfigurationTest extends AbstractConfigWithJolokiaTest {
+ private final int numberOfThreads = 5;
+ private final int numberOfThreads2 = 10;
+ private static final String fixed1 = "fixed1";
+ private static final List<ObjectName> emptyONs = Collections
+ .<ObjectName> emptyList();
+ private static final ObjectName platformFixed1ON = ObjectNameUtil
+ .createReadOnlyModuleON(TestingFixedThreadPoolModuleFactory.NAME, fixed1);
+ private static final List<ObjectName> fixed1List = Arrays
+ .asList(platformFixed1ON);
+
+ @Before
+ public void setUp() {
+ super.initConfigTransactionManagerImpl(new HardcodedModuleFactoriesResolver(
+ new TestingFixedThreadPoolModuleFactory()));
+ }
+
+ @After
+ public void tearDown() {
+ TestingFixedThreadPool.cleanUp();
+ }
+
+ private ObjectName firstCommit() throws Exception {
+ ConfigTransactionJMXClient transaction = configRegistryClient
+ .createTransaction();
+
+ ObjectName fixed1names = createFixedThreadPool(transaction);
+
+ // commit
+ assertEquals(1, configRegistryClient.getOpenConfigs().size());
+ CommitStatus commitStatus = transaction.commit();
+ assertEquals(0, configRegistryClient.getOpenConfigs().size());
+ CommitStatus expected = new CommitStatus(Arrays.asList(ObjectNameUtil
+ .withoutTransactionName(fixed1names)), emptyONs, emptyONs);
+ assertEquals(expected, commitStatus);
+
+ assertEquals(1, TestingFixedThreadPool.allExecutors.size());
+ assertFalse(TestingFixedThreadPool.allExecutors.get(0).isShutdown());
+ return fixed1names;
+ }
+
+ private ObjectName createFixedThreadPool(
+ ConfigTransactionJMXClient transaction)
+ throws InstanceAlreadyExistsException, InstanceNotFoundException {
+ transaction.assertVersion(0, 1);
+
+ ObjectName fixed1names = transaction.createModule(
+ TestingFixedThreadPoolModuleFactory.NAME, fixed1);
+ TestingFixedThreadPoolConfigMXBean fixedConfigProxy = transaction
+ .newMXBeanProxy(fixed1names, TestingFixedThreadPoolConfigMXBean.class);
+ fixedConfigProxy.setThreadCount(numberOfThreads);
+
+ ObjectName retrievedNames = transaction.lookupConfigBean(
+ TestingFixedThreadPoolModuleFactory.NAME, fixed1);
+ assertEquals(fixed1names, retrievedNames);
+ return fixed1names;
+ }
+
+ @Test
+ public void testCreateAndDestroyBeanInSameTransaction() throws Exception {
+ ConfigTransactionJMXClient transaction = configRegistryClient
+ .createTransaction();
+ ObjectName fixed1names = createFixedThreadPool(transaction);
+ transaction.destroyModule(fixed1names);
+ CommitStatus commitStatus = transaction.commit();
+ assertStatus(commitStatus, 0, 0, 0);
+
+ }
+
+ @Test
+ public void testValidationUsingJMXClient() throws Exception {
+ ConfigTransactionClient transaction = configRegistryClient
+ .createTransaction();
+ testValidation(transaction);
+ }
+
+ @Test
+ public void testValidationUsingJolokiaClient() throws Exception {
+ ConfigTransactionClient transaction = configRegistryJolokiaClient
+ .createTransaction();
+ testValidation(transaction);
+ }
+
+ private void testValidation(ConfigTransactionClient transaction)
+ throws InstanceAlreadyExistsException, ReflectionException,
+ InstanceNotFoundException, MBeanException {
+ ObjectName fixed1names = transaction.createModule(
+ TestingFixedThreadPoolModuleFactory.NAME, fixed1);
+ // call validate on config bean
+ try {
+ platformMBeanServer.invoke(fixed1names, "validate", new Object[0],
+ new String[0]);
+ fail();
+ } catch (RuntimeMBeanException e) {
+ RuntimeException targetException = e.getTargetException();
+ assertNotNull(targetException);
+ assertEquals(ValidationException.class, targetException.getClass());
+ }
+
+ // validate config bean
+ try {
+ transaction.validateBean(fixed1names);
+ fail();
+ } catch (ValidationException e) {
+ for (Map.Entry<String, Map<String, ExceptionMessageWithStackTrace>> exception : e
+ .getFailedValidations().entrySet()) {
+ for (Map.Entry<String, ExceptionMessageWithStackTrace> entry : exception
+ .getValue().entrySet()) {
+ assertEquals(
+ "Parameter 'threadCount' must be greater than 0",
+ entry.getValue().getMessage());
+ }
+ }
+ }
+ // validate transaction
+ try {
+ transaction.validateConfig();
+ fail();
+ } catch (ValidationException e) {
+ for (Map.Entry<String, Map<String, ExceptionMessageWithStackTrace>> exception : e
+ .getFailedValidations().entrySet()) {
+ for (Map.Entry<String, ExceptionMessageWithStackTrace> entry : exception
+ .getValue().entrySet()) {
+ assertEquals(
+ "Parameter 'threadCount' must be greater than 0",
+ entry.getValue().getMessage());
+ }
+ }
+ }
+ try {
+ transaction.commit();
+ } catch (ValidationException e) {
+ for (Map.Entry<String, Map<String, ExceptionMessageWithStackTrace>> exception : e
+ .getFailedValidations().entrySet()) {
+ for (Map.Entry<String, ExceptionMessageWithStackTrace> entry : exception
+ .getValue().entrySet()) {
+ assertEquals(
+ "Parameter 'threadCount' must be greater than 0",
+ entry.getValue().getMessage());
+ }
+ }
+ }
+ }
+
+ @Test
+ public void test_createThreadPool_changeNumberOfThreads() throws Exception {
+ firstCommit();
+ ConfigTransactionJMXClient transaction = configRegistryClient
+ .createTransaction();
+ TestingFixedThreadPoolConfigMXBean fixedConfigProxy = startReconfiguringFixed1ThreadPool(transaction);
+ assertEquals(numberOfThreads, fixedConfigProxy.getThreadCount());
+ fixedConfigProxy.setThreadCount(numberOfThreads2);
+ CommitStatus commitStatus = transaction.commit();
+ checkThreadPools(1, numberOfThreads2);
+ CommitStatus expected = new CommitStatus(emptyONs, fixed1List, emptyONs);
+ assertEquals(expected, commitStatus);
+ }
+
+ @Test
+ public void test_createFixedThreadPool_destroyIt() throws Exception {
+ // 1, start transaction, create new fixed thread pool
+ ObjectName fixed1name = firstCommit();
+
+ // 2, check that configuration was copied to platform
+ DynamicMBean dynamicMBean = configRegistryClient.newMBeanProxy(
+ ObjectNameUtil.withoutTransactionName(fixed1name),
+ DynamicMBean.class);
+ dynamicMBean.getMBeanInfo();
+ assertEquals(numberOfThreads, dynamicMBean.getAttribute("ThreadCount"));
+
+ // 3, shutdown fixed1 in new transaction
+ assertFalse(TestingFixedThreadPool.allExecutors.get(0).isShutdown());
+ ConfigTransactionJMXClient transaction = configRegistryClient
+ .createTransaction();
+
+ // check versions
+ transaction.assertVersion(1, 2);
+
+ // test that it was copied to new transaction
+ ObjectName retrievedName = transaction.lookupConfigBean(
+ TestingFixedThreadPoolModuleFactory.NAME, fixed1);
+ assertNotNull(retrievedName);
+
+ // check that number of threads was copied from dynamic
+
+ TestingFixedThreadPoolConfigMXBean fixedConfigProxy = transaction
+ .newMXBeanProxy(retrievedName, TestingFixedThreadPoolConfigMXBean.class);
+ assertEquals(numberOfThreads, fixedConfigProxy.getThreadCount());
+
+ // destroy
+ transaction.destroyModule(ObjectNameUtil.createTransactionModuleON(
+ transaction.getTransactionName(),
+ TestingFixedThreadPoolModuleFactory.NAME, fixed1));
+ transaction.commit();
+
+ // 4, check
+ assertEquals(2, configRegistryClient.getVersion());
+ assertEquals(1, TestingFixedThreadPool.allExecutors.size());
+ assertTrue(TestingFixedThreadPool.allExecutors.get(0).isShutdown());
+
+ // dynamic config should be removed from platform
+ try {
+ dynamicMBean.getMBeanInfo();
+ fail();
+ } catch (Exception e) {
+ assertTrue(e.getCause() instanceof InstanceNotFoundException);
+ }
+ }
+
+ @Test
+ public void testReplaceFixed1() throws Exception {
+ // 1, start transaction, create new fixed thread pool
+ firstCommit();
+ // destroy and recreate with different # of threads
+ ConfigTransactionJMXClient transaction = configRegistryClient
+ .createTransaction();
+
+ transaction.destroyModule(ObjectNameUtil.createTransactionModuleON(
+ transaction.getTransactionName(),
+ TestingFixedThreadPoolModuleFactory.NAME, fixed1));
+
+ ObjectName fixed1name = transaction.createModule(
+ TestingFixedThreadPoolModuleFactory.NAME, fixed1);
+ TestingFixedThreadPoolConfigMXBean fixedConfigProxy = transaction
+ .newMXBeanProxy(fixed1name, TestingFixedThreadPoolConfigMXBean.class);
+ fixedConfigProxy.setThreadCount(numberOfThreads2);
+ // commit
+ transaction.commit();
+ // check that first threadpool is closed
+ checkThreadPools(2, numberOfThreads2);
+ }
+
+ private void checkThreadPools(int expectedTotalNumberOfExecutors,
+ int expectedNumberOfThreadsInLastExecutor) {
+ assertEquals(expectedTotalNumberOfExecutors,
+ TestingFixedThreadPool.allExecutors.size());
+ for (int i = 0; i < expectedTotalNumberOfExecutors - 1; i++) {
+ assertTrue(TestingFixedThreadPool.allExecutors.get(i).isShutdown());
+ }
+ ThreadPoolExecutor lastExecutor = TestingFixedThreadPool.allExecutors
+ .get(expectedTotalNumberOfExecutors - 1);
+ assertFalse(lastExecutor.isShutdown());
+ assertEquals(expectedNumberOfThreadsInLastExecutor,
+ lastExecutor.getMaximumPoolSize());
+ }
+
+ @Test
+ public void testTriggerRecreatingInstance() throws Exception {
+ // 1, start transaction, create new fixed thread pool
+ firstCommit();
+ // switch boolean to create new instance
+ ConfigTransactionJMXClient transaction = configRegistryClient
+ .createTransaction();
+ TestingFixedThreadPoolConfigMXBean fixedConfigProxy = startReconfiguringFixed1ThreadPool(transaction);
+
+ fixedConfigProxy.setTriggerNewInstanceCreation(true);
+ // commit
+ CommitStatus commitStatus = transaction.commit();
+ // check that new threadpool is created and old one is closed
+ checkThreadPools(2, numberOfThreads);
+ CommitStatus expected = new CommitStatus(emptyONs, emptyONs, fixed1List);
+ assertEquals(expected, commitStatus);
+ }
+
+ // return MBeanProxy for 'fixed1' and current transaction
+ private TestingFixedThreadPoolConfigMXBean startReconfiguringFixed1ThreadPool(
+ ConfigTransactionJMXClient transaction)
+ throws InstanceNotFoundException {
+ ObjectName fixed1name = transaction.lookupConfigBean(
+ TestingFixedThreadPoolModuleFactory.NAME, fixed1);
+
+ TestingFixedThreadPoolConfigMXBean fixedConfigProxy = transaction
+ .newMXBeanProxy(fixed1name, TestingFixedThreadPoolConfigMXBean.class);
+ return fixedConfigProxy;
+ }
+
+ @Test
+ public void testAbort() {
+ ConfigTransactionJMXClient transaction = configRegistryClient
+ .createTransaction();
+ assertEquals(1, configRegistryClient.getOpenConfigs().size());
+
+ transaction.abortConfig();
+ try {
+ transaction.createModule(TestingFixedThreadPoolModuleFactory.NAME,
+ fixed1);
+ fail();
+ } catch (Exception e) {
+ assertTrue(e.getCause() instanceof InstanceNotFoundException);
+ }
+ try {
+ transaction.validateConfig();
+ fail();
+ } catch (Exception e) {
+ assertTrue(e.getCause() instanceof InstanceNotFoundException);
+ }
+ assertEquals(0, configRegistryClient.getOpenConfigs().size());
+ }
+
+ @Test
+ public void testOptimisticLock_ConfigTransactionClient() throws Exception {
+ ConfigTransactionJMXClient transaction1 = configRegistryClient
+ .createTransaction();
+ ConfigTransactionJMXClient transaction2 = configRegistryClient
+ .createTransaction();
+ transaction2.assertVersion(0, 2);
+ transaction2.commit();
+ try {
+ transaction1.commit();
+ fail();
+ } catch (ConflictingVersionException e) {
+ assertEquals(
+ "Optimistic lock failed. Expected parent version 2, was 0",
+ e.getMessage());
+ }
+ }
+
+ @Test
+ public void testOptimisticLock_ConfigRegistry() throws Exception {
+ ConfigTransactionJMXClient transaction1 = configRegistryClient
+ .createTransaction();
+ ConfigTransactionJMXClient transaction2 = configRegistryClient
+ .createTransaction();
+ transaction2.assertVersion(0, 2);
+ transaction2.commit();
+ try {
+ configRegistryClient.commitConfig(transaction1.getObjectName());
+ fail();
+ } catch (ConflictingVersionException e) {
+ assertEquals(
+ "Optimistic lock failed. Expected parent version 2, was 0",
+ e.getMessage());
+ }
+ }
+
+ @Test
+ public void testOptimisticLock_ConfigTransactionJolokiaClient()
+ throws Exception {
+ ConfigTransactionJolokiaClient transaction1 = configRegistryJolokiaClient
+ .createTransaction();
+ ConfigTransactionJolokiaClient transaction2 = configRegistryJolokiaClient
+ .createTransaction();
+ transaction2.assertVersion(0, 2);
+ transaction2.commit();
+ try {
+ transaction1.commit();
+ fail();
+ } catch (ConflictingVersionException e) {
+ assertEquals(
+ "Optimistic lock failed. Expected parent version 2, was 0",
+ e.getMessage());
+ }
+ }
+
+ @Test
+ public void testOptimisticLock_ConfigRegistryJolokiaClient()
+ throws Exception {
+ ConfigTransactionJolokiaClient transaction1 = configRegistryJolokiaClient
+ .createTransaction();
+ ConfigTransactionJolokiaClient transaction2 = configRegistryJolokiaClient
+ .createTransaction();
+ transaction2.assertVersion(0, 2);
+ transaction2.commit();
+ try {
+ configRegistryJolokiaClient.commitConfig(transaction1
+ .getObjectName());
+ fail();
+ } catch (ConflictingVersionException e) {
+ assertEquals(
+ "Optimistic lock failed. Expected parent version 2, was 0",
+ e.getMessage());
+ }
+ }
+
+ @Test
+ public void testUsingJolokia() throws Exception {
+ ConfigTransactionJolokiaClient transactionClient = configRegistryJolokiaClient
+ .createTransaction();
+
+ ObjectName name = transactionClient.createModule(
+ TestingFixedThreadPoolModuleFactory.NAME, fixed1);
+
+ try {
+ transactionClient.validateConfig();
+ fail();
+ } catch (ValidationException e) {
+ assertThat(
+ e.getMessage(),
+ containsString("Parameter 'threadCount' must be greater than 0"));
+ }
+
+ transactionClient.setAttribute(name, "ThreadCount", numberOfThreads);
+ // commit
+ CommitStatus commitStatus = transactionClient.commit();
+ CommitStatus expected = new CommitStatus(Arrays.asList(ObjectNameUtil
+ .withoutTransactionName(name)), emptyONs, emptyONs);
+ assertEquals(expected, commitStatus);
+ }
+
+}
--- /dev/null
+target
+.classpath
+.settings
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <artifactId>config-subsystem</artifactId>
+ <groupId>org.opendaylight</groupId>
+ <version>0.2.1-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+ <artifactId>config-util</artifactId>
+ <name>${project.artifactId}</name>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <!-- compile dependencies -->
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>config-api</artifactId>
+ <version>0.2.1-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.jolokia</groupId>
+ <artifactId>jolokia-client-java</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jolokia</groupId>
+ <artifactId>jolokia-jvm</artifactId>
+ <classifier>agent</classifier>
+ </dependency>
+
+ <!-- test dependencies -->
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <forkCount>1</forkCount>
+ <reuseForks>false</reuseForks>
+ <perCoreThreadCount>false</perCoreThreadCount>
+ <threadCount>1</threadCount>
+ </configuration>
+ </plugin>
+ <!-- test jar -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.util;
+
+public class AttributeEntry {
+ private final String key;
+ private final String description;
+ private final Object value;
+ private final String type;
+ private final boolean rw;
+
+ public AttributeEntry(String key, String description, Object value,
+ String type, boolean rw) {
+ this.key = key;
+ this.description = description;
+ this.value = value;
+ this.type = type;
+ this.rw = rw;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public boolean isRw() {
+ return rw;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.util;
+
+import javax.management.ObjectName;
+
+import org.opendaylight.controller.config.api.jmx.ConfigRegistryMXBean;
+
+public interface ConfigRegistryClient extends ConfigRegistryMXBean {
+
+ ConfigTransactionClient createTransaction();
+
+ ConfigTransactionClient getConfigTransactionClient(String transactionName);
+
+ ConfigTransactionClient getConfigTransactionClient(ObjectName objectName);
+
+ long getVersion();
+
+ Object invokeMethod(ObjectName on, String name, Object[] params,
+ String[] signature);
+
+ Object getAttributeCurrentValue(ObjectName on, String attributeName);
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.util;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+import javax.management.AttributeNotFoundException;
+import javax.management.InstanceNotFoundException;
+import javax.management.JMException;
+import javax.management.JMX;
+import javax.management.MBeanException;
+import javax.management.MBeanServer;
+import javax.management.ObjectInstance;
+import javax.management.ObjectName;
+import javax.management.ReflectionException;
+
+import org.opendaylight.controller.config.api.ConflictingVersionException;
+import org.opendaylight.controller.config.api.ValidationException;
+import org.opendaylight.controller.config.api.jmx.CommitStatus;
+import org.opendaylight.controller.config.api.jmx.ConfigRegistryMXBean;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+
+public class ConfigRegistryJMXClient implements ConfigRegistryClient {
+ private final ConfigRegistryMXBean configRegistryProxy;
+ private final ObjectName configRegistryON;
+ private final MBeanServer configMBeanServer;
+
+ public ConfigRegistryJMXClient(MBeanServer configMBeanServer) {
+ this.configMBeanServer = configMBeanServer;
+ configRegistryON = OBJECT_NAME;
+ Set<ObjectInstance> searchResult = configMBeanServer.queryMBeans(
+ configRegistryON, null);
+ if (!(searchResult.size() == 1)) {
+ throw new IllegalStateException("Config registry not found");
+ }
+ configRegistryProxy = JMX.newMXBeanProxy(configMBeanServer, configRegistryON, ConfigRegistryMXBean.class,
+ false);
+ }
+
+ @Override
+ public ConfigTransactionJMXClient createTransaction() {
+ ObjectName configTransactionControllerON = beginConfig();
+ return getConfigTransactionClient(configTransactionControllerON);
+ }
+
+ @Override
+ public ConfigTransactionJMXClient getConfigTransactionClient(
+ String transactionName) {
+ ObjectName objectName = ObjectNameUtil
+ .createTransactionControllerON(transactionName);
+ return getConfigTransactionClient(objectName);
+ }
+
+ @Override
+ public ConfigTransactionJMXClient getConfigTransactionClient(
+ ObjectName objectName) {
+ return new ConfigTransactionJMXClient(configRegistryProxy, objectName,
+ configMBeanServer);
+ }
+
+ public <T> T newMBeanProxy(ObjectName on, Class<T> clazz) {
+ return JMX.newMBeanProxy(configMBeanServer, on, clazz);
+ }
+
+ public <T> T newMXBeanProxy(ObjectName on, Class<T> clazz) {
+ return JMX.newMXBeanProxy(configMBeanServer, on, clazz);
+ }
+
+ @Override
+ public ObjectName beginConfig() {
+ return configRegistryProxy.beginConfig();
+ }
+
+ @Override
+ public CommitStatus commitConfig(ObjectName transactionControllerON)
+ throws ConflictingVersionException, ValidationException {
+ return configRegistryProxy.commitConfig(transactionControllerON);
+ }
+
+ @Override
+ public List<ObjectName> getOpenConfigs() {
+ return configRegistryProxy.getOpenConfigs();
+ }
+
+ @Override
+ public long getVersion() {
+ try {
+ return (Long) configMBeanServer.getAttribute(configRegistryON,
+ "Version");
+ } catch (JMException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public Set<String> getAvailableModuleNames() {
+ return configRegistryProxy.getAvailableModuleNames();
+ }
+
+ @Override
+ public boolean isHealthy() {
+ return configRegistryProxy.isHealthy();
+ }
+
+ @Override
+ public Set<ObjectName> lookupConfigBeans() {
+ return configRegistryProxy.lookupConfigBeans();
+ }
+
+ @Override
+ public Set<ObjectName> lookupConfigBeans(String moduleName) {
+ return configRegistryProxy.lookupConfigBeans(moduleName);
+ }
+
+ @Override
+ public Set<ObjectName> lookupConfigBeans(String moduleName,
+ String instanceName) {
+ return configRegistryProxy.lookupConfigBeans(moduleName, instanceName);
+ }
+
+ @Override
+ public ObjectName lookupConfigBean(String moduleName, String instanceName)
+ throws InstanceNotFoundException {
+ return configRegistryProxy.lookupConfigBean(moduleName, instanceName);
+ }
+
+ @Override
+ public Set<ObjectName> lookupRuntimeBeans() {
+ return configRegistryProxy.lookupRuntimeBeans();
+ }
+
+ @Override
+ public Set<ObjectName> lookupRuntimeBeans(String ifcName,
+ String instanceName) {
+ return configRegistryProxy.lookupRuntimeBeans(ifcName, instanceName);
+ }
+
+ @Override
+ public Object invokeMethod(ObjectName on, String name, Object[] params,
+ String[] signature) {
+ try {
+ return configMBeanServer.invoke(on, name, params, signature);
+ } catch (InstanceNotFoundException | ReflectionException
+ | MBeanException e) {
+ throw new RuntimeException("Unable to invoke operation " + name
+ + " on " + on + " with attributes "
+ + Arrays.toString(params) + " and signature "
+ + Arrays.toString(signature), e);
+ }
+ }
+
+ @Override
+ public Object getAttributeCurrentValue(ObjectName on, String attributeName) {
+ try {
+ return configMBeanServer.getAttribute(on, attributeName);
+ } catch (AttributeNotFoundException | InstanceNotFoundException
+ | MBeanException | ReflectionException e) {
+ throw new RuntimeException("Unable to get attribute "
+ + attributeName + " for " + on, e);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.util;
+
+import javax.management.Attribute;
+import javax.management.InstanceNotFoundException;
+import javax.management.ObjectName;
+
+import org.opendaylight.controller.config.api.ConflictingVersionException;
+import org.opendaylight.controller.config.api.ValidationException;
+import org.opendaylight.controller.config.api.jmx.CommitStatus;
+import org.opendaylight.controller.config.api.jmx.ConfigTransactionControllerMXBean;
+
+public interface ConfigTransactionClient extends
+ ConfigTransactionControllerMXBean {
+
+ CommitStatus commit() throws ConflictingVersionException,
+ ValidationException;
+
+ void assertVersion(int expectedParentVersion, int expectedCurrentVersion);
+
+ long getParentVersion();
+
+ long getVersion();
+
+ ObjectName getObjectName();
+
+ void validateBean(ObjectName configBeanON) throws ValidationException;
+
+ void destroyConfigBean(String moduleName, String instanceName)
+ throws InstanceNotFoundException;
+
+ void setAttribute(ObjectName on, String jmxName, Attribute attribute);
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.util;
+
+import java.util.Set;
+
+import javax.management.Attribute;
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.InstanceNotFoundException;
+import javax.management.JMException;
+import javax.management.JMX;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import javax.management.RuntimeMBeanException;
+
+import org.opendaylight.controller.config.api.ConflictingVersionException;
+import org.opendaylight.controller.config.api.ValidationException;
+import org.opendaylight.controller.config.api.jmx.CommitStatus;
+import org.opendaylight.controller.config.api.jmx.ConfigRegistryMXBean;
+import org.opendaylight.controller.config.api.jmx.ConfigTransactionControllerMXBean;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+
+public class ConfigTransactionJMXClient implements ConfigTransactionClient {
+ private final ConfigRegistryMXBean configTransactionManagerProxy;
+ private final ObjectName configTransactionControllerON;
+ private final ConfigTransactionControllerMXBean configControllerProxy;
+ private final MBeanServer configMBeanServer;
+
+ public ConfigTransactionJMXClient(
+ ConfigRegistryMXBean configTransactionManagerProxy,
+ ObjectName configTransactionControllerON,
+ MBeanServer configMBeanServer) {
+ this.configMBeanServer = configMBeanServer;
+ this.configTransactionManagerProxy = configTransactionManagerProxy;
+ this.configTransactionControllerON = configTransactionControllerON;
+ this.configControllerProxy = JMX.newMXBeanProxy(configMBeanServer,
+ configTransactionControllerON,
+ ConfigTransactionControllerMXBean.class);
+ }
+
+ public <T> T newMXBeanProxy(ObjectName on, Class<T> clazz) {
+ return JMX.newMXBeanProxy(configMBeanServer, on, clazz);
+ }
+
+ public <T> T newMBeanProxy(ObjectName on, Class<T> clazz) {
+ return JMX.newMBeanProxy(configMBeanServer, on, clazz);
+ }
+
+ @Override
+ public CommitStatus commit() throws ConflictingVersionException,
+ ValidationException {
+ return configTransactionManagerProxy
+ .commitConfig(configTransactionControllerON);
+ }
+
+ @Override
+ public void assertVersion(int expectedParentVersion,
+ int expectedCurrentVersion) {
+ if (expectedParentVersion != getParentVersion()) {
+ throw new IllegalStateException();
+ }
+ if (expectedCurrentVersion != getVersion()) {
+ throw new IllegalStateException();
+ }
+ }
+
+ // proxy around ConfigManagerMXBean
+ @Override
+ public ObjectName createModule(String moduleName, String instanceName)
+ throws InstanceAlreadyExistsException {
+ return configControllerProxy.createModule(moduleName, instanceName);
+ }
+
+ @Override
+ public void destroyModule(ObjectName objectName)
+ throws InstanceNotFoundException {
+ configControllerProxy.destroyModule(objectName);
+ }
+
+ @Override
+ public void destroyConfigBean(String moduleName, String instanceName)
+ throws InstanceNotFoundException {
+ destroyModule(ObjectNameUtil.createTransactionModuleON(
+ getTransactionName(), moduleName, instanceName));
+ }
+
+ @Override
+ public void abortConfig() {
+ configControllerProxy.abortConfig();
+ }
+
+ @Override
+ public void validateConfig() throws ValidationException {
+ configControllerProxy.validateConfig();
+ }
+
+ @Override
+ public long getParentVersion() {
+ try {
+ return (Long) configMBeanServer.getAttribute(
+ configTransactionControllerON, "ParentVersion");
+ } catch (JMException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public long getVersion() {
+ try {
+ return (Long) configMBeanServer.getAttribute(
+ configTransactionControllerON, "Version");
+ } catch (JMException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public String getTransactionName() {
+ return configControllerProxy.getTransactionName();
+ }
+
+ @Override
+ public Set<String> getAvailableModuleNames() {
+ return configControllerProxy.getAvailableModuleNames();
+ }
+
+ @Override
+ public ObjectName getObjectName() {
+ return configTransactionControllerON;
+ }
+
+ @Override
+ public Set<ObjectName> lookupConfigBeans() {
+ return configControllerProxy.lookupConfigBeans();
+ }
+
+ @Override
+ public Set<ObjectName> lookupConfigBeans(String moduleName) {
+ return configControllerProxy.lookupConfigBeans(moduleName);
+ }
+
+ @Override
+ public ObjectName lookupConfigBean(String moduleName, String instanceName)
+ throws InstanceNotFoundException {
+ return configControllerProxy.lookupConfigBean(moduleName, instanceName);
+ }
+
+ @Override
+ public Set<ObjectName> lookupConfigBeans(String moduleName,
+ String instanceName) {
+ return configControllerProxy
+ .lookupConfigBeans(moduleName, instanceName);
+ }
+
+ @Override
+ public void validateBean(ObjectName configBeanON)
+ throws ValidationException {
+ try {
+ configMBeanServer.invoke(configBeanON, "validate", null, null);
+ } catch (JMException e) {
+ throw new RuntimeException(e);
+ } catch (RuntimeMBeanException e) {
+ throw e.getTargetException();
+ }
+ }
+
+ @Override
+ public void setAttribute(ObjectName on, String attrName, Attribute attribute) {
+ if (ObjectNameUtil.getTransactionName(on) == null)
+ throw new IllegalArgumentException("Not in transaction instance "
+ + on + ", no transaction name present");
+
+ try {
+ configMBeanServer.setAttribute(on, attribute);
+ } catch (JMException e) {
+ throw new IllegalStateException("Unable to set attribute "
+ + attrName + " for " + on, e);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.util.jolokia;
+
+import java.util.List;
+import java.util.Set;
+
+import javax.management.ObjectName;
+
+import org.jolokia.client.request.J4pExecRequest;
+import org.jolokia.client.request.J4pReadRequest;
+import org.jolokia.client.request.J4pResponse;
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import org.opendaylight.controller.config.api.ConflictingVersionException;
+import org.opendaylight.controller.config.api.ValidationException;
+import org.opendaylight.controller.config.api.jmx.CommitStatus;
+import org.opendaylight.controller.config.api.jmx.ConfigRegistryMXBean;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.util.ConfigRegistryClient;
+
+public class ConfigRegistryJolokiaClient extends ListableJolokiaClient
+ implements ConfigRegistryClient {
+
+ public ConfigRegistryJolokiaClient(String url) {
+ super(url, ConfigRegistryMXBean.OBJECT_NAME);
+ }
+
+ @Override
+ public ConfigTransactionJolokiaClient createTransaction() {
+ // create transaction
+ J4pExecRequest execReq = new J4pExecRequest(objectName, "beginConfig");
+ J4pResponse<J4pExecRequest> resp = execute(execReq);
+ ObjectName transactionControllerON = extractObjectName(resp);
+ return getConfigTransactionClient(transactionControllerON);
+ }
+
+ @Override
+ public ConfigTransactionJolokiaClient getConfigTransactionClient(
+ String transactionName) {
+ ObjectName objectName = ObjectNameUtil
+ .createTransactionControllerON(transactionName);
+ return getConfigTransactionClient(objectName);
+ }
+
+ @Override
+ public ConfigTransactionJolokiaClient getConfigTransactionClient(
+ ObjectName objectName) {
+ return new ConfigTransactionJolokiaClient(url, objectName, this);
+ }
+
+ @Override
+ public CommitStatus commitConfig(ObjectName transactionControllerON)
+ throws ConflictingVersionException, ValidationException {
+ J4pExecRequest execReq = new J4pExecRequest(objectName, "commitConfig",
+ transactionControllerON);
+ JSONObject jsonObject;
+ jsonObject = execute(execReq).getValue();
+ JSONArray newInstancesArray = (JSONArray) jsonObject
+ .get("newInstances");
+ List<ObjectName> newInstances = jsonArrayToObjectNames(newInstancesArray);
+ JSONArray reusedInstancesArray = (JSONArray) jsonObject
+ .get("reusedInstances");
+ List<ObjectName> reusedInstances = jsonArrayToObjectNames(reusedInstancesArray);
+ JSONArray recreatedInstancesArray = (JSONArray) jsonObject
+ .get("recreatedInstances");
+ List<ObjectName> recreatedInstances = jsonArrayToObjectNames(recreatedInstancesArray);
+ return new CommitStatus(newInstances, reusedInstances,
+ recreatedInstances);
+ }
+
+ public Object getAttribute(ObjectName configBeanTransactionON, String key) {
+ J4pReadRequest req = new J4pReadRequest(configBeanTransactionON, key);
+ return execute(req).getValue();
+ }
+
+ public ObjectName getAttributeON(ObjectName configBeanTransactionON,
+ String key) {
+ JSONObject jsonAttrib = (JSONObject) getAttribute(
+ configBeanTransactionON, key);
+ return extractObjectName(jsonAttrib);
+ }
+
+ // proxy around ConfigTransactionManagerMXBean
+
+ @Override
+ public ObjectName beginConfig() {
+ ConfigTransactionJolokiaClient result = createTransaction();
+ return result.getTransactionON();
+ }
+
+ @Override
+ public List<ObjectName> getOpenConfigs() {
+ J4pReadRequest req = new J4pReadRequest(objectName, "OpenConfigs");
+ JSONArray jsonArray = execute(req).getValue();
+ return jsonArrayToObjectNames(jsonArray);
+ }
+
+ @Override
+ public long getVersion() {
+ J4pReadRequest req = new J4pReadRequest(objectName, "Version");
+ return (Long) execute(req).getValue();
+ }
+
+ @Override
+ public boolean isHealthy() {
+ J4pReadRequest req = new J4pReadRequest(objectName, "Healthy");
+ return (Boolean) execute(req).getValue();
+ }
+
+ @Override
+ public Set<ObjectName> lookupRuntimeBeans() {
+ return lookupSomething("lookupRuntimeBeans()", new Object[0]);
+ }
+
+ @Override
+ public Set<ObjectName> lookupRuntimeBeans(String moduleName,
+ String instanceName) {
+ return lookupSomething(
+ "lookupRuntimeBeans(java.lang.String,java.lang.String)",
+ new Object[] { moduleName, instanceName });
+ }
+
+ @Override
+ public Object invokeMethod(ObjectName on, String name, Object[] params,
+ String[] signature) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Object getAttributeCurrentValue(ObjectName on, String attributeName) {
+ throw new UnsupportedOperationException();
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.util.jolokia;
+
+import java.util.Map;
+
+import javax.management.Attribute;
+import javax.management.AttributeNotFoundException;
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.InstanceNotFoundException;
+import javax.management.ObjectName;
+
+import org.jolokia.client.request.J4pExecRequest;
+import org.jolokia.client.request.J4pReadRequest;
+import org.jolokia.client.request.J4pWriteRequest;
+import org.opendaylight.controller.config.api.ConflictingVersionException;
+import org.opendaylight.controller.config.api.ValidationException;
+import org.opendaylight.controller.config.api.jmx.CommitStatus;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.util.AttributeEntry;
+import org.opendaylight.controller.config.util.ConfigTransactionClient;
+
+public class ConfigTransactionJolokiaClient extends ListableJolokiaClient
+ implements ConfigTransactionClient {
+
+ private final ConfigRegistryJolokiaClient configRegistryJolokiaClient;
+
+ public ConfigTransactionJolokiaClient(String url,
+ ObjectName transactionControllerON,
+ ConfigRegistryJolokiaClient configRegistryJolokiaClient) {
+ super(url, transactionControllerON);
+ this.configRegistryJolokiaClient = configRegistryJolokiaClient;
+ }
+
+ public ObjectName getTransactionON() {
+ return objectName;
+ }
+
+ @Override
+ public CommitStatus commit() throws ConflictingVersionException,
+ ValidationException {
+ return configRegistryJolokiaClient.commitConfig(objectName);
+ }
+
+ @Override
+ public ObjectName createModule(String moduleName, String instanceName)
+ throws InstanceAlreadyExistsException {
+ J4pExecRequest execReq = new J4pExecRequest(objectName, "createModule",
+ moduleName, instanceName);
+ try {
+ return extractObjectName(execute(execReq));
+ } catch (RuntimeException e) {
+ if (e.getMessage() != null
+ && e.getMessage().startsWith(
+ InstanceAlreadyExistsException.class.getName()))
+ throw new InstanceAlreadyExistsException();
+ throw e;
+ }
+ }
+
+ @Override
+ public void destroyModule(ObjectName configBeanON) {
+ J4pExecRequest execReq = new J4pExecRequest(objectName,
+ "destroyModule(javax.management.ObjectName)", configBeanON);
+ execute(execReq);
+ }
+
+ @Override
+ public void destroyConfigBean(String moduleName, String instanceName)
+ throws InstanceNotFoundException {
+ destroyModule(ObjectNameUtil.createTransactionModuleON(
+ getTransactionName(), moduleName, instanceName));
+ }
+
+ @Override
+ public void abortConfig() {
+ J4pExecRequest execReq = new J4pExecRequest(objectName, "abortConfig");
+ execute(execReq);
+ }
+
+ @Override
+ public void validateConfig() throws ValidationException {
+ J4pExecRequest execReq = new J4pExecRequest(objectName,
+ "validateConfig");
+ execute(execReq);
+ }
+
+ @Override
+ public long getParentVersion() {
+ J4pReadRequest req = new J4pReadRequest(objectName, "ParentVersion");
+ return (Long) execute(req).getValue();
+ }
+
+ @Override
+ public long getVersion() {
+ J4pReadRequest req = new J4pReadRequest(objectName, "Version");
+ return (Long) execute(req).getValue();
+ }
+
+ public void setAttribute(ObjectName configBeanTransactionON, String key,
+ Object value) {
+ J4pWriteRequest req = new J4pWriteRequest(configBeanTransactionON, key,
+ value);
+ try {
+ execute(req);
+ } catch (RuntimeException e) {
+ if (e.getMessage() != null
+ && e.getMessage().startsWith(
+ AttributeNotFoundException.class.getName())) {
+ // try to fix wrong case
+ Map<String, AttributeEntry> allAttributes = getAttributes(configBeanTransactionON);
+ for (AttributeEntry attrib : allAttributes.values()) {
+ if (attrib.getKey().equalsIgnoreCase(key)) {
+ req = new J4pWriteRequest(configBeanTransactionON,
+ attrib.getKey(), value);
+ execute(req);
+ return;
+ }
+ }
+ }
+ throw e;
+ }
+ }
+
+ public Object getAttribute(ObjectName objectName, String key) {
+ return configRegistryJolokiaClient.getAttribute(objectName, key);
+ }
+
+ public ObjectName getAttributeON(ObjectName objectName, String key) {
+ return configRegistryJolokiaClient.getAttributeON(objectName, key);
+ }
+
+ @Override
+ public String getTransactionName() {
+ return ObjectNameUtil.getTransactionName(objectName);
+ }
+
+ @Override
+ public void validateBean(ObjectName rwON) throws ValidationException {
+ J4pExecRequest req = new J4pExecRequest(rwON, "validate", new Object[0]);
+ execute(req);
+ }
+
+ @Override
+ public void assertVersion(int expectedParentVersion,
+ int expectedCurrentVersion) {
+ if (expectedParentVersion != getParentVersion()) {
+ throw new IllegalStateException();
+ }
+ if (expectedCurrentVersion != getVersion()) {
+ throw new IllegalStateException();
+ }
+ }
+
+ @Override
+ public void setAttribute(ObjectName on, String jmxName, Attribute attribute) {
+ throw new UnsupportedOperationException();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.util.jolokia;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import javax.management.InstanceNotFoundException;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+
+import org.jolokia.client.J4pClient;
+import org.jolokia.client.exception.J4pException;
+import org.jolokia.client.exception.J4pRemoteException;
+import org.jolokia.client.request.J4pExecRequest;
+import org.jolokia.client.request.J4pListRequest;
+import org.jolokia.client.request.J4pQueryParameter;
+import org.jolokia.client.request.J4pReadRequest;
+import org.jolokia.client.request.J4pRequest;
+import org.jolokia.client.request.J4pResponse;
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import org.opendaylight.controller.config.api.ConflictingVersionException;
+import org.opendaylight.controller.config.api.ValidationException;
+import org.opendaylight.controller.config.api.ValidationException.ExceptionMessageWithStackTrace;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.util.AttributeEntry;
+
+abstract class ListableJolokiaClient {
+ protected final J4pClient j4pClient;
+ protected final String url;
+ protected final ObjectName objectName;
+
+ public ListableJolokiaClient(String url, ObjectName objectName) {
+ if (url == null) {
+ throw new NullPointerException("Parameter 'url' is null");
+ }
+ if (!url.endsWith("/")) {
+ throw new IllegalArgumentException(
+ "Parameter 'url' must end with '/'");
+ }
+
+ this.url = url;
+ this.j4pClient = new J4pClient(url);
+ this.objectName = objectName;
+ }
+
+ public ObjectName getObjectName() {
+ return objectName;
+ }
+
+ protected <R extends J4pResponse<T>, T extends J4pRequest> R execute(
+ T pRequest) {
+ try {
+ Map<J4pQueryParameter, String> pProcessingOptions = new HashMap<J4pQueryParameter, String>();
+ pProcessingOptions
+ .put(J4pQueryParameter.INCLUDE_STACKTRACE, "true");
+ pProcessingOptions.put(J4pQueryParameter.SERIALIZE_EXCEPTION,
+ "true");
+ return j4pClient.execute(pRequest, "POST", pProcessingOptions);
+ } catch (J4pRemoteException e) {
+ tryToConvertException(e.getRemoteStackTrace(), e.getErrorValue());
+ throw new RuntimeException(e.getRemoteStackTrace(), e);
+ } catch (J4pException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected void tryToConvertException(String remoteStackTrace,
+ JSONObject errorValue) {
+ String conflictPrefix = ConflictingVersionException.class.getName()
+ + ": ";
+ if (remoteStackTrace.startsWith(conflictPrefix)) {
+ remoteStackTrace = remoteStackTrace.substring(conflictPrefix
+ .length());
+ Pattern p = Pattern.compile("\r?\n");
+ remoteStackTrace = Arrays.asList(p.split(remoteStackTrace))
+ .iterator().next();
+ throw new ConflictingVersionException(remoteStackTrace);
+ }
+ String validationExceptionPrefix = ValidationException.class.getName();
+ if (remoteStackTrace.startsWith(validationExceptionPrefix)) {
+ throw createValidationExceptionFromJSONObject(errorValue);
+ }
+ }
+
+ static ValidationException createValidationExceptionFromJSONObject(
+ JSONObject errorValue) {
+ String fValsKey = "failedValidations";
+ JSONObject failedVals = (JSONObject) errorValue.get(fValsKey);
+
+ checkArgument(
+ !failedVals.isEmpty(),
+ fValsKey + " was not present in received JSON: "
+ + errorValue.toJSONString());
+ Map<String, Map<String, ExceptionMessageWithStackTrace>> failedValsMap = new HashMap<String, Map<String, ExceptionMessageWithStackTrace>>();
+
+ for (Object key : failedVals.keySet()) {
+ checkArgument(key instanceof String, "Unexpected key " + key
+ + ", expected instance of String");
+ Map<String, ExceptionMessageWithStackTrace> innerMap = new HashMap<String, ValidationException.ExceptionMessageWithStackTrace>();
+ for (Object innerKey : ((JSONObject) failedVals.get(key)).keySet()) {
+ checkArgument(innerKey instanceof String, "Unexpected key "
+ + innerKey + ", expected instance of String");
+ JSONObject exWithStackTraceVal = (JSONObject) (((JSONObject) failedVals
+ .get(key)).get(innerKey));
+ Object mess = exWithStackTraceVal.get("message");
+ Object stack = exWithStackTraceVal.get("trace");
+ checkArgument(mess != null && stack != null,
+ "\"Message\" and \"trace\" elements expected in received json: "
+ + errorValue.toJSONString());
+ innerMap.put(innerKey.toString(),
+ new ExceptionMessageWithStackTrace((String) mess,
+ (String) stack));
+ }
+ failedValsMap.put((String) key, innerMap);
+ }
+ return new ValidationException(failedValsMap);
+ }
+
+ private static void checkArgument(boolean b, String string) {
+ if (b == false)
+ throw new IllegalArgumentException(string);
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public Map<String, AttributeEntry> getAttributes(ObjectName on) {
+ J4pListRequest req = new J4pListRequest(on);
+ J4pResponse<J4pListRequest> response = execute(req);
+ JSONObject listJSONResponse = response.getValue();
+ JSONObject attributes = (JSONObject) listJSONResponse.get("attr");
+ Map<String, JSONObject> listMap = new HashMap<String, JSONObject>();
+ for (Object entryObject : attributes.entrySet()) {
+ Entry<String, Object> entry = (Entry<String, Object>) entryObject;
+ JSONObject entryVal = (JSONObject) entry.getValue();
+
+ // read value
+ listMap.put(entry.getKey(), entryVal);
+ }
+ J4pReadRequest j4pReadRequest = new J4pReadRequest(on, listMap.keySet()
+ .toArray(new String[0]));
+ J4pResponse<J4pReadRequest> readResponse = execute(j4pReadRequest);
+ Object readResponseValue = readResponse.getValue();
+ // readResponseValue can be String if there is just one attribute or
+ // JSONObject
+ Map<String, Object> attribsToValues = new HashMap<String, Object>();
+ if (readResponseValue instanceof JSONObject) {
+ JSONObject readJSONResponse = (JSONObject) readResponseValue;
+ for (Object entryObject : readJSONResponse.entrySet()) {
+ Entry<String, Object> entry = (Entry<String, Object>) entryObject;
+ String key = entry.getKey();
+ Object value = entry.getValue();
+ attribsToValues.put(key, value);
+ }
+ }
+
+ Map<String, AttributeEntry> resultMap = new HashMap<String, AttributeEntry>();
+ for (Entry<String, JSONObject> entry : listMap.entrySet()) {
+ String key = entry.getKey();
+ Object value = attribsToValues.size() > 0 ? attribsToValues
+ .get(key) : readResponseValue;
+ JSONObject listJSON = entry.getValue();
+ String description = (String) listJSON.get("desc");
+ String type = (String) listJSON.get("type");
+ boolean rw = (Boolean) listJSON.get("rw");
+ AttributeEntry attributeEntry = new AttributeEntry(key,
+ description, value, type, rw);
+ resultMap.put(key, attributeEntry);
+ }
+
+ return resultMap;
+ }
+
+ public String getConfigBeanDescripton(ObjectName on) {
+ J4pListRequest req = new J4pListRequest(on);
+ J4pResponse<J4pListRequest> response = execute(req);
+ JSONObject jsonDesc = response.getValue();
+ Object description = jsonDesc.get("desc");
+ return description == null ? null : description.toString();
+ }
+
+ protected List<ObjectName> jsonArrayToObjectNames(JSONArray jsonArray) {
+ List<ObjectName> result = new ArrayList<>(jsonArray.size());
+ for (Object entry : jsonArray) {
+ JSONObject jsonObject = (JSONObject) entry;
+ String objectNameString = (String) jsonObject.get("objectName");
+ try {
+ result.add(new ObjectName(objectNameString));
+ } catch (MalformedObjectNameException e) {
+ throw new IllegalStateException("Cannot convert "
+ + objectNameString + " to ObjectName", e);
+ }
+ }
+ return result;
+ }
+
+ protected ObjectName extractObjectName(J4pResponse<J4pExecRequest> resp) {
+ JSONObject jsonResponse = resp.getValue();
+ return extractObjectName(jsonResponse);
+ }
+
+ protected ObjectName extractObjectName(JSONObject jsonResponse) {
+ String result = jsonResponse.get("objectName").toString();
+ return ObjectNameUtil.createON(result);
+ }
+
+ protected Set<ObjectName> lookupSomething(String signature,
+ Object[] parameters) {
+ J4pExecRequest req = new J4pExecRequest(objectName, signature,
+ parameters);
+ JSONArray jsonArray = execute(req).getValue();
+ return new HashSet<>(jsonArrayToObjectNames(jsonArray));
+ }
+
+ public Set<ObjectName> lookupConfigBeans() {
+ return lookupSomething("lookupConfigBeans()", new Object[0]);
+ }
+
+ public Set<ObjectName> lookupConfigBeans(String ifcName) {
+ return lookupSomething("lookupConfigBeans(java.lang.String)",
+ new Object[] { ifcName });
+ }
+
+ public Set<ObjectName> lookupConfigBeans(String ifcName, String instanceName) {
+ return lookupSomething(
+ "lookupConfigBeans(java.lang.String,java.lang.String)",
+ new Object[] { ifcName, instanceName });
+ }
+
+ public ObjectName lookupConfigBean(String ifcName, String instanceName)
+ throws InstanceNotFoundException {
+ J4pExecRequest req = new J4pExecRequest(objectName,
+ "lookupConfigBean(java.lang.String,java.lang.String)",
+ new Object[] { ifcName, instanceName });
+ try {
+ J4pResponse<J4pExecRequest> resp = execute(req);
+ return extractObjectName(resp);
+ } catch (RuntimeException e) {
+ if (e.getMessage() != null
+ && e.getMessage().startsWith(
+ InstanceNotFoundException.class.getName()))
+ throw new InstanceNotFoundException();
+ throw e;
+ }
+ }
+
+ public Set<String> getAvailableModuleNames() {
+ J4pReadRequest req = new J4pReadRequest(objectName,
+ "AvailableModuleNames");
+ List<String> value = execute(req).getValue();
+ return new HashSet<>(value);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.util;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+import static org.junit.matchers.JUnitMatchers.containsString;
+
+import java.lang.management.ManagementFactory;
+import java.util.Set;
+
+import javax.management.InstanceNotFoundException;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.config.api.ConfigRegistry;
+import org.opendaylight.controller.config.api.ValidationException;
+import org.opendaylight.controller.config.util.jolokia.ConfigRegistryJolokiaClient;
+
+public class ConfigRegistryClientsTest {
+
+ private String jolokiaURL;
+
+ private TestingConfigRegistry testingRegistry;
+ private ObjectName testingRegistryON;
+ private final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+ private ConfigRegistryClient jmxRegistryClient, jolokiaRegistryClient;
+
+ @Before
+ public void setUp() throws Exception {
+ jolokiaURL = JolokiaHelper.startTestingJolokia();
+ testingRegistry = new TestingConfigRegistry();
+ testingRegistryON = ConfigRegistry.OBJECT_NAME;
+ mbs.registerMBean(testingRegistry, testingRegistryON);
+ jmxRegistryClient = new ConfigRegistryJMXClient(
+ ManagementFactory.getPlatformMBeanServer());
+ jolokiaRegistryClient = new ConfigRegistryJolokiaClient(jolokiaURL);
+ }
+
+ @After
+ public void cleanUp() throws Exception {
+ JolokiaHelper.stopJolokia();
+ if (testingRegistryON != null) {
+ mbs.unregisterMBean(testingRegistryON);
+ }
+ }
+
+ @Test
+ public void testLookupRuntimeBeans() throws Exception {
+ Set<ObjectName> jmxLookup = lookupRuntimeBeans(jmxRegistryClient);
+ Set<ObjectName> jolokiaLookup = lookupRuntimeBeans(jolokiaRegistryClient);
+ assertEquals(jmxLookup, jolokiaLookup);
+ }
+
+ private Set<ObjectName> lookupRuntimeBeans(ConfigRegistryClient client)
+ throws Exception {
+ Set<ObjectName> beans = client.lookupRuntimeBeans();
+ for (ObjectName on : beans) {
+ assertEquals("RuntimeBean", on.getKeyProperty("type"));
+ }
+ assertEquals(3, beans.size());
+ return beans;
+ }
+
+ @Test
+ public void testLookupRuntimeBeansWithIfcNameAndInstanceName()
+ throws InstanceNotFoundException {
+ Set<ObjectName> jmxLookup = clientLookupRuntimeBeansWithModuleAndInstance(
+ jmxRegistryClient, TestingConfigRegistry.moduleName1,
+ TestingConfigRegistry.instName1);
+ assertEquals(1, jmxLookup.size());
+ Set<ObjectName> jolokiaLookup = clientLookupRuntimeBeansWithModuleAndInstance(
+ jolokiaRegistryClient, TestingConfigRegistry.moduleName1,
+ TestingConfigRegistry.instName1);
+ assertEquals(jmxLookup, jolokiaLookup);
+
+ jmxLookup = clientLookupRuntimeBeansWithModuleAndInstance(
+ jmxRegistryClient, TestingConfigRegistry.moduleName2,
+ TestingConfigRegistry.instName2);
+ assertEquals(1, jmxLookup.size());
+ jolokiaLookup = clientLookupRuntimeBeansWithModuleAndInstance(
+ jolokiaRegistryClient, TestingConfigRegistry.moduleName2,
+ TestingConfigRegistry.instName2);
+ assertEquals(jmxLookup, jolokiaLookup);
+
+ jmxLookup = clientLookupRuntimeBeansWithModuleAndInstance(
+ jmxRegistryClient, TestingConfigRegistry.moduleName1,
+ TestingConfigRegistry.instName2);
+ assertEquals(0, jmxLookup.size());
+ jolokiaLookup = clientLookupRuntimeBeansWithModuleAndInstance(
+ jolokiaRegistryClient, TestingConfigRegistry.moduleName1,
+ TestingConfigRegistry.instName2);
+ assertEquals(jmxLookup, jolokiaLookup);
+ }
+
+ private Set<ObjectName> clientLookupRuntimeBeansWithModuleAndInstance(
+ ConfigRegistryClient client, String moduleName, String instanceName) {
+ Set<ObjectName> beans = client.lookupRuntimeBeans(moduleName, instanceName);
+ if (beans.size() > 0) {
+ assertEquals("RuntimeBean",
+ beans.iterator().next().getKeyProperty("type"));
+ }
+ return beans;
+ }
+
+ @Test
+ public void testValidationExceptionDeserialization() {
+ try {
+ jolokiaRegistryClient.commitConfig(null);
+ fail();
+ } catch (ValidationException e) {
+ String moduleName = "moduleName", instanceName = "instanceName";
+ assertThat(e.getFailedValidations().containsKey(moduleName),
+ is(true));
+ assertThat(e.getFailedValidations().size(), is(1));
+ assertThat(e.getFailedValidations().get(moduleName).size(), is(1));
+ assertThat(
+ e.getFailedValidations().get(moduleName)
+ .containsKey(instanceName), is(true));
+ assertThat(
+ e.getFailedValidations().get(moduleName).get(instanceName)
+ .getMessage(), is("message"));
+ assertThat(
+ e.getFailedValidations().get(moduleName).get(instanceName)
+ .getTrace(),
+ containsString("org.opendaylight.controller.config.util.TestingConfigRegistry.commitConfig"));
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.util;
+
+import static org.junit.Assert.assertEquals;
+
+import java.lang.management.ManagementFactory;
+import java.util.Set;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.config.api.jmx.ConfigTransactionControllerMXBean;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.util.jolokia.ConfigTransactionJolokiaClient;
+
+public class ConfigTransactionClientsTest {
+
+ private final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+ private String jolokiaURL;
+ private ConfigTransactionControllerMXBean transactionController;
+ private ObjectName transactionControllerON;
+ private ConfigTransactionClient jmxTransactionClient,
+ jolokiaTransactionClient;
+
+ @Before
+ public void setUp() throws Exception {
+ jolokiaURL = JolokiaHelper.startTestingJolokia();
+ transactionController = new TestingConfigTransactionController();
+ transactionControllerON = new ObjectName(ObjectNameUtil.ON_DOMAIN + ":"
+ + ObjectNameUtil.TYPE_KEY + "=TransactionController");
+ mbs.registerMBean(transactionController, transactionControllerON);
+ jmxTransactionClient = new ConfigTransactionJMXClient(null,
+ transactionControllerON,
+ ManagementFactory.getPlatformMBeanServer());
+ jolokiaTransactionClient = new ConfigTransactionJolokiaClient(
+ jolokiaURL, transactionControllerON, null);
+ }
+
+ @After
+ public void cleanUp() throws Exception {
+ JolokiaHelper.stopJolokia();
+ if (transactionControllerON != null) {
+ mbs.unregisterMBean(transactionControllerON);
+ }
+ }
+
+ @Test
+ public void testLookupConfigBeans() throws Exception {
+ Set<ObjectName> jmxLookup = testClientLookupConfigBeans(jmxTransactionClient);
+ Set<ObjectName> jolokiaLookup = testClientLookupConfigBeans(jolokiaTransactionClient);
+ assertEquals(jmxLookup, jolokiaLookup);
+ }
+
+ private Set<ObjectName> testClientLookupConfigBeans(
+ ConfigTransactionClient client) {
+ Set<ObjectName> beans = client.lookupConfigBeans();
+ for (ObjectName on : beans) {
+ assertEquals("Module", on.getKeyProperty("type"));
+ }
+ assertEquals(3, beans.size());
+ return beans;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.util;
+
+import java.io.IOException;
+
+import org.jolokia.jvmagent.JolokiaServer;
+import org.jolokia.jvmagent.JvmAgentConfig;
+
+public class JolokiaHelper {
+ private static JolokiaServer jolokiaServer;
+
+ /**
+ * Bind to port 17777. By convention, ports above 10000 are used for testing
+ * and < 10000 for production
+ *
+ * @return url that can be passed to new J4pClient(url)
+ */
+ public static String startTestingJolokia() {
+ return startJolokia("localhost", 17777);
+ }
+
+ /**
+ * @return url that can be passed to new J4pClient(url)
+ * @throws IOException
+ */
+ public static String startJolokia(String host, int port) {
+ String agentArgs = "host=" + host + ",port=" + port;
+ JvmAgentConfig config = new JvmAgentConfig(agentArgs);
+ Exception lastException = null;
+ for (int i = 0; i < 10; i++) {
+ try {
+ jolokiaServer = new JolokiaServer(config, false);
+ jolokiaServer.start();
+ return "http://" + host + ":" + port + "/jolokia/";
+ } catch (Exception e) {
+ lastException = e;
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ie) {
+ ie.printStackTrace();
+ }
+ }
+ }
+ throw new RuntimeException(lastException);
+ }
+
+ public static void stopJolokia() {
+ jolokiaServer.stop();
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.lang.management.ManagementFactory;
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.management.InstanceNotFoundException;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.config.api.ConfigRegistry;
+import org.opendaylight.controller.config.api.LookupRegistry;
+import org.opendaylight.controller.config.api.jmx.ConfigTransactionControllerMXBean;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.util.jolokia.ConfigRegistryJolokiaClient;
+import org.opendaylight.controller.config.util.jolokia.ConfigTransactionJolokiaClient;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Sets;
+
+public class LookupTest {
+
+ private String jolokiaURL;
+ private TestingConfigRegistry testingRegistry;
+ private ObjectName testingRegistryON;
+ private final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+ private ConfigRegistryClient jmxRegistryClient, jolokiaRegistryClient;
+ private ConfigTransactionControllerMXBean testingTransactionController;
+ private ObjectName testingTransactionControllerON;
+ private ConfigTransactionClient jmxTransactionClient,
+ jolokiaTransactionClient;
+
+ Map<LookupRegistry, ? extends Set<? extends LookupRegistry>> lookupProvidersToClients;
+
+ @Before
+ public void setUp() throws Exception {
+ jolokiaURL = JolokiaHelper.startTestingJolokia();
+ testingRegistry = new TestingConfigRegistry();
+ testingRegistryON = ConfigRegistry.OBJECT_NAME;
+ mbs.registerMBean(testingRegistry, testingRegistryON);
+ jmxRegistryClient = new ConfigRegistryJMXClient(
+ ManagementFactory.getPlatformMBeanServer());
+ jolokiaRegistryClient = new ConfigRegistryJolokiaClient(jolokiaURL);
+
+ testingTransactionController = new TestingConfigTransactionController();
+ testingTransactionControllerON = new ObjectName(
+ ObjectNameUtil.ON_DOMAIN + ":" + ObjectNameUtil.TYPE_KEY
+ + "=TransactionController");
+ mbs.registerMBean(testingTransactionController,
+ testingTransactionControllerON);
+
+ jmxTransactionClient = new ConfigTransactionJMXClient(null,
+ testingTransactionControllerON,
+ ManagementFactory.getPlatformMBeanServer());
+ jolokiaTransactionClient = new ConfigTransactionJolokiaClient(
+ jolokiaURL, testingTransactionControllerON, null);
+ lookupProvidersToClients = ImmutableMap
+ .of(testingRegistry, Sets.newHashSet(jmxRegistryClient, jolokiaRegistryClient),
+ testingTransactionController, Sets.newHashSet(jmxTransactionClient, jolokiaTransactionClient));
+ }
+
+ @After
+ public void cleanUp() throws Exception {
+ JolokiaHelper.stopJolokia();
+ mbs.unregisterMBean(testingRegistryON);
+ mbs.unregisterMBean(testingTransactionControllerON);
+ }
+
+ @Test
+ public void testLookupConfigBeans() throws Exception {
+ Method method = LookupRegistry.class.getMethod("lookupConfigBeans");
+ Object[] args = new Object[0];
+ test(method, args);
+ }
+
+ @Test
+ public void testLookupConfigBeans1() throws Exception {
+ Method method = LookupRegistry.class.getMethod("lookupConfigBeans",
+ String.class);
+ Object[] args = new Object[] { TestingConfigRegistry.moduleName1 };
+ test(method, args);
+ }
+
+ @Test
+ public void testLookupConfigBeans2() throws Exception {
+ Method method = LookupRegistry.class.getMethod("lookupConfigBeans",
+ String.class, String.class);
+ Object[] args = new Object[] { TestingConfigRegistry.moduleName1,
+ TestingConfigRegistry.instName1 };
+ test(method, args);
+ }
+
+ @Test
+ public void testLookupConfigBean() throws Exception {
+ Method method = LookupRegistry.class.getMethod("lookupConfigBean",
+ String.class, String.class);
+ Object[] args = new Object[] { TestingConfigRegistry.moduleName1,
+ TestingConfigRegistry.instName1 };
+ test(method, args);
+ }
+
+ private void test(Method method, Object[] args) throws Exception {
+ for (Entry<LookupRegistry, ? extends Set<? extends LookupRegistry>> entry : lookupProvidersToClients
+ .entrySet()) {
+ Object expected = method.invoke(entry.getKey(), args);
+ for (LookupRegistry client : entry.getValue()) {
+ Object actual = method.invoke(client, args);
+ assertEquals("Error while comparing " + entry.getKey()
+ + " with client " + client, expected, actual);
+ }
+ }
+ }
+
+ @Test
+ public void testException() {
+ for (Entry<LookupRegistry, ? extends Set<? extends LookupRegistry>> entry : lookupProvidersToClients
+ .entrySet()) {
+ for (LookupRegistry client : entry.getValue()) {
+ try {
+ client.lookupConfigBean(
+ InstanceNotFoundException.class.getSimpleName(), "");
+ fail(client.toString());
+ } catch (InstanceNotFoundException e) {
+
+ }
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.util;
+
+public class TestingBeanImpl implements TestingBeanMXBean {
+
+ @Override
+ public int getStat() {
+ return 0;
+ }
+
+ @Override
+ public void setStat() {
+
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.util;
+
+public interface TestingBeanMXBean {
+
+ int getStat();
+
+ void setStat();
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.util;
+
+import java.util.List;
+import java.util.Set;
+
+import javax.management.InstanceNotFoundException;
+import javax.management.ObjectName;
+
+import org.opendaylight.controller.config.api.ConflictingVersionException;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.ValidationException;
+import org.opendaylight.controller.config.api.jmx.CommitStatus;
+import org.opendaylight.controller.config.api.jmx.ConfigRegistryMXBean;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+
+import com.google.common.collect.Sets;
+
+public class TestingConfigRegistry implements ConfigRegistryMXBean {
+
+ static final ObjectName conf1, conf2, conf3, run1, run2, run3;
+
+ public static final String moduleName1 = "moduleA";
+ public static final String moduleName2 = "moduleB";
+ public static final String instName1 = "instA";
+ public static final String instName2 = "instB";
+
+ static {
+ conf1 = ObjectNameUtil.createON(ObjectNameUtil.ON_DOMAIN
+ + ":type=Module," + ObjectNameUtil.MODULE_FACTORY_NAME_KEY
+ + "=" + moduleName1);
+ conf2 = ObjectNameUtil.createON(ObjectNameUtil.ON_DOMAIN
+ + ":type=Module," + ObjectNameUtil.MODULE_FACTORY_NAME_KEY
+ + "=" + moduleName1 + "," + ObjectNameUtil.INSTANCE_NAME_KEY
+ + "=" + instName1);
+ conf3 = ObjectNameUtil.createON(ObjectNameUtil.ON_DOMAIN
+ + ":type=Module," + ObjectNameUtil.MODULE_FACTORY_NAME_KEY
+ + "=" + moduleName2 + "," + ObjectNameUtil.INSTANCE_NAME_KEY
+ + "=" + instName2);
+ run1 = ObjectNameUtil.createON(ObjectNameUtil.ON_DOMAIN
+ + ":type=RuntimeBean," + ObjectNameUtil.MODULE_FACTORY_NAME_KEY
+ + "=" + moduleName1);
+ run2 = ObjectNameUtil.createON(ObjectNameUtil.ON_DOMAIN
+ + ":type=RuntimeBean," + ObjectNameUtil.MODULE_FACTORY_NAME_KEY
+ + "=" + moduleName1 + "," + ObjectNameUtil.INSTANCE_NAME_KEY
+ + "=" + instName1);
+ run3 = ObjectNameUtil.createON(ObjectNameUtil.ON_DOMAIN
+ + ":type=RuntimeBean," + ObjectNameUtil.MODULE_FACTORY_NAME_KEY
+ + "=" + moduleName2 + "," + ObjectNameUtil.INSTANCE_NAME_KEY
+ + "=" + instName2);
+ }
+
+ @Override
+ public ObjectName beginConfig() {
+ return null;
+ }
+
+ @Override
+ public CommitStatus commitConfig(ObjectName transactonControllerON)
+ throws ConflictingVersionException, ValidationException {
+ if (transactonControllerON == null) {
+ Exception e = new RuntimeException("message");
+ throw ValidationException.createForSingleException(
+ new ModuleIdentifier("moduleName", "instanceName"), e);
+ }
+ return null;
+ }
+
+ @Override
+ public List<ObjectName> getOpenConfigs() {
+ return null;
+ }
+
+ @Override
+ public boolean isHealthy() {
+ return false;
+ }
+
+ @Override
+ public Set<String> getAvailableModuleNames() {
+ return null;
+ }
+
+ @Override
+ public Set<ObjectName> lookupConfigBeans() {
+ return Sets.newHashSet(conf1, conf2, conf3);
+ }
+
+ @Override
+ public Set<ObjectName> lookupConfigBeans(String moduleName) {
+ if (moduleName.equals(moduleName1)) {
+ return Sets.newHashSet(conf1, conf2);
+ } else if (moduleName.equals(moduleName2)) {
+ return Sets.newHashSet(conf3);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public Set<ObjectName> lookupConfigBeans(String moduleName,
+ String instanceName) {
+ if (moduleName.equals(moduleName1) && instanceName.equals(instName1)) {
+ return Sets.newHashSet(conf2);
+ } else if (moduleName.equals(moduleName2)
+ && instanceName.equals(instName2)) {
+ return Sets.newHashSet(conf3);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public ObjectName lookupConfigBean(String moduleName, String instanceName)
+ throws InstanceNotFoundException {
+ if (moduleName.equals(InstanceNotFoundException.class.getSimpleName())) {
+ throw new InstanceNotFoundException();
+ }
+ return conf3;
+ }
+
+ @Override
+ public Set<ObjectName> lookupRuntimeBeans() {
+ return Sets.<ObjectName> newHashSet(run1, run2, run3);
+ }
+
+ @Override
+ public Set<ObjectName> lookupRuntimeBeans(String moduleName,
+ String instanceName) {
+ if (moduleName.equals(moduleName1) && instanceName.equals(instName1)) {
+ return Sets.<ObjectName> newHashSet(run2);
+ } else if (moduleName.equals(moduleName2)
+ && instanceName.equals(instName2)) {
+ return Sets.<ObjectName> newHashSet(run3);
+ } else {
+ return Sets.<ObjectName> newHashSet();
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.util;
+
+import java.util.Set;
+
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.InstanceNotFoundException;
+import javax.management.ObjectName;
+
+import org.opendaylight.controller.config.api.ValidationException;
+import org.opendaylight.controller.config.api.jmx.ConfigTransactionControllerMXBean;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+
+import com.google.common.collect.Sets;
+
+public class TestingConfigTransactionController implements
+ ConfigTransactionControllerMXBean {
+
+ private final ObjectName conf1, conf2, conf3;
+
+ public static final String moduleName1 = "moduleA";
+ public static final String moduleName2 = "moduleB";
+ public static final String instName1 = "instA";
+ public static final String instName2 = "instB";
+
+ public TestingConfigTransactionController() throws Exception {
+ conf1 = ObjectNameUtil.createON(ObjectNameUtil.ON_DOMAIN
+ + ":type=Module," + ObjectNameUtil.MODULE_FACTORY_NAME_KEY
+ + "=" + moduleName1);
+ conf2 = ObjectNameUtil.createON(ObjectNameUtil.ON_DOMAIN
+ + ":type=Module," + ObjectNameUtil.MODULE_FACTORY_NAME_KEY
+ + "=" + moduleName1 + "," + ObjectNameUtil.INSTANCE_NAME_KEY
+ + "=" + instName1);
+ conf3 = ObjectNameUtil.createON(ObjectNameUtil.ON_DOMAIN
+ + ":type=Module," + ObjectNameUtil.MODULE_FACTORY_NAME_KEY
+ + "=" + moduleName2 + "," + ObjectNameUtil.INSTANCE_NAME_KEY
+ + "=" + instName2);
+ }
+
+ @Override
+ public ObjectName createModule(String moduleName, String instanceName)
+ throws InstanceAlreadyExistsException {
+ return null;
+ }
+
+ @Override
+ public void destroyModule(ObjectName objectName)
+ throws InstanceNotFoundException {
+ }
+
+ @Override
+ public void abortConfig() {
+ }
+
+ @Override
+ public void validateConfig() throws ValidationException {
+ }
+
+ @Override
+ public String getTransactionName() {
+ return null;
+ }
+
+ @Override
+ public Set<String> getAvailableModuleNames() {
+ return null;
+ }
+
+ @Override
+ public Set<ObjectName> lookupConfigBeans() {
+ return Sets.newHashSet(conf1, conf2, conf3);
+ }
+
+ @Override
+ public Set<ObjectName> lookupConfigBeans(String moduleName) {
+ if (moduleName.equals(moduleName1)) {
+ return Sets.newHashSet(conf1, conf2);
+ } else if (moduleName.equals(moduleName2)) {
+ return Sets.newHashSet(conf3);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public ObjectName lookupConfigBean(String moduleName, String instanceName)
+ throws InstanceNotFoundException {
+ if (moduleName.equals(InstanceNotFoundException.class.getSimpleName())) {
+ throw new InstanceNotFoundException();
+ }
+ return conf3;
+ }
+
+ @Override
+ public Set<ObjectName> lookupConfigBeans(String moduleName,
+ String instanceName) {
+ if (moduleName.equals(moduleName1) && instanceName.equals(instName1)) {
+ return Sets.newHashSet(conf2);
+ } else if (moduleName.equals(moduleName2)
+ && instanceName.equals(instName2)) {
+ return Sets.newHashSet(conf3);
+ } else {
+ return null;
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.util.jolokia;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.json.simple.JSONObject;
+import org.json.simple.JSONValue;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.config.api.ValidationException;
+import org.opendaylight.controller.config.api.ValidationException.ExceptionMessageWithStackTrace;
+
+public class ListableJolokiaClientTest {
+
+ private Map<String, Map<String, ExceptionMessageWithStackTrace>> failedValidations;
+
+ private ValidationException val;
+
+ private final static String ex = "{\"message\":null,"
+ + "\"failedValidations\":"
+ + "{\"ifc2\":{\"impl1\":{\"message\":\"abc\",\"trace\":\"vvv\"},"
+ + "\"impl2\":{\"message\":\"abc2\",\"trace\":\"vvv2\"}},"
+ + "\"ifc1\":"
+ + "{\"impl1\":{\"message\":\"abc\",\"trace\":\"vvv\"},"
+ + "\"impl2\":{\"message\":\"abc2\",\"trace\":\"vvv2\"}}},"
+ + "\"localizedMessage\":null," + "\"cause\":null}";
+
+ @Before
+ public void setUp() {
+ failedValidations = new HashMap<String, Map<String, ExceptionMessageWithStackTrace>>();
+ Map<String, ExceptionMessageWithStackTrace> map1 = new HashMap<String, ValidationException.ExceptionMessageWithStackTrace>();
+ map1.put("impl1", new ExceptionMessageWithStackTrace("abc", "vvv"));
+ map1.put("impl2", new ExceptionMessageWithStackTrace("abc2", "vvv2"));
+ failedValidations.put("ifc1", map1);
+ failedValidations.put("ifc2", map1);
+ val = new ValidationException(failedValidations);
+ }
+
+ @Test
+ public void testParsing() {
+ JSONObject e = (JSONObject) JSONValue.parse(ex);
+ ValidationException val2 = ListableJolokiaClient
+ .createValidationExceptionFromJSONObject(e);
+ assertThat(val2.getMessage(), is(val.getMessage()));
+ assertThat(val2.getFailedValidations(), is(val.getFailedValidations()));
+ }
+
+}
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>commons.opendaylight</artifactId>
+ <version>1.4.0-SNAPSHOT</version>
+ <relativePath>../commons/opendaylight</relativePath>
+ </parent>
+
+
+ <groupId>org.opendaylight</groupId>
+ <version>0.2.1-SNAPSHOT</version>
+ <artifactId>config-subsystem</artifactId>
+ <packaging>pom</packaging>
+ <name>${project.artifactId}</name>
+ <prerequisites>
+ <maven>3.0.4</maven>
+ </prerequisites>
+ <modules>
+ <module>config-api</module>
+ <module>config-manager</module>
+ <module>config-util</module>
+ <module>yang-jmx-generator</module>
+ <module>yang-jmx-generator-plugin</module>
+ <module>yang-jmx-generator-it</module>
+ <module>yang-store-api</module>
+ <module>yang-store-impl</module>
+ <module>yang-test</module>
+ </modules>
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <java.version.source>1.7</java.version.source>
+ <java.version.target>1.7</java.version.target>
+ <junit.version>4.10</junit.version>
+ <maven.bundle.version>2.3.7</maven.bundle.version>
+ <osgi.version>5.0.0</osgi.version>
+ <jacoco.version>0.6.2.201302030002</jacoco.version>
+ <slf4j.version>1.7.2</slf4j.version>
+ <jolokia.version>1.1.1</jolokia.version>
+ <opendaylight.yang.version>0.5.7-SNAPSHOT</opendaylight.yang.version>
+ <opendaylight.binding.version>0.5.7-SNAPSHOT</opendaylight.binding.version>
+ <jmxGeneratorPath>${project.build.directory}/generated-sources/config</jmxGeneratorPath>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ <version>${logback.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>${slf4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <version>${osgi.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ <version>2.0.1</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>2.4</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>14.0.1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.jolokia</groupId>
+ <artifactId>jolokia-core</artifactId>
+ <version>${jolokia.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.jolokia</groupId>
+ <artifactId>jolokia-jvm</artifactId>
+ <version>${jolokia.version}</version>
+ <classifier>agent</classifier>
+ </dependency>
+ <dependency>
+ <groupId>org.jolokia</groupId>
+ <artifactId>jolokia-client-java</artifactId>
+ <version>${jolokia.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ <version>${commons.lang.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>2.5.1</version>
+ <configuration>
+ <source>${java.version.source}</source>
+ <target>${java.version.target}</target>
+ <testSource>${java.version.source}</testSource>
+ <testTarget>${java.version.target}</testTarget>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.jacoco</groupId>
+ <artifactId>jacoco-maven-plugin</artifactId>
+ <version>${jacoco.version}</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>prepare-agent</goal>
+ </goals>
+ </execution>
+ <execution>
+ <id>report</id>
+ <phase>prepare-package</phase>
+ <goals>
+ <goal>check</goal>
+ <goal>report</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${basedir}/target/jacoco</outputDirectory>
+ <haltOnFailure>false</haltOnFailure>
+ <check>
+ <classRatio>80</classRatio>
+ </check>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ </plugin>
+ </plugins>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-maven-plugin</artifactId>
+ <version>${opendaylight.yang.version}</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>generate-sources</goal>
+ </goals>
+ <configuration>
+ <codeGenerators>
+ <generator>
+ <codeGeneratorClass>
+ org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator
+ </codeGeneratorClass>
+ <outputBaseDir>${jmxGeneratorPath}</outputBaseDir>
+ <additionalConfiguration>
+ <namespaceToPackage1>
+ urn:opendaylight:params:xml:ns:yang:controller==org.opendaylight.controller.config.yang
+ </namespaceToPackage1>
+ </additionalConfiguration>
+ </generator>
+ </codeGenerators>
+ <inspectDependencies>true</inspectDependencies>
+ </configuration>
+ </execution>
+ </executions>
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight</groupId>
+ <artifactId>yang-jmx-generator-plugin</artifactId>
+ <version>0.2.1-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+ </plugin>
+ <!-- tell eclipse about generated source folders -->
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <version>1.8</version>
+ <executions>
+ <execution>
+ <id>add-source</id>
+ <phase>generate-sources</phase>
+ <goals>
+ <goal>add-source</goal>
+ </goals>
+ <configuration>
+ <sources>
+ <source>${jmxGeneratorPath}</source>
+ </sources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>2.4</version>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>test-jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <version>${maven.bundle.version}</version>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-Name>${project.groupId}.${project.artifactId}</Bundle-Name>
+ </instructions>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>2.14.1</version>
+ <configuration>
+ <redirectTestOutputToFile>true</redirectTestOutputToFile>
+ <parallel>classes</parallel>
+ <forkCount>1C</forkCount>
+ <reuseForks>false</reuseForks>
+ <perCoreThreadCount>true</perCoreThreadCount>
+ <threadCount>2</threadCount>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-antrun-plugin</artifactId>
+ <version>1.3</version>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.groovy.maven</groupId>
+ <artifactId>gmaven-plugin</artifactId>
+ <version>1.0</version>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+
+ <pluginRepositories>
+ <pluginRepository>
+ <id>nexus.opendaylight.org</id>
+ <url>http://nexus.opendaylight.org/content/repositories/opendaylight.snapshot</url>
+ <releases>
+ <enabled>true</enabled>
+ </releases>
+ <snapshots>
+ <updatePolicy>daily</updatePolicy>
+ </snapshots>
+ </pluginRepository>
+ </pluginRepositories>
+
+ <repositories>
+ <repository>
+ <id>opendaylight-snapshot</id>
+ <url>http://nexus.opendaylight.org/content/repositories/opendaylight.snapshot</url>
+ <snapshots>
+ <updatePolicy>daily</updatePolicy>
+ </snapshots>
+ </repository>
+ </repositories>
+</project>
--- /dev/null
+<?xml version="1.0"?>
+<project
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+ xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <artifactId>config-subsystem</artifactId>
+ <groupId>org.opendaylight</groupId>
+ <version>0.2.1-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>yang-jmx-generator-it</artifactId>
+ <name>${project.artifactId}</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>config-api</artifactId>
+ <version>0.2.1-SNAPSHOT</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>yang-test</artifactId>
+ <version>0.2.1-SNAPSHOT</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>config-manager</artifactId>
+ <version>0.2.1-SNAPSHOT</version>
+ <scope>test</scope>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>config-manager</artifactId>
+ <version>0.2.1-SNAPSHOT</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>config-util</artifactId>
+ <version>0.2.1-SNAPSHOT</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.bgpcep</groupId>
+ <artifactId>mockito-configuration</artifactId>
+ <version>0.2.0-SNAPSHOT</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <forkCount>1</forkCount>
+ <reuseForks>false</reuseForks>
+ <perCoreThreadCount>false</perCoreThreadCount>
+ </configuration>
+ <executions>
+ <execution>
+ <id>default-test</id>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </execution>
+ <execution>
+ <id>integration-tests</id>
+ <phase>integration-test</phase>
+ <goals>
+ <goal>test</goal>
+ </goals>
+ <configuration>
+ <includes>
+ <include>**/org/opendaylight/controller/config/yangjmxgenerator/it/*.java</include>
+ </includes>
+ <skip>false</skip>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.it;
+
+import static org.junit.Assert.fail;
+
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.ObjectName;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.opendaylight.controller.config.api.ConflictingVersionException;
+import org.opendaylight.controller.config.api.ValidationException;
+import org.opendaylight.controller.config.api.jmx.CommitStatus;
+import org.opendaylight.controller.config.manager.impl.AbstractConfigTest;
+import org.opendaylight.controller.config.manager.impl.factoriesresolver.HardcodedModuleFactoriesResolver;
+import org.opendaylight.controller.config.util.ConfigTransactionJMXClient;
+import org.opendaylight.controller.config.yang.test.impl.DtoA;
+import org.opendaylight.controller.config.yang.test.impl.DtoB;
+import org.opendaylight.controller.config.yang.test.impl.TestImplModuleFactory;
+import org.opendaylight.controller.config.yang.test.impl.TestImplModuleMXBean;
+
+@Ignore
+// ietf beans are not JMX compliant beans:
+// Do not know how to make a
+// org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev2010924.AsNumber
+// from a CompositeData: no method from(CompositeData); no constructor has
+// @ConstructorProperties annotation; does not have a public no-arg constructor;
+// not an interface
+public class ITTest extends AbstractConfigTest {
+
+ private TestImplModuleFactory factory;
+ private final String instanceName = "instance";
+
+ @Before
+ public void setUp() {
+
+ factory = new TestImplModuleFactory();
+ super.initConfigTransactionManagerImpl(new HardcodedModuleFactoriesResolver(
+ factory));
+ }
+
+ @Test
+ public void testCreateBean() throws InstanceAlreadyExistsException,
+ ValidationException, ConflictingVersionException {
+ ConfigTransactionJMXClient transaction = configRegistryClient
+ .createTransaction();
+
+ createModule(transaction, instanceName);
+ transaction.validateConfig();
+ CommitStatus status = transaction.commit();
+
+ assertBeanCount(1, factory.getImplementationName());
+ assertStatus(status, 1, 0, 0);
+ }
+
+ @Test
+ public void testReusingOldInstance() throws InstanceAlreadyExistsException,
+ ConflictingVersionException, ValidationException {
+
+ ConfigTransactionJMXClient transaction = configRegistryClient
+ .createTransaction();
+ createModule(transaction, instanceName);
+
+ transaction.commit();
+
+ assertBeanCount(1, factory.getImplementationName());
+
+ transaction = configRegistryClient.createTransaction();
+ CommitStatus status = transaction.commit();
+
+ assertBeanCount(1, factory.getImplementationName());
+ assertStatus(status, 0, 0, 1);
+
+ }
+
+ @Test
+ public void testInstanceAlreadyExistsException()
+ throws ConflictingVersionException, ValidationException,
+ InstanceAlreadyExistsException {
+ ConfigTransactionJMXClient transaction = configRegistryClient
+ .createTransaction();
+
+ createModule(transaction, instanceName);
+ transaction.commit();
+
+ transaction = configRegistryClient.createTransaction();
+ try {
+ createModule(transaction, instanceName);
+ fail();
+ } catch (InstanceAlreadyExistsException e) {
+
+ }
+ }
+
+ private ObjectName createModule(ConfigTransactionJMXClient transaction,
+ String instanceName) throws InstanceAlreadyExistsException {
+ ObjectName nameCreated = transaction.createModule(
+ factory.getImplementationName(), instanceName);
+ TestImplModuleMXBean mxBean = transaction.newMBeanProxy(nameCreated,
+ TestImplModuleMXBean.class);
+ mxBean.setSimpleInt((long) 45);
+ // mxBean.setAsNumber(new AsNumber((long) 999));
+ mxBean.setDtoA(new DtoA());
+ mxBean.setDtoB(new DtoB());
+ return nameCreated;
+
+ }
+
+}
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <artifactId>config-subsystem</artifactId>
+ <groupId>org.opendaylight</groupId>
+ <version>0.2.1-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+ <artifactId>yang-jmx-generator-plugin</artifactId>
+ <dependencies>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight</groupId>
+ <artifactId>yang-jmx-generator</artifactId>
+ <version>0.2.1-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-maven-plugin-spi</artifactId>
+ <version>${opendaylight.yang.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>binding-generator-impl</artifactId>
+ <version>${opendaylight.binding.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jdt</groupId>
+ <artifactId>core</artifactId>
+ <version>3.3.0-v_771</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.freemarker</groupId>
+ <artifactId>freemarker</artifactId>
+ <version>2.3.20</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>config-api</artifactId>
+ <version>0.2.1-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ </dependency>
+ <dependency>
+ <!--FIXME two implementations of slf4j on classpath, logback classic from parent-->
+ <groupId>com.googlecode.slf4j-maven-plugin-log</groupId>
+ <artifactId>slf4j-maven-plugin-log</artifactId>
+ <version>1.0.0</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.opendaylight</groupId>
+ <artifactId>yang-jmx-generator</artifactId>
+ <version>0.2.1-SNAPSHOT</version>
+ <scope>test</scope>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse</groupId>
+ <artifactId>jdt</artifactId>
+ <version>3.3.0-v20070607-1300</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.bgpcep</groupId>
+ <artifactId>mockito-configuration</artifactId>
+ <version>0.2.0-SNAPSHOT</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+</project>
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin;
+
+import java.io.File;
+import java.util.List;
+
+import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntry;
+import org.opendaylight.controller.config.yangjmxgenerator.ServiceInterfaceEntry;
+
+public interface CodeWriter {
+
+ File writeSie(ServiceInterfaceEntry sie, File targetBaseDir);
+
+ List<File> writeMbe(ModuleMXBeanEntry mbe, File targetBaseDir,
+ File mainBaseDir, File resourceBaseDir);
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntry;
+import org.opendaylight.controller.config.yangjmxgenerator.RuntimeBeanEntry;
+import org.opendaylight.controller.config.yangjmxgenerator.ServiceInterfaceEntry;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.FtlFilePersister;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.FtlTemplate;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.RuntimeRegistratorFtlTemplate;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.TemplateFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.Lists;
+
+final class FreeMarkerCodeWriterImpl implements CodeWriter {
+
+ private static final Logger logger = LoggerFactory
+ .getLogger(FreeMarkerCodeWriterImpl.class);
+
+ private final FtlFilePersister ftlFilePersister = new FtlFilePersister();
+
+ public FreeMarkerCodeWriterImpl() {
+ }
+
+ @Override
+ public File writeSie(ServiceInterfaceEntry sie, File outputBaseDir) {
+ try {
+ Collection<FtlTemplate> values = TemplateFactory.getFtlTemplates(
+ sie).values();
+ return ftlFilePersister.persist(values, outputBaseDir, true).get(0);
+ } catch (Exception e) {
+ String message = "An error occurred during Service interface generating, sie:"
+ + sie.getTypeName() + ", " + sie.getFullyQualifiedName();
+ logger.error(message, e);
+ throw new RuntimeException(message, e);
+ }
+ }
+
+ @Override
+ public List<File> writeMbe(ModuleMXBeanEntry mbe, File targetBaseDir,
+ File mainBaseDir, File resourceBaseDir) {
+ try {
+ List<File> generatedFiles = Lists.newArrayList();
+
+ generatedFiles.addAll(ftlFilePersister.persist(TemplateFactory
+ .getFtlTemplates(mbe).values(), targetBaseDir, true));
+ generatedFiles.addAll(ftlFilePersister.persist(TemplateFactory
+ .getFtlStubTemplates(mbe).values(), mainBaseDir, false));
+
+ // write runtime bean MXBeans and registrators
+ Collection<RuntimeBeanEntry> runtimeBeans = mbe.getRuntimeBeans();
+ if (runtimeBeans.size() > 0) {
+ List<FtlTemplate> allFtlFiles = new ArrayList<>();
+ { // registrators
+ Map<String, FtlTemplate> registratorNamesToFtls = RuntimeRegistratorFtlTemplate
+ .create(RuntimeRegistratorFtlTemplate.findRoot(runtimeBeans));
+
+ allFtlFiles.addAll(registratorNamesToFtls.values());
+ }
+ { // TOs, MXBean interfaces
+ for (RuntimeBeanEntry runtimeBeanEntry : runtimeBeans) {
+ Collection<FtlTemplate> ftlFiles = TemplateFactory
+ .getTOAndMXInterfaceFtlFiles(runtimeBeanEntry)
+ .values();
+ allFtlFiles.addAll(ftlFiles);
+ }
+ }
+ boolean overwrite = true;
+
+ FtlFilePersister ftlFilePersister = new FtlFilePersister();
+ List<File> persisted = ftlFilePersister.persist(allFtlFiles,
+ targetBaseDir, overwrite);
+ // FIXME: check for intersection
+ generatedFiles.addAll(persisted);
+ }
+
+ // purge nulls
+ for (Iterator<File> it = generatedFiles.iterator(); it.hasNext();) {
+ if (it.next() == null) {
+ it.remove();
+ }
+ }
+
+ return generatedFiles;
+
+ } catch (Exception e) {
+ String message = "An error occurred during Module generating, mbe:"
+ + mbe.getJavaNamePrefix();
+ logger.error(message, e);
+ throw new RuntimeException(message, e);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.maven.plugin.logging.Log;
+import org.apache.maven.project.MavenProject;
+import org.opendaylight.controller.config.spi.ModuleFactory;
+import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntry;
+import org.opendaylight.controller.config.yangjmxgenerator.PackageTranslator;
+import org.opendaylight.controller.config.yangjmxgenerator.ServiceInterfaceEntry;
+import org.opendaylight.controller.config.yangjmxgenerator.TypeProviderWrapper;
+import org.opendaylight.yangtools.sal.binding.yang.types.TypeProviderImpl;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang2sources.spi.CodeGenerator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.impl.StaticLoggerBinder;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+/**
+ * This class interfaces with yang-maven-plugin. Gets parsed yang modules in
+ * {@link SchemaContext}, and parameters form the plugin configuration, and
+ * writes service interfaces and/or modules.
+ */
+public class JMXGenerator implements CodeGenerator {
+
+ static final String NAMESPACE_TO_PACKAGE_DIVIDER = "==";
+ static final String NAMESPACE_TO_PACKAGE_PREFIX = "namespaceToPackage";
+ static final String MODULE_FACTORY_FILE_BOOLEAN = "moduleFactoryFile";
+
+ private PackageTranslator packageTranslator;
+ private final CodeWriter codeWriter;
+ private static final Logger logger = LoggerFactory
+ .getLogger(JMXGenerator.class);
+ private Map<String, String> namespaceToPackageMapping;
+ private File resourceBaseDir;
+ private File projectBaseDir;
+ private boolean generateModuleFactoryFile = true;
+
+ public JMXGenerator() {
+ this.codeWriter = new FreeMarkerCodeWriterImpl();
+ }
+
+ public JMXGenerator(CodeWriter codeWriter) {
+ this.codeWriter = codeWriter;
+ }
+
+ @Override
+ public Collection<File> generateSources(SchemaContext context,
+ File outputBaseDir, Set<Module> yangModulesInCurrentMavenModule) {
+
+ Preconditions.checkArgument(context != null, "Null context received");
+ Preconditions.checkArgument(outputBaseDir != null,
+ "Null outputBaseDir received");
+
+ Preconditions
+ .checkArgument(namespaceToPackageMapping != null && !namespaceToPackageMapping.isEmpty(),
+ "No namespace to package mapping provided in additionalConfiguration");
+
+ packageTranslator = new PackageTranslator(namespaceToPackageMapping);
+
+ if (!outputBaseDir.exists())
+ outputBaseDir.mkdirs();
+
+ GeneratedFilesTracker generatedFiles = new GeneratedFilesTracker();
+ Map<QName, ServiceInterfaceEntry> qNamesToSIEs = new HashMap<>();
+
+ // create SIE structure qNamesToSIEs
+ for (Module module : context.getModules()) {
+ String packageName = packageTranslator.getPackageName(module);
+ Map<QName, ServiceInterfaceEntry> namesToSIEntries = ServiceInterfaceEntry
+ .create(module, packageName);
+
+ for (Entry<QName, ServiceInterfaceEntry> sieEntry : namesToSIEntries
+ .entrySet()) {
+
+ // merge value into qNamesToSIEs
+ if (qNamesToSIEs.containsKey(sieEntry.getKey()) == false) {
+ qNamesToSIEs.put(sieEntry.getKey(), sieEntry.getValue());
+ } else {
+ throw new IllegalStateException(
+ "Cannot add two SIE with same qname "
+ + sieEntry.getValue());
+ }
+ }
+ if (yangModulesInCurrentMavenModule.contains(module)) {
+ // write this sie to disk
+ for (ServiceInterfaceEntry sie : namesToSIEntries.values()) {
+ try {
+ generatedFiles.addFile(codeWriter.writeSie(sie,
+ outputBaseDir));
+ } catch (Exception e) {
+ throw new RuntimeException(
+ "Error occurred during SIE source generate phase",
+ e);
+ }
+ }
+ }
+ }
+
+ File mainBaseDir = concatFolders(projectBaseDir, "src", "main", "java");
+ Preconditions.checkNotNull(resourceBaseDir,
+ "resource base dir attribute was null");
+
+ StringBuffer fullyQualifiedNamesOfFactories = new StringBuffer();
+ // create MBEs
+ for (Module module : yangModulesInCurrentMavenModule) {
+ String packageName = packageTranslator.getPackageName(module);
+ Map<String /* MB identity local name */, ModuleMXBeanEntry> namesToMBEs = ModuleMXBeanEntry
+ .create(module, qNamesToSIEs, context, new TypeProviderWrapper(new TypeProviderImpl(context)),
+ packageName);
+
+ for (Entry<String, ModuleMXBeanEntry> mbeEntry : namesToMBEs
+ .entrySet()) {
+ ModuleMXBeanEntry mbe = mbeEntry.getValue();
+ try {
+ List<File> files1 = codeWriter.writeMbe(mbe, outputBaseDir,
+ mainBaseDir, resourceBaseDir);
+ generatedFiles.addFile(files1);
+ } catch (Exception e) {
+ throw new RuntimeException(
+ "Error occurred during MBE source generate phase",
+ e);
+ }
+ fullyQualifiedNamesOfFactories.append(mbe
+ .getFullyQualifiedName(mbe.getStubFactoryName()));
+ fullyQualifiedNamesOfFactories.append("\n");
+ }
+ }
+ // create ModuleFactory file if needed
+ if (fullyQualifiedNamesOfFactories.length() > 0
+ && generateModuleFactoryFile) {
+ File serviceLoaderFile = JMXGenerator.concatFolders(
+ resourceBaseDir, "META-INF", "services",
+ ModuleFactory.class.getName());
+ // if this file does not exist, create empty file
+ serviceLoaderFile.getParentFile().mkdirs();
+ try {
+ serviceLoaderFile.createNewFile();
+ FileUtils.write(serviceLoaderFile,
+ fullyQualifiedNamesOfFactories.toString());
+ } catch (IOException e) {
+ String message = "Cannot write to " + serviceLoaderFile;
+ logger.error(message);
+ throw new RuntimeException(message, e);
+ }
+ }
+ return generatedFiles.getFiles();
+ }
+
+ static File concatFolders(File projectBaseDir, String... folderNames) {
+ StringBuilder b = new StringBuilder();
+ for (String folder : folderNames) {
+ b.append(folder);
+ b.append(File.separator);
+ }
+ return new File(projectBaseDir, b.toString());
+ }
+
+ @Override
+ public void setAdditionalConfig(Map<String, String> additionalCfg) {
+ if (logger != null)
+ logger.debug(getClass().getCanonicalName(),
+ ": Additional configuration received: ",
+ additionalCfg.toString());
+ this.namespaceToPackageMapping = extractNamespaceMapping(additionalCfg);
+ this.generateModuleFactoryFile = extractModuleFactoryBoolean(additionalCfg);
+ }
+
+ private boolean extractModuleFactoryBoolean(
+ Map<String, String> additionalCfg) {
+ String bool = additionalCfg.get(MODULE_FACTORY_FILE_BOOLEAN);
+ if (bool == null)
+ return true;
+ if (bool.equals("false"))
+ return false;
+ return true;
+ }
+
+ @Override
+ public void setLog(Log log) {
+ StaticLoggerBinder.getSingleton().setLog(log);
+ }
+
+ private static Map<String, String> extractNamespaceMapping(
+ Map<String, String> additionalCfg) {
+ Map<String, String> namespaceToPackage = Maps.newHashMap();
+ for (String key : additionalCfg.keySet()) {
+ if (key.startsWith(NAMESPACE_TO_PACKAGE_PREFIX)) {
+ String mapping = additionalCfg.get(key);
+ NamespaceMapping mappingResolved = extractNamespaceMapping(mapping);
+ namespaceToPackage.put(mappingResolved.namespace,
+ mappingResolved.packageName);
+ }
+ }
+ return namespaceToPackage;
+ }
+
+ static Pattern namespaceMappingPattern = Pattern.compile("(.+)"
+ + NAMESPACE_TO_PACKAGE_DIVIDER + "(.+)");
+
+ private static NamespaceMapping extractNamespaceMapping(String mapping) {
+ Matcher matcher = namespaceMappingPattern.matcher(mapping);
+ Preconditions
+ .checkArgument(matcher.matches(), String.format("Namespace to package mapping:%s is in invalid " +
+ "format, requested format is: %s", mapping, namespaceMappingPattern));
+ return new NamespaceMapping(matcher.group(1), matcher.group(2));
+ }
+
+ private static class NamespaceMapping {
+ public NamespaceMapping(String namespace, String packagename) {
+ this.namespace = namespace;
+ this.packageName = packagename;
+ }
+
+ private final String namespace, packageName;
+ }
+
+ @Override
+ public void setResourceBaseDir(File resourceDir) {
+ this.resourceBaseDir = resourceDir;
+ }
+
+ @Override
+ public void setMavenProject(MavenProject project) {
+ this.projectBaseDir = project.getBasedir();
+
+ if (logger != null)
+ logger.debug(getClass().getCanonicalName(), " project base dir: ",
+ projectBaseDir);
+ }
+
+ @VisibleForTesting
+ static class GeneratedFilesTracker {
+ private final Set<File> files = Sets.newHashSet();
+
+ void addFile(File file) {
+ if (files.contains(file)) {
+ List<File> undeletedFiles = Lists.newArrayList();
+ for (File presentFile : files) {
+ if (presentFile.delete() == false) {
+ undeletedFiles.add(presentFile);
+ }
+ }
+ if (undeletedFiles.isEmpty() == false) {
+ logger.error(
+ "Illegal state occurred: Unable to delete already generated files, undeleted files: {}",
+ undeletedFiles);
+ }
+ throw new IllegalStateException(
+ "Name conflict in generated files, file" + file
+ + " present twice");
+ }
+ files.add(file);
+ }
+
+ void addFile(Collection<File> files) {
+ for (File file : files) {
+ addFile(file);
+ }
+ }
+
+ public Set<File> getFiles() {
+ return files;
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.ftl;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.opendaylight.controller.config.api.DependencyResolver;
+import org.opendaylight.controller.config.api.DynamicMBeanWithInstance;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.annotations.AbstractServiceInterface;
+import org.opendaylight.controller.config.spi.Module;
+import org.opendaylight.controller.config.spi.ModuleFactory;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Constructor;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Field;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Header;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.MethodDefinition;
+
+import com.google.common.collect.Lists;
+
+/**
+ *
+ */
+public class AbstractFactoryTemplate extends GeneralClassTemplate {
+
+ private static final List<String> implementedIfcs = Lists
+ .newArrayList(ModuleFactory.class.getCanonicalName());
+
+ private final String globallyUniqueName, moduleInstanceType;
+ private final List<String> providedServices;
+
+ public AbstractFactoryTemplate(Header header, String packageName,
+ String abstractFactoryName, String globallyUniqueName,
+ String moduleInstanceType, List<Field> fields,
+ List<String> providedServices) {
+ super(header, packageName, abstractFactoryName, Collections
+ .<String> emptyList(), implementedIfcs, fields, Collections
+ .<MethodDefinition> emptyList(), true, false, Collections
+ .<Constructor> emptyList());
+ this.globallyUniqueName = globallyUniqueName;
+ this.moduleInstanceType = moduleInstanceType;
+ this.providedServices = providedServices;
+ }
+
+ public String getGloballyUniqueName() {
+ return globallyUniqueName;
+ }
+
+ public String getInstanceType() {
+ return AutoCloseable.class.getCanonicalName();
+ }
+
+ public String getModuleNameType() {
+ return ModuleIdentifier.class.getCanonicalName();
+ }
+
+ public String getModuleInstanceType() {
+ return moduleInstanceType;
+ }
+
+ public String getAbstractServiceInterfaceType() {
+ return AbstractServiceInterface.class.getCanonicalName();
+ }
+
+ public List<String> getProvidedServices() {
+ return providedServices;
+ }
+
+ public String getModuleType() {
+ return Module.class.getCanonicalName();
+ }
+
+ public String getDependencyResolverType() {
+ return DependencyResolver.class.getCanonicalName();
+ }
+
+ public String getDynamicMBeanWithInstanceType() {
+ return DynamicMBeanWithInstance.class.getCanonicalName();
+ }
+
+ @Override
+ public String getFtlTempleteLocation() {
+ return "factory_abs_template.ftl";
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.ftl;
+
+import java.io.File;
+import java.util.List;
+
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Annotation;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Field;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Header;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Method;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.TypeDeclaration;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.util.FullyQualifiedNameHelper;
+
+import com.google.common.collect.Lists;
+
+public abstract class AbstractFtlTemplate implements FtlTemplate {
+ private final String packageName;
+ private final List<Field> fields;
+ private final List<Annotation> annotations;
+ private final List<? extends Method> methods;
+ private String javadoc = null;
+ private final TypeDeclaration typeDeclaration;
+ private final Header header;
+
+ protected AbstractFtlTemplate(Header header, String packageName,
+ List<Field> fields, List<? extends Method> methods,
+ TypeDeclaration typeDeclaration) {
+ this.packageName = packageName;
+ this.fields = fields;
+ this.methods = methods;
+ this.annotations = Lists.newArrayList();
+ this.typeDeclaration = typeDeclaration;
+ this.header = header;
+ }
+
+ @Override
+ public Header getHeader() {
+ return header;
+ }
+
+ @Override
+ public String getFullyQualifiedName() {
+ return FullyQualifiedNameHelper.getFullyQualifiedName(getPackageName(),
+ getTypeDeclaration().getName());
+ }
+
+ @Override
+ public String getPackageName() {
+ return packageName;
+ }
+
+ @Override
+ public TypeDeclaration getTypeDeclaration() {
+ return typeDeclaration;
+ }
+
+ @Override
+ public String getJavadoc() {
+ return javadoc;
+ }
+
+ public void setJavadoc(String javadoc) {
+ this.javadoc = javadoc;
+ }
+
+ @Override
+ public List<Annotation> getAnnotations() {
+ return annotations;
+ }
+
+ @Override
+ public List<Field> getFields() {
+ return fields;
+ }
+
+ @Override
+ public List<? extends Method> getMethods() {
+ return methods;
+ }
+
+ @Override
+ public File getRelativeFile() {
+ return new File(packageName.replace(".", File.separator),
+ getTypeDeclaration().getName() + ".java");
+ }
+
+ @Override
+ public String getFtlTempleteLocation() {
+ return "abstract_ftl_file.ftl";
+ }
+
+ @Override
+ public String toString() {
+ return "AbstractFtlTemplate{" + "typeDeclaration=" + typeDeclaration
+ + ", packageName='" + packageName + '\'' + '}';
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.ftl;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.opendaylight.controller.config.api.DependencyResolver;
+import org.opendaylight.controller.config.api.DynamicMBeanWithInstance;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.annotations.AbstractServiceInterface;
+import org.opendaylight.controller.config.api.runtime.RootRuntimeBeanRegistrator;
+import org.opendaylight.controller.config.spi.Module;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Constructor;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Field;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Header;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.MethodDefinition;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.ModuleField;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ */
+public class AbstractModuleTemplate extends GeneralClassTemplate {
+
+ private final List<ModuleField> moduleFields;
+ private final boolean runtime;
+ private final String registratorType;
+
+ public AbstractModuleTemplate(Header header, String packageName,
+ String abstractModuleName, List<String> implementedIfcs,
+ List<ModuleField> moduleFields, List<MethodDefinition> methods,
+ boolean isRuntime, String registratorType) {
+ super(header, packageName, abstractModuleName, Collections
+ .<String> emptyList(), implementedIfcs, Collections
+ .<Field> emptyList(), methods, true, false, Collections
+ .<Constructor> emptyList());
+ this.moduleFields = moduleFields;
+ this.runtime = isRuntime;
+ this.registratorType = registratorType;
+ }
+
+ public List<ModuleField> getModuleFields() {
+ return moduleFields;
+ }
+
+ public String getInstanceType() {
+ return AutoCloseable.class.getCanonicalName();
+ }
+
+ public String getModuleNameType() {
+ return ModuleIdentifier.class.getCanonicalName();
+ }
+
+ public String getAbstractServiceInterfaceType() {
+ return AbstractServiceInterface.class.getCanonicalName();
+ }
+
+ public String getModuleType() {
+ return Module.class.getCanonicalName();
+ }
+
+ public String getRegistratorType() {
+ return registratorType;
+ }
+
+ public boolean isRuntime() {
+ return runtime;
+ }
+
+ public String getDependencyResolverType() {
+ return DependencyResolver.class.getCanonicalName();
+ }
+
+ public String getDynamicMBeanWithInstanceType() {
+ return DynamicMBeanWithInstance.class.getCanonicalName();
+ }
+
+ public String getRootRuntimeRegistratorType() {
+ return RootRuntimeBeanRegistrator.class.getCanonicalName();
+ }
+
+ @Override
+ public String getFtlTempleteLocation() {
+ return "module_abs_template_new.ftl";
+ }
+
+ public String getLoggerType() {
+ return Logger.class.getCanonicalName();
+ }
+
+ public String getLoggerFactoryType() {
+ return LoggerFactory.class.getCanonicalName();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.ftl;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.directives.AnnotationsDirective;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.directives.ConstructorsDirective;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.directives.FieldsDirectiveProg;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.directives.HeaderDirective;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.directives.JavadocDirective;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.directives.MethodsDirective;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.directives.ModuleFieldsDirective;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.directives.TypeDeclarationDirective;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.directives.UnimplementedExceptionDirective;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+
+public class FtlFilePersister {
+ private static final Logger logger = LoggerFactory
+ .getLogger(FtlFilePersister.class);
+
+ private static final Pattern TRAILING_WHITESPACES = Pattern.compile(" +$", Pattern.MULTILINE);
+
+ @VisibleForTesting
+ public Map<FtlTemplate, String> serializeFtls(
+ Collection<? extends FtlTemplate> ftlFiles) {
+ Map<FtlTemplate, String> result = new HashMap<>();
+ for (FtlTemplate ftlFile : ftlFiles) {
+
+ try (Writer writer = new StringWriter()) {
+ Template template = getCfg().getTemplate(
+ ftlFile.getFtlTempleteLocation());
+ try {
+ template.process(ftlFile, writer);
+ } catch (TemplateException e) {
+ throw new IllegalStateException(
+ "Template error while generating " + ftlFile, e);
+ }
+ String fileContent = writer.toString();
+ // remove trailing spaces
+ fileContent = TRAILING_WHITESPACES.matcher(fileContent).replaceAll("");
+ result.put(ftlFile, fileContent);
+ } catch (IOException e) {
+ throw new IllegalStateException(
+ "Exception while processing template", e);
+ }
+ }
+
+ return result;
+ }
+
+ public List<File> persist(Collection<? extends FtlTemplate> ftlFiles,
+ File dstFolder, boolean overwrite) throws IOException {
+ Map<FtlTemplate, String> ftlFileStringMap = serializeFtls(ftlFiles);
+ List<File> result = new ArrayList<>();
+ for (Entry<FtlTemplate, String> entry : ftlFileStringMap.entrySet()) {
+ FtlTemplate ftlFile = entry.getKey();
+ File targetFile = new File(dstFolder, ftlFile.getRelativeFile()
+ .getPath());
+ File pathToFile = targetFile.getParentFile();
+ if (pathToFile.exists() == false) {
+ pathToFile.mkdirs();
+ }
+ if (targetFile.exists() && overwrite == false) {
+ logger.info("Skipping {} since it already exists", targetFile);
+ } else {
+ try (Writer fileWriter = new FileWriter(targetFile)) {
+ fileWriter.write(entry.getValue());
+ }
+ logger.info("{}: File {} generated successfully",
+ JMXGenerator.class.getCanonicalName(), targetFile);
+ result.add(targetFile);
+ }
+ }
+ return result;
+ }
+
+ private Configuration getCfg() {
+ Configuration cfg = new Configuration();
+ cfg.setClassForTemplateLoading(getClass(), "/freeMarker/");
+ cfg.setSharedVariable("javadocD", new JavadocDirective());
+ cfg.setSharedVariable("annotationsD", new AnnotationsDirective());
+ cfg.setSharedVariable("typeDeclarationD",
+ new TypeDeclarationDirective());
+ cfg.setSharedVariable("constructorsD", new ConstructorsDirective());
+ cfg.setSharedVariable("fieldsD", new FieldsDirectiveProg());
+ cfg.setSharedVariable("moduleFieldsD", new ModuleFieldsDirective());
+ cfg.setSharedVariable("methodsD", new MethodsDirective());
+ cfg.setSharedVariable("headerD", new HeaderDirective());
+ cfg.setSharedVariable("unimplementedExceptionD",
+ new UnimplementedExceptionDirective());
+ return cfg;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.ftl;
+
+import java.io.File;
+import java.util.List;
+
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Annotation;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Field;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Header;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Method;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.TypeDeclaration;
+
+public interface FtlTemplate {
+
+ Header getHeader();
+
+ String getPackageName();
+
+ String getJavadoc();
+
+ public List<Annotation> getAnnotations();
+
+ TypeDeclaration getTypeDeclaration();
+
+ public String getFullyQualifiedName();
+
+ public List<Field> getFields();
+
+ List<? extends Method> getMethods();
+
+ /**
+ * @return relative path to file to be created.
+ */
+ public File getRelativeFile();
+
+ /**
+ *
+ * @return ftl template location
+ */
+ public String getFtlTempleteLocation();
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.ftl;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Constructor;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Field;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Header;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.MethodDefinition;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.TypeDeclaration;
+
+public class GeneralClassTemplate extends AbstractFtlTemplate {
+
+ private final List<Constructor> constructors;
+
+ public GeneralClassTemplate(Header header, String packageName, String name,
+ List<String> extendedClasses, List<String> implementedIfcs,
+ List<Field> fields, List<MethodDefinition> methods) {
+ this(header, packageName, name, extendedClasses, implementedIfcs,
+ fields, methods, false, false, Collections
+ .<Constructor> emptyList());
+ }
+
+ public GeneralClassTemplate(Header header, String packageName, String name,
+ List<String> extendedClasses, List<String> implementedIfcs,
+ List<Field> fields, List<MethodDefinition> methods,
+ boolean isAbstract, boolean isFinal, List<Constructor> constructors) {
+ super(header, packageName, fields, methods, new TypeDeclaration(
+ "class", name, checkCardinality(extendedClasses),
+ implementedIfcs, isAbstract, isFinal));
+ this.constructors = constructors;
+ }
+
+ static List<String> checkCardinality(List<String> extendedClass) {
+ if (extendedClass.size() > 1)
+ throw new IllegalArgumentException(
+ "Class cannot have more than one super " + "class");
+ return extendedClass;
+ }
+
+ public List<Constructor> getConstructors() {
+ return constructors;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.ftl;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Field;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Header;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.MethodDeclaration;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.TypeDeclaration;
+
+public class GeneralInterfaceTemplate extends AbstractFtlTemplate {
+
+ public GeneralInterfaceTemplate(Header header, String packageName,
+ String name, List<String> extendedInterfaces,
+ List<MethodDeclaration> methods) {
+ super(header, packageName, Collections.<Field> emptyList(), methods,
+ new TypeDeclaration("interface", name, extendedInterfaces,
+ Collections.<String> emptyList()));
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.ftl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static java.lang.String.format;
+
+import java.io.Closeable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.opendaylight.controller.config.api.runtime.HierarchicalRuntimeBeanRegistration;
+import org.opendaylight.controller.config.api.runtime.RootRuntimeBeanRegistrator;
+import org.opendaylight.controller.config.yangjmxgenerator.RuntimeBeanEntry;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Annotation;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Annotation.Parameter;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Field;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.MethodDefinition;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.util.FullyQualifiedNameHelper;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+
+public class RuntimeRegistratorFtlTemplate extends GeneralClassTemplate {
+
+ private RuntimeRegistratorFtlTemplate(RuntimeBeanEntry runtimeBeanEntry,
+ String name, List<Field> fields, List<MethodDefinition> methods) {
+ // TODO header
+ super(null, runtimeBeanEntry.getPackageName(), name, Collections
+ .<String> emptyList(), Arrays.asList(Closeable.class
+ .getCanonicalName()), fields, methods);
+ }
+
+ public static RuntimeBeanEntry findRoot(
+ Collection<RuntimeBeanEntry> runtimeBeanEntries) {
+ RuntimeBeanEntry result = null;
+ for (RuntimeBeanEntry rb : runtimeBeanEntries) {
+ if (rb.isRoot()) {
+ if (result != null) {
+ throw new IllegalArgumentException(
+ "More than one root runtime bean found");
+ }
+ result = rb;
+ }
+ }
+ if (result != null) {
+ return result;
+ }
+ throw new IllegalArgumentException("No root runtime bean found");
+ }
+
+ private static String constructConstructorBody(
+ List<Field> constructorParameters) {
+ StringBuffer constructorBody = new StringBuffer();
+ for (Field field : constructorParameters) {
+ constructorBody.append("this.");
+ constructorBody.append(field.getName());
+ constructorBody.append("=");
+ constructorBody.append(field.getName());
+ constructorBody.append(";\n");
+ }
+ return constructorBody.toString();
+ }
+
+ // TODO Move to factory
+ /**
+ * Get registrator and n registration ftls where n is equal to total number
+ * of runtime beans in hierarchy.
+ */
+ public static Map<String, FtlTemplate> create(RuntimeBeanEntry rootRB) {
+ checkArgument(rootRB.isRoot(), "RuntimeBeanEntry must be root");
+ String registratorName = getJavaNameOfRuntimeRegistrator(rootRB);
+ List<MethodDefinition> methods = new ArrayList<>();
+ Field rootRuntimeBeanRegistratorField = new Field(
+ Lists.newArrayList("final"),
+ RootRuntimeBeanRegistrator.class.getName(),
+ "rootRuntimeBeanRegistrator");
+ List<Field> constructorParameters = Lists
+ .newArrayList(rootRuntimeBeanRegistratorField);
+ String constructorBody = constructConstructorBody(constructorParameters);
+ MethodDefinition constructor = MethodDefinition.createConstructor(
+ registratorName, constructorParameters, constructorBody);
+ methods.add(constructor);
+
+ LinkedHashMap<String, RuntimeRegistratorFtlTemplate> RuntimeRegistratorFtlTemplates = createRegistrationHierarchy(
+ rootRB, Collections.<String> emptySet());
+ RuntimeRegistratorFtlTemplate rootFtlFile = RuntimeRegistratorFtlTemplates
+ .values().iterator().next();
+
+ {// add register(rootruntimemxbean)
+ String fullyQualifiedNameOfMXBean = FullyQualifiedNameHelper
+ .getFullyQualifiedName(rootRB.getPackageName(), rootRB.getJavaNameOfRuntimeMXBean());
+ String childRegistratorFQN = rootFtlFile.getFullyQualifiedName();
+ Field rbParameter = new Field(fullyQualifiedNameOfMXBean, "rb");
+ StringBuffer registerBody = new StringBuffer();
+ registerBody.append(format("%s %s = this.%s.registerRoot(%s);\n",
+ HierarchicalRuntimeBeanRegistration.class
+ .getCanonicalName(), hierachchicalRegistration
+ .getName(), rootRuntimeBeanRegistratorField
+ .getName(), rbParameter.getName()));
+ registerBody.append(format("return new %s(%s);\n",
+ rootFtlFile.getFullyQualifiedName(),
+ hierachchicalRegistration.getName()));
+
+ MethodDefinition registerMethod = new MethodDefinition(
+ childRegistratorFQN, "register",
+ Arrays.asList(rbParameter), registerBody.toString());
+ methods.add(registerMethod);
+ }
+
+ MethodDefinition closeRegistrator = createCloseMethodToCloseField(rootRuntimeBeanRegistratorField);
+ methods.add(closeRegistrator);
+
+ // TODO add header
+ GeneralClassTemplate registrator = new GeneralClassTemplate(null,
+ rootRB.getPackageName(), registratorName,
+ Collections.<String> emptyList(), Arrays.asList(Closeable.class
+ .getCanonicalName()), constructorParameters, methods);
+
+ checkState(RuntimeRegistratorFtlTemplates.containsKey(registrator
+ .getTypeDeclaration().getName()) == false, "Name conflict: "
+ + registrator.getTypeDeclaration().getName());
+ Map<String, FtlTemplate> result = new HashMap<>();
+ result.putAll(RuntimeRegistratorFtlTemplates);
+ result.put(registrator.getTypeDeclaration().getName(), registrator);
+ return result;
+ }
+
+ private static Field hierachchicalRegistration = new Field(
+ Lists.newArrayList("final"),
+ HierarchicalRuntimeBeanRegistration.class.getCanonicalName(),
+ "registration");
+
+ // TODO move to factory + RuntimeBeanEntry
+ /**
+ * Create ftls representing registrations. First registration is represents
+ * parent.
+ *
+ * @return map containing java class name as key, instance representing the
+ * java file as value.
+ */
+ private static LinkedHashMap<String, RuntimeRegistratorFtlTemplate> createRegistrationHierarchy(
+ RuntimeBeanEntry parent, Set<String> occupiedKeys) {
+ LinkedHashMap<String, RuntimeRegistratorFtlTemplate> unorderedResult = new LinkedHashMap<>();
+ List<MethodDefinition> methods = new ArrayList<>();
+
+ // hierarchy of ON is created as follows:
+ // root RB: <domain>, type=RuntimeBean
+ // 1st RB in hierarchy: <domain>, type=RuntimeBean, <java name of leaf
+ // list>: key or counter
+ // n-th RB in hierarchy has same ON as n-1, with added <java name of
+ // leaf list>: key or counter
+ if (occupiedKeys.contains(parent.getJavaNamePrefix())) {
+ throw new IllegalArgumentException(
+ "Name conflict in runtime bean hierarchy - java name found more than "
+ + "once. Consider using java-name extension. Conflicting name: "
+ + parent.getJavaNamePrefix());
+ }
+ Set<String> currentOccupiedKeys = new HashSet<>(occupiedKeys);
+ currentOccupiedKeys.add(parent.getJavaNamePrefix());
+
+ Field registratorsMapField = new Field(Arrays.asList("final"),
+ TypeHelper.getGenericType(Map.class, String.class,
+ AtomicInteger.class), "unkeyedMap", "new "
+ + TypeHelper.getGenericType(HashMap.class,
+ String.class, AtomicInteger.class) + "()");
+
+ // create register methods for children
+ for (RuntimeBeanEntry child : parent.getChildren()) {
+ checkArgument(parent.getPackageName()
+ .equals(child.getPackageName()), "Invalid package name");
+
+ // call itself recursively to generate child
+ // registrators/registrations
+ LinkedHashMap<String, RuntimeRegistratorFtlTemplate> childRegistratorMap = createRegistrationHierarchy(
+ child, currentOccupiedKeys);
+ for (Entry<String, RuntimeRegistratorFtlTemplate> entry : childRegistratorMap
+ .entrySet()) {
+ if (unorderedResult.containsKey(entry.getKey())) {
+ throw new IllegalStateException(
+ "Conflicting name found while generating runtime registration:"
+ + entry.getKey());
+ }
+ unorderedResult.put(entry.getKey(), entry.getValue());
+ }
+
+ if (childRegistratorMap.size() > 0) {
+ // first entry is the direct descendant according to the create
+ // contract
+ RuntimeRegistratorFtlTemplate childRegistrator = childRegistratorMap
+ .values().iterator().next();
+ StringBuffer body = new StringBuffer();
+ String key, value;
+ key = child.getJavaNamePrefix();
+ body.append(format(
+ "String key = \"%s\"; //TODO: check for conflicts\n",
+ key));
+
+ if (child.getKeyJavaName().isPresent()) {
+ value = "bean.get" + child.getKeyJavaName().get() + "()";
+ value = "String.valueOf(" + value + ")";
+ } else {
+ body.append("java.util.concurrent.atomic.AtomicInteger counter = unkeyedMap.get(key);\n"
+ + "if (counter==null){\n"
+ + "counter = new java.util.concurrent.atomic.AtomicInteger();\n"
+ + "unkeyedMap.put(key, counter);\n" + "}\n");
+ value = "String.valueOf(counter.incrementAndGet())";
+ }
+ body.append(format("String value = %s;\n", value));
+ body.append(format("%s r = %s.register(key, value, bean);\n",
+ HierarchicalRuntimeBeanRegistration.class
+ .getCanonicalName(), hierachchicalRegistration
+ .getName()));
+ body.append(format("return new %s(r);",
+ childRegistrator.getFullyQualifiedName()));
+
+ Field param = new Field(Lists.newArrayList("final"),
+ child.getJavaNameOfRuntimeMXBean(), "bean");
+ MethodDefinition register = new MethodDefinition(
+ Arrays.asList("synchronized"),
+ childRegistrator.getFullyQualifiedName(), "register",
+ Arrays.asList(param), Collections.<String> emptyList(),
+ Collections.<Annotation> emptyList(), body.toString());
+ methods.add(register);
+
+ }
+ }
+
+ // create parent registration
+ String createdName = getJavaNameOfRuntimeRegistration(parent.getJavaNamePrefix());
+
+ List<Field> constructorParameters = Arrays
+ .asList(hierachchicalRegistration);
+ String constructorBody = constructConstructorBody(constructorParameters);
+
+ MethodDefinition constructor = MethodDefinition.createConstructor(
+ createdName, constructorParameters, constructorBody);
+
+ MethodDefinition closeRegistrator = createCloseMethodToCloseField(hierachchicalRegistration);
+ methods.add(closeRegistrator);
+ methods.add(constructor);
+ List<Field> privateFields = Lists.newArrayList(registratorsMapField);
+ privateFields.addAll(constructorParameters);
+
+ RuntimeRegistratorFtlTemplate created = new RuntimeRegistratorFtlTemplate(
+ parent, createdName, privateFields, methods);
+
+ LinkedHashMap<String, RuntimeRegistratorFtlTemplate> result = new LinkedHashMap<>();
+ result.put(created.getTypeDeclaration().getName(), created);
+ checkState(unorderedResult.containsKey(created.getTypeDeclaration()
+ .getName()) == false, "Naming conflict: "
+ + created.getTypeDeclaration().getName());
+ result.putAll(unorderedResult);
+ return result;
+ }
+
+ private static MethodDefinition createCloseMethodToCloseField(Field field) {
+ String body = field.getName() + ".close();";
+ // TODO Thrown exception breaks build
+ // return new MethodDefinition(Collections.<String> emptyList(), "void",
+ // "close", Collections.<Field> emptyList(),
+ // Arrays.asList(IOException.class.getCanonicalName()),
+ // Collections.<Annotation> emptyList(), body);
+ List<Annotation> annotations = Lists.newArrayList(new Annotation(
+ "Override", Collections.<Parameter> emptyList()));
+ return new MethodDefinition(Collections.<String> emptyList(), "void",
+ "close", Collections.<Field> emptyList(),
+ Collections.<String> emptyList(), annotations, body);
+ }
+
+ @VisibleForTesting
+ public static String getJavaNameOfRuntimeRegistration(String javaNamePrefix) {
+ return javaNamePrefix + "RuntimeRegistration";
+ }
+
+ public static String getJavaNameOfRuntimeRegistrator(RuntimeBeanEntry rootRB) {
+ checkArgument(rootRB.isRoot(), "RuntimeBeanEntry must be root");
+ return rootRB.getJavaNamePrefix() + "RuntimeRegistrator";
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.ftl;
+
+import java.util.Collections;
+
+import org.opendaylight.controller.config.api.DynamicMBeanWithInstance;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Field;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Header;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.MethodDefinition;
+
+import com.google.common.collect.Lists;
+
+public class StubFactoryTemplate extends GeneralClassTemplate {
+
+ private final String moduleInstanceType;
+
+ public StubFactoryTemplate(Header header, String packageName, String name,
+ String extendedClass, String moduleInstanceType) {
+ super(header, packageName, name, Lists.newArrayList(extendedClass),
+ Collections.<String> emptyList(), Collections
+ .<Field> emptyList(), Collections
+ .<MethodDefinition> emptyList());
+ this.moduleInstanceType = moduleInstanceType;
+ }
+
+ public String getModuleInstanceType() {
+ return moduleInstanceType;
+ }
+
+ public String getDynamicMBeanWithInstanceType() {
+ return DynamicMBeanWithInstance.class.getCanonicalName();
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.ftl;
+
+import java.util.Collections;
+
+import org.opendaylight.controller.config.api.DependencyResolver;
+import org.opendaylight.controller.config.api.DynamicMBeanWithInstance;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.annotations.AbstractServiceInterface;
+import org.opendaylight.controller.config.spi.Module;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Constructor;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Field;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Header;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.MethodDefinition;
+
+import com.google.common.collect.Lists;
+
+/**
+ *
+ */
+public class StubModuleTemplate extends GeneralClassTemplate {
+
+ private final String extendedClass;
+
+ public StubModuleTemplate(Header header, String packageName,
+ String stubModuleName, String extendedClass) {
+ super(header, packageName, stubModuleName, Lists
+ .newArrayList(extendedClass), Collections.<String> emptyList(),
+ Collections.<Field> emptyList(), Collections
+ .<MethodDefinition> emptyList(), false, true,
+ Collections.<Constructor> emptyList());
+ this.extendedClass = extendedClass;
+ }
+
+ public String getExtendedClass() {
+ return extendedClass;
+ }
+
+ public String getInstanceType() {
+ return AutoCloseable.class.getCanonicalName();
+ }
+
+ public String getModuleNameType() {
+ return ModuleIdentifier.class.getCanonicalName();
+ }
+
+ public String getAbstractServiceInterfaceType() {
+ return AbstractServiceInterface.class.getCanonicalName();
+ }
+
+ public String getModuleType() {
+ return Module.class.getCanonicalName();
+ }
+
+ public String getDependencyResolverType() {
+ return DependencyResolver.class.getCanonicalName();
+ }
+
+ public String getDynamicMBeanWithInstanceType() {
+ return DynamicMBeanWithInstance.class.getCanonicalName();
+ }
+
+ @Override
+ public String getFtlTempleteLocation() {
+ return "module_stub_template.ftl";
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.ftl;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.opendaylight.controller.config.api.RuntimeBeanRegistratorAwareModule;
+import org.opendaylight.controller.config.api.annotations.AbstractServiceInterface;
+import org.opendaylight.controller.config.api.runtime.RuntimeBean;
+import org.opendaylight.controller.config.spi.Module;
+import org.opendaylight.controller.config.yangjmxgenerator.AbstractEntry;
+import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntry;
+import org.opendaylight.controller.config.yangjmxgenerator.RuntimeBeanEntry;
+import org.opendaylight.controller.config.yangjmxgenerator.RuntimeBeanEntry.Rpc;
+import org.opendaylight.controller.config.yangjmxgenerator.ServiceInterfaceEntry;
+import org.opendaylight.controller.config.yangjmxgenerator.attribute.AttributeIfc;
+import org.opendaylight.controller.config.yangjmxgenerator.attribute.DependencyAttribute;
+import org.opendaylight.controller.config.yangjmxgenerator.attribute.DependencyAttribute.Dependency;
+import org.opendaylight.controller.config.yangjmxgenerator.attribute.JavaAttribute;
+import org.opendaylight.controller.config.yangjmxgenerator.attribute.ListAttribute;
+import org.opendaylight.controller.config.yangjmxgenerator.attribute.TOAttribute;
+import org.opendaylight.controller.config.yangjmxgenerator.attribute.TypedAttribute;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Annotation;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Annotation.Parameter;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Constructor;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Field;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Header;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.MethodDeclaration;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.MethodDefinition;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.ModuleField;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.util.FullyQualifiedNameHelper;
+import org.opendaylight.yangtools.binding.generator.util.BindingGeneratorUtil;
+import org.opendaylight.yangtools.sal.binding.model.api.Type;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+public class TemplateFactory {
+
+ public static Map<String, FtlTemplate> getFtlTemplates(
+ ModuleMXBeanEntry entry) {
+ Map<String, FtlTemplate> result = new HashMap<>();
+
+ result.putAll(TemplateFactory.tOsFromMbe(entry));
+
+ // IFC
+ result.put(entry.getMXBeanInterfaceName() + ".java",
+ TemplateFactory.mXBeanInterfaceTemplateFromMbe(entry));
+
+ // ABS fact
+ result.put(entry.getAbstractFactoryName() + ".java",
+ TemplateFactory.abstractFactoryTemplateFromMbe(entry));
+
+ // ABS module
+ result.put(entry.getAbstractModuleName() + ".java",
+ TemplateFactory.abstractModuleTemplateFromMbe(entry));
+
+ return result;
+ }
+
+ public static Map<String, FtlTemplate> getFtlStubTemplates(
+ ModuleMXBeanEntry entry) {
+ Map<String, FtlTemplate> result = new HashMap<>();
+ // STUB fact
+ result.put(entry.getStubFactoryName() + ".java",
+ TemplateFactory.stubFactoryTemplateFromMbe(entry));
+
+ result.put(entry.getStubModuleName() + ".java",
+ TemplateFactory.stubModuleTemplateFromMbe(entry));
+ return result;
+ }
+
+ public static Map<String, FtlTemplate> getFtlTemplates(
+ ServiceInterfaceEntry entry) {
+
+ Map<String, FtlTemplate> result = new HashMap<>();
+ result.put(entry.getTypeName() + ".java",
+ TemplateFactory.serviceInterfaceFromSie(entry));
+
+ return result;
+ }
+
+ /**
+ * Get map of file name as key, FtlFile instance representing runtime mx
+ * bean as value that should be persisted from this instance.
+ */
+ public static Map<String, FtlTemplate> getTOAndMXInterfaceFtlFiles(
+ RuntimeBeanEntry entry) {
+ Map<String, FtlTemplate> result = new HashMap<>();
+ { // create GeneralInterfaceFtlFile for runtime MXBean. Attributes will
+ // be transformed to getter methods
+ String mxBeanTypeName = entry.getJavaNameOfRuntimeMXBean();
+ List<String> extendedInterfaces = Arrays.asList(RuntimeBean.class
+ .getCanonicalName());
+ List<MethodDeclaration> methods = new ArrayList<>();
+ // convert attributes to getters
+ for (AttributeIfc attributeIfc : entry.getAttributes()) {
+ String returnType = null;
+ if (attributeIfc instanceof TypedAttribute) {
+ returnType = ((TypedAttribute) attributeIfc).getType()
+ .getFullyQualifiedName();
+ } else if (attributeIfc instanceof TOAttribute) {
+ String fullyQualifiedName = FullyQualifiedNameHelper
+ .getFullyQualifiedName(entry.getPackageName(),
+ attributeIfc.getUpperCaseCammelCase());
+
+ returnType = fullyQualifiedName;
+ } else if (attributeIfc instanceof ListAttribute) {
+ AttributeIfc innerAttr = ((ListAttribute) attributeIfc)
+ .getInnerAttribute();
+
+ String innerTpe = innerAttr instanceof TypedAttribute ? ((TypedAttribute) innerAttr)
+ .getType().getFullyQualifiedName()
+ : FullyQualifiedNameHelper.getFullyQualifiedName(
+ entry.getPackageName(),
+ attributeIfc.getUpperCaseCammelCase());
+
+ returnType = "java.util.List<" + innerTpe + ">";
+ } else {
+ throw new UnsupportedOperationException(
+ "Attribute not supported: "
+ + attributeIfc.getClass());
+ }
+ String getterName = "get"
+ + attributeIfc.getUpperCaseCammelCase();
+ MethodDeclaration getter = new MethodDeclaration(returnType,
+ getterName, Collections.<Field> emptyList());
+ methods.add(getter);
+ }
+ // add rpc methods
+ for (Rpc rpc : entry.getRpcs()) {
+ // convert JavaAttribute parameters into fields
+ List<Field> fields = new ArrayList<>();
+ for (JavaAttribute ja : rpc.getParameters()) {
+ Field field = new Field(Collections.<String> emptyList(),
+ ja.getType().getFullyQualifiedName(),
+ ja.getLowerCaseCammelCase());
+ fields.add(field);
+ }
+ MethodDeclaration operation = new MethodDeclaration(
+ rpc.getReturnType(), rpc.getName(), fields);
+ methods.add(operation);
+ }
+
+ // FIXME header
+ GeneralInterfaceTemplate runtimeMxBeanIfc = new GeneralInterfaceTemplate(
+ null, entry.getPackageName(), mxBeanTypeName,
+ extendedInterfaces, methods);
+
+ result.put(runtimeMxBeanIfc.getTypeDeclaration().getName()
+ + ".java", runtimeMxBeanIfc);
+ }
+
+ result.putAll(TemplateFactory.tOsFromRbe(entry));
+
+ return result;
+ }
+
+ public static GeneralInterfaceTemplate serviceInterfaceFromSie(
+ ServiceInterfaceEntry sie) {
+
+ List<String> extendedInterfaces = Lists
+ .newArrayList(AbstractServiceInterface.class.getCanonicalName());
+ if (sie.getBase().isPresent()) {
+ extendedInterfaces.add(sie.getBase().get().getFullyQualifiedName());
+ }
+
+ // FIXME header
+ GeneralInterfaceTemplate sieTemplate = new GeneralInterfaceTemplate(
+ getHeaderFromEntry(sie), sie.getPackageName(),
+ sie.getTypeName(), extendedInterfaces,
+ Lists.<MethodDeclaration> newArrayList());
+ sieTemplate.setJavadoc(sie.getNullableDescription());
+
+ if (sie.getNullableDescription() != null)
+ sieTemplate.getAnnotations().add(
+ Annotation.createDescriptionAnnotation(sie
+ .getNullableDescription()));
+ sieTemplate.getAnnotations().add(Annotation.createSieAnnotation(sie.getQName(), sie.getExportedOsgiClassName
+ ()));
+
+ return sieTemplate;
+ }
+
+ public static AbstractFactoryTemplate abstractFactoryTemplateFromMbe(
+ ModuleMXBeanEntry mbe) {
+ AbstractFactoryAttributesProcessor attrProcessor = new AbstractFactoryAttributesProcessor();
+ attrProcessor.processAttributes(mbe.getAttributes(),
+ mbe.getPackageName());
+
+ Collection<String> transformed = Collections2.transform(mbe
+ .getProvidedServices().keySet(),
+ new Function<String, String>() {
+
+ @Override
+ public String apply(String input) {
+ return input + ".class";
+ }
+ });
+
+ return new AbstractFactoryTemplate(getHeaderFromEntry(mbe),
+ mbe.getPackageName(), mbe.getAbstractFactoryName(),
+ mbe.getGloballyUniqueName(), mbe.getFullyQualifiedName(mbe
+ .getStubModuleName()), attrProcessor.getFields(),
+ Lists.newArrayList(transformed));
+ }
+
+ public static AbstractModuleTemplate abstractModuleTemplateFromMbe(
+ ModuleMXBeanEntry mbe) {
+ AbstractModuleAttributesProcessor attrProcessor = new AbstractModuleAttributesProcessor();
+ attrProcessor.processAttributes(mbe.getAttributes(),
+ mbe.getPackageName());
+
+ List<ModuleField> moduleFields = attrProcessor.getModuleFields();
+ List<String> implementedIfcs = Lists.newArrayList(
+ Module.class.getCanonicalName(),
+ mbe.getFullyQualifiedName(mbe.getMXBeanInterfaceName()));
+
+ for (String implementedService : mbe.getProvidedServices().keySet()) {
+ implementedIfcs.add(implementedService);
+ }
+
+ boolean generateRuntime = false;
+ String registratorFullyQualifiedName = null;
+ if (mbe.getRuntimeBeans() != null
+ && mbe.getRuntimeBeans().isEmpty() == false) {
+ generateRuntime = true;
+ RuntimeBeanEntry rootEntry = RuntimeRegistratorFtlTemplate
+ .findRoot(mbe.getRuntimeBeans());
+ registratorFullyQualifiedName = rootEntry
+ .getPackageName()
+ .concat(".")
+ .concat(RuntimeRegistratorFtlTemplate.getJavaNameOfRuntimeRegistrator(rootEntry));
+ implementedIfcs.add(RuntimeBeanRegistratorAwareModule.class
+ .getCanonicalName());
+ }
+
+ AbstractModuleTemplate abstractModuleTemplate = new AbstractModuleTemplate(
+ getHeaderFromEntry(mbe), mbe.getPackageName(),
+ mbe.getAbstractModuleName(), implementedIfcs, moduleFields,
+ attrProcessor.getMethods(), generateRuntime,
+ registratorFullyQualifiedName);
+
+ if (mbe.getNullableDescription() != null)
+ abstractModuleTemplate.getAnnotations().add(
+ Annotation.createDescriptionAnnotation(mbe
+ .getNullableDescription()));
+ return abstractModuleTemplate;
+ }
+
+ public static StubFactoryTemplate stubFactoryTemplateFromMbe(
+ ModuleMXBeanEntry mbe) {
+ return new StubFactoryTemplate(getHeaderFromEntry(mbe),
+ mbe.getPackageName(), mbe.getStubFactoryName(),
+ mbe.getFullyQualifiedName(mbe.getAbstractFactoryName()),
+ mbe.getStubModuleName());
+ }
+
+ public static StubModuleTemplate stubModuleTemplateFromMbe(
+ ModuleMXBeanEntry mbe) {
+ return new StubModuleTemplate(getHeaderFromEntry(mbe),
+ mbe.getPackageName(), mbe.getStubModuleName(),
+ mbe.getFullyQualifiedName(mbe.getAbstractModuleName()));
+ }
+
+ public static GeneralInterfaceTemplate mXBeanInterfaceTemplateFromMbe(
+ ModuleMXBeanEntry mbe) {
+ MXBeanInterfaceAttributesProcessor attrProcessor = new MXBeanInterfaceAttributesProcessor();
+ attrProcessor.processAttributes(mbe.getAttributes(),
+ mbe.getPackageName());
+ GeneralInterfaceTemplate ifcTemplate = new GeneralInterfaceTemplate(
+ getHeaderFromEntry(mbe), mbe.getPackageName(),
+ mbe.getMXBeanInterfaceName(), Lists.<String> newArrayList(),
+ attrProcessor.getMethods());
+ ifcTemplate.setJavadoc(mbe.getNullableDescription());
+ return ifcTemplate;
+ }
+
+ public static Map<String, GeneralClassTemplate> tOsFromMbe(
+ ModuleMXBeanEntry mbe) {
+ Map<String, GeneralClassTemplate> retVal = Maps.newHashMap();
+ TOAttributesProcessor processor = new TOAttributesProcessor();
+ processor.processAttributes(mbe.getAttributes(), mbe.getPackageName());
+ for (org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.TemplateFactory.TOAttributesProcessor.TOInternal to : processor
+ .getTOs()) {
+ List<Constructor> constructors = Lists.newArrayList();
+ constructors.add(new Constructor(to.getName(), "super();"));
+
+ Header header = getHeaderFromEntry(mbe);
+ retVal.put(
+ to.getType(),
+ new GeneralClassTemplate(header, mbe.getPackageName(), to
+ .getName(), Collections.<String> emptyList(),
+ Collections.<String> emptyList(), to.getFields(),
+ to.getMethods(), false, false, constructors));
+ }
+ return retVal;
+ }
+
+ public static Map<String, GeneralClassTemplate> tOsFromRbe(
+ RuntimeBeanEntry rbe) {
+ Map<String, GeneralClassTemplate> retVal = Maps.newHashMap();
+ TOAttributesProcessor processor = new TOAttributesProcessor();
+ processor.processAttributes(rbe.getYangPropertiesToTypesMap(),
+ rbe.getPackageName());
+ for (org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.TemplateFactory.TOAttributesProcessor.TOInternal to : processor
+ .getTOs()) {
+ List<Constructor> constructors = Lists.newArrayList();
+ constructors.add(new Constructor(to.getName(), "super();"));
+
+ // TODO header
+ retVal.put(
+ to.getType(),
+ new GeneralClassTemplate(null, rbe.getPackageName(), to
+ .getName(), Collections.<String> emptyList(),
+ Collections.<String> emptyList(), to.getFields(),
+ to.getMethods(), false, false, constructors));
+ }
+ return retVal;
+ }
+
+ private static Header getHeaderFromEntry(AbstractEntry mbe) {
+ return new Header(mbe.getYangModuleName(), mbe.getYangModuleLocalname());
+ }
+
+ // TODO refactor attribute processors
+
+ private static class TOAttributesProcessor {
+
+ private final List<TOInternal> tos = Lists.newArrayList();
+
+ void processAttributes(Map<String, AttributeIfc> attributes,
+ String packageName) {
+ for (Entry<String, AttributeIfc> attrEntry : attributes.entrySet()) {
+ AttributeIfc attributeIfc = attrEntry.getValue();
+ if (attributeIfc instanceof TOAttribute) {
+ createTOInternal(packageName, attributeIfc);
+ }
+ if (attributeIfc instanceof ListAttribute) {
+ AttributeIfc innerAttr = ((ListAttribute) attributeIfc)
+ .getInnerAttribute();
+ if (innerAttr instanceof TOAttribute) {
+ createTOInternal(packageName, innerAttr);
+ }
+ }
+ }
+ }
+
+ private void createTOInternal(String packageName,
+ AttributeIfc attributeIfc) {
+ String fullyQualifiedName = FullyQualifiedNameHelper
+ .getFullyQualifiedName(packageName, attributeIfc.getUpperCaseCammelCase());
+
+ String type = fullyQualifiedName;
+ String name = attributeIfc.getUpperCaseCammelCase();
+ Map<String, AttributeIfc> attrs = ((TOAttribute) attributeIfc)
+ .getCapitalizedPropertiesToTypesMap();
+ // recursive processing
+ processAttributes(attrs, packageName);
+
+ tos.add(new TOInternal(type, name, attrs, packageName));
+ }
+
+ List<TOInternal> getTOs() {
+ return tos;
+ }
+
+ private static class TOInternal {
+ private final String type, name;
+ private List<Field> fields;
+ private List<MethodDefinition> methods;
+
+ public TOInternal(String type, String name,
+ Map<String, AttributeIfc> attrs, String packageName) {
+ super();
+ this.type = type;
+ this.name = name;
+ processAttrs(attrs, packageName);
+ }
+
+ private void processAttrs(Map<String, AttributeIfc> attrs,
+ String packageName) {
+ fields = Lists.newArrayList();
+ methods = Lists.newArrayList();
+
+ for (Entry<String, AttributeIfc> attrEntry : attrs.entrySet()) {
+ String innerName = attrEntry.getKey();
+ String varName = BindingGeneratorUtil
+ .parseToValidParamName(attrEntry.getKey());
+
+ String fullyQualifiedName = null;
+ if (attrEntry.getValue() instanceof TypedAttribute) {
+ Type innerType = ((TypedAttribute) attrEntry.getValue())
+ .getType();
+ fullyQualifiedName = innerType.getFullyQualifiedName();
+ } else if (attrEntry.getValue() instanceof ListAttribute) {
+ AttributeIfc innerAttr = ((ListAttribute) attrEntry
+ .getValue()).getInnerAttribute();
+
+ String innerTpe = innerAttr instanceof TypedAttribute ? ((TypedAttribute) innerAttr)
+ .getType().getFullyQualifiedName()
+ : FullyQualifiedNameHelper
+ .getFullyQualifiedName(packageName, attrEntry.getValue().getUpperCaseCammelCase());
+
+ fullyQualifiedName = "java.util.List<" + innerTpe + ">";
+ } else
+ fullyQualifiedName = FullyQualifiedNameHelper
+ .getFullyQualifiedName(packageName, attrEntry.getValue().getUpperCaseCammelCase());
+
+ fields.add(new Field(fullyQualifiedName, varName));
+
+ String getterName = "get" + innerName;
+ MethodDefinition getter = new MethodDefinition(
+ fullyQualifiedName, getterName,
+ Collections.<Field> emptyList(), "return "
+ + varName + ";");
+
+ String setterName = "set" + innerName;
+ MethodDefinition setter = new MethodDefinition("void",
+ setterName, Lists.newArrayList(new Field(
+ fullyQualifiedName, varName)), "this."
+ + varName + " = " + varName + ";");
+ methods.add(getter);
+ methods.add(setter);
+ }
+
+ }
+
+ String getType() {
+ return type;
+ }
+
+ String getName() {
+ return name;
+ }
+
+ List<Field> getFields() {
+ return fields;
+ }
+
+ List<MethodDefinition> getMethods() {
+ return methods;
+ }
+ }
+ }
+
+ private static class MXBeanInterfaceAttributesProcessor {
+ private static final String STRING_FULLY_QUALIFIED_NAME = "java.util.List";
+ private final List<MethodDeclaration> methods = Lists.newArrayList();
+
+ void processAttributes(Map<String, AttributeIfc> attributes,
+ String packageName) {
+ for (Entry<String, AttributeIfc> attrEntry : attributes.entrySet()) {
+ String returnType;
+ AttributeIfc attributeIfc = attrEntry.getValue();
+
+ if (attributeIfc instanceof TypedAttribute) {
+ returnType = ((TypedAttribute) attributeIfc).getType()
+ .getFullyQualifiedName();
+ } else if (attributeIfc instanceof TOAttribute) {
+ String fullyQualifiedName = FullyQualifiedNameHelper
+ .getFullyQualifiedName(packageName, attributeIfc.getUpperCaseCammelCase());
+
+ returnType = fullyQualifiedName;
+ } else if (attributeIfc instanceof ListAttribute) {
+ String fullyQualifiedName = null;
+
+ AttributeIfc innerAttr = ((ListAttribute) attributeIfc)
+ .getInnerAttribute();
+ if (innerAttr instanceof JavaAttribute) {
+ fullyQualifiedName = ((JavaAttribute) innerAttr)
+ .getType().getFullyQualifiedName();
+ } else if (innerAttr instanceof TOAttribute) {
+ fullyQualifiedName = FullyQualifiedNameHelper
+ .getFullyQualifiedName(packageName, innerAttr.getUpperCaseCammelCase());
+ }
+
+ returnType = STRING_FULLY_QUALIFIED_NAME.concat("<")
+ .concat(fullyQualifiedName).concat(">");
+ } else {
+ throw new UnsupportedOperationException(
+ "Attribute not supported: "
+ + attributeIfc.getClass());
+ }
+
+ String getterName = "get"
+ + attributeIfc.getUpperCaseCammelCase();
+ MethodDeclaration getter = new MethodDeclaration(returnType,
+ getterName, Collections.<Field> emptyList());
+
+ String varName = BindingGeneratorUtil
+ .parseToValidParamName(attrEntry.getKey());
+ String setterName = "set"
+ + attributeIfc.getUpperCaseCammelCase();
+ MethodDeclaration setter = new MethodDeclaration("void",
+ setterName, Lists.newArrayList(new Field(returnType,
+ varName)));
+ methods.add(getter);
+ methods.add(setter);
+
+ if (attributeIfc.getNullableDescription() != null) {
+ setter.setJavadoc(attrEntry.getValue()
+ .getNullableDescription());
+ }
+ }
+ }
+
+ List<MethodDeclaration> getMethods() {
+ return methods;
+ }
+ }
+
+ private static class AbstractFactoryAttributesProcessor {
+
+ private final List<Field> fields = Lists.newArrayList();
+ private static final String STRING_FULLY_QUALIFIED_NAME = "java.util.List";
+
+ void processAttributes(Map<String, AttributeIfc> attributes,
+ String packageName) {
+ for (Entry<String, AttributeIfc> attrEntry : attributes.entrySet()) {
+ String type;
+ AttributeIfc attributeIfc = attrEntry.getValue();
+
+ if (attributeIfc instanceof TypedAttribute) {
+ type = ((TypedAttribute) attributeIfc).getType()
+ .getFullyQualifiedName();
+ } else if (attributeIfc instanceof TOAttribute) {
+ String fullyQualifiedName = FullyQualifiedNameHelper
+ .getFullyQualifiedName(packageName, attributeIfc.getUpperCaseCammelCase());
+
+ type = fullyQualifiedName;
+ } else if (attributeIfc instanceof ListAttribute) {
+ String fullyQualifiedName = null;
+ AttributeIfc innerAttr = ((ListAttribute) attributeIfc)
+ .getInnerAttribute();
+ if (innerAttr instanceof JavaAttribute) {
+ fullyQualifiedName = ((JavaAttribute) innerAttr)
+ .getType().getFullyQualifiedName();
+ } else if (innerAttr instanceof TOAttribute) {
+ fullyQualifiedName = FullyQualifiedNameHelper
+ .getFullyQualifiedName(packageName, innerAttr.getUpperCaseCammelCase());
+ }
+
+ type = STRING_FULLY_QUALIFIED_NAME.concat("<")
+ .concat(fullyQualifiedName).concat(">");
+
+ } else {
+ throw new UnsupportedOperationException(
+ "Attribute not supported: "
+ + attributeIfc.getClass());
+ }
+
+ fields.add(new Field(type, attributeIfc
+ .getUpperCaseCammelCase()));
+ }
+ }
+
+ List<Field> getFields() {
+ return fields;
+ }
+ }
+
+ private static class AbstractModuleAttributesProcessor {
+
+ private static final String STRING_FULLY_QUALIFIED_NAME = "java.util.List";
+
+ private final List<ModuleField> moduleFields = Lists.newArrayList();
+ private final List<MethodDefinition> methods = Lists.newArrayList();
+
+ void processAttributes(Map<String, AttributeIfc> attributes,
+ String packageName) {
+ for (Entry<String, AttributeIfc> attrEntry : attributes.entrySet()) {
+ String type;
+ AttributeIfc attributeIfc = attrEntry.getValue();
+
+ if (attributeIfc instanceof TypedAttribute) {
+ type = ((TypedAttribute) attributeIfc).getType()
+ .getFullyQualifiedName();
+ } else if (attributeIfc instanceof TOAttribute) {
+ String fullyQualifiedName = FullyQualifiedNameHelper
+ .getFullyQualifiedName(packageName, attributeIfc.getUpperCaseCammelCase());
+
+ type = fullyQualifiedName;
+ } else if (attributeIfc instanceof ListAttribute) {
+ String fullyQualifiedName = null;
+ AttributeIfc innerAttr = ((ListAttribute) attributeIfc)
+ .getInnerAttribute();
+ if (innerAttr instanceof JavaAttribute) {
+ fullyQualifiedName = ((JavaAttribute) innerAttr)
+ .getType().getFullyQualifiedName();
+ } else if (innerAttr instanceof TOAttribute) {
+ fullyQualifiedName = FullyQualifiedNameHelper
+ .getFullyQualifiedName(packageName, innerAttr.getUpperCaseCammelCase());
+ }
+
+ type = STRING_FULLY_QUALIFIED_NAME.concat("<")
+ .concat(fullyQualifiedName).concat(">");
+ } else {
+ throw new UnsupportedOperationException(
+ "Attribute not supported: "
+ + attributeIfc.getClass());
+ }
+
+ boolean isDependency = false;
+ Dependency dependency = null;
+ Annotation overrideAnnotation = new Annotation("Override",
+ Collections.<Parameter> emptyList());
+ List<Annotation> annotations = Lists
+ .newArrayList(overrideAnnotation);
+
+ if (attributeIfc instanceof DependencyAttribute) {
+ isDependency = true;
+ dependency = ((DependencyAttribute) attributeIfc)
+ .getDependency();
+ annotations.add(Annotation
+ .createRequireIfcAnnotation(dependency.getSie()));
+ }
+
+ String varName = BindingGeneratorUtil
+ .parseToValidParamName(attrEntry.getKey());
+ moduleFields.add(new ModuleField(type, varName, attributeIfc
+ .getUpperCaseCammelCase(), attributeIfc
+ .getNullableDefault(), isDependency, dependency));
+
+ String getterName = "get"
+ + attributeIfc.getUpperCaseCammelCase();
+ MethodDefinition getter = new MethodDefinition(type,
+ getterName, Collections.<Field> emptyList(),
+ Lists.newArrayList(overrideAnnotation), "return "
+ + varName + ";");
+
+ String setterName = "set"
+ + attributeIfc.getUpperCaseCammelCase();
+
+ if (attributeIfc.getNullableDescription() != null) {
+ annotations.add(Annotation
+ .createDescriptionAnnotation(attributeIfc.getNullableDescription()));
+ }
+
+ MethodDefinition setter = new MethodDefinition("void",
+ setterName,
+ Lists.newArrayList(new Field(type, varName)),
+ annotations, "this." + varName + " = " + varName + ";");
+ setter.setJavadoc(attributeIfc.getNullableDescription());
+
+ methods.add(getter);
+ methods.add(setter);
+ }
+ }
+
+ List<ModuleField> getModuleFields() {
+ return moduleFields;
+ }
+
+ List<MethodDefinition> getMethods() {
+ return methods;
+ }
+
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.ftl;
+
+class TypeHelper {
+
+ /**
+ * Output string representing java notation of generic class, e.g.
+ * "List<String>" for input parameters List.class, String.class
+ */
+ static String getGenericType(Class<?> type, Class<?>... parameters) {
+ StringBuffer sb = new StringBuffer();
+ sb.append(type.getCanonicalName());
+ if (parameters.length > 0) {
+ sb.append("<");
+ boolean first = true;
+ for (Class<?> parameter : parameters) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append(",");
+ }
+ sb.append(parameter.getCanonicalName());
+ }
+ sb.append(">");
+ }
+ return sb.toString();
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.ftl.directives;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.List;
+import java.util.Map;
+
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.FtlTemplate;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Annotation;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Annotation.Parameter;
+
+import com.google.common.collect.Lists;
+
+import freemarker.core.Environment;
+import freemarker.template.SimpleSequence;
+import freemarker.template.TemplateDirectiveBody;
+import freemarker.template.TemplateDirectiveModel;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateModel;
+
+/**
+ * Add annotations to freemarker template.
+ */
+public class AnnotationsDirective implements TemplateDirectiveModel {
+
+ private static final String OBJECT = "object";
+
+ @Override
+ public void execute(Environment env, Map params, TemplateModel[] loopVars,
+ TemplateDirectiveBody body) throws TemplateException, IOException {
+ Object object = params.get(OBJECT);
+ List<Annotation> annotations = Lists.newArrayList();
+
+ if (object != null) {
+ if (object instanceof SimpleSequence)
+ annotations = ((SimpleSequence) object).toList();
+ else if (object instanceof FtlTemplate) {
+ annotations = ((FtlTemplate) object).getAnnotations();
+ } else
+ throw new IllegalArgumentException(
+ "Object must be a SimpleSequence or instance of "
+ + FtlTemplate.class + "but was "
+ + object.getClass());
+ }
+
+ Writer out = env.getOut();
+ StringBuilder build = new StringBuilder();
+ writeAnnotations(annotations, build, "");
+
+ if (!annotations.isEmpty())
+ out.write(build.toString().toCharArray());
+ }
+
+ static void writeAnnotations(List<Annotation> annotations,
+ StringBuilder build, String linePrefix) {
+ for (Annotation annotation : annotations) {
+ build.append(linePrefix + "@");
+ build.append(annotation.getName());
+ if (!annotation.getParams().isEmpty()) {
+ build.append("(");
+ for (Parameter param : annotation.getParams()) {
+ build.append(param.getKey());
+ build.append(" = ");
+ build.append(fixString(param.getValue()));
+ build.append(", ");
+ }
+ build.setCharAt(build.length() - 2, ')');
+ }
+ build.append(System.lineSeparator());
+ }
+ }
+
+ private static String fixString(String value) {
+ // TODO replace with compress single line if possible
+ return value.replaceAll("\\r\\n|\\r|\\n", " ").replaceAll(" +", " ");
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.ftl.directives;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.List;
+import java.util.Map;
+
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.GeneralClassTemplate;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Constructor;
+
+import com.google.common.collect.Lists;
+
+import freemarker.core.Environment;
+import freemarker.template.SimpleSequence;
+import freemarker.template.TemplateDirectiveBody;
+import freemarker.template.TemplateDirectiveModel;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateModel;
+
+/**
+ * Add annotations to freemarker template.
+ */
+public class ConstructorsDirective implements TemplateDirectiveModel {
+
+ private static final String OBJECT = "object";
+
+ @Override
+ public void execute(Environment env, Map params, TemplateModel[] loopVars,
+ TemplateDirectiveBody body) throws TemplateException, IOException {
+ Object object = params.get(OBJECT);
+ List<Constructor> constructors = Lists.newArrayList();
+
+ if (object != null) {
+ if (object instanceof SimpleSequence)
+ constructors = ((SimpleSequence) object).toList();
+ else if (object instanceof GeneralClassTemplate) {
+ constructors = ((GeneralClassTemplate) object)
+ .getConstructors();
+ } else
+ throw new IllegalArgumentException(
+ "Object must be a SimpleSequence or instance of "
+ + GeneralClassTemplate.class + "but was "
+ + object.getClass());
+ }
+
+ Writer out = env.getOut();
+ StringBuilder build = new StringBuilder();
+ for (Constructor constr : constructors) {
+ build.append(" ");
+ if (constr.isPublic())
+ build.append("public ");
+ build.append(constr.getTypeName() + " ");
+ build.append("() {");
+ build.append(System.lineSeparator());
+ build.append(" ");
+ build.append(" ");
+ build.append(constr.getBody());
+ build.append(System.lineSeparator());
+ build.append(" ");
+ build.append("}");
+ build.append(System.lineSeparator());
+ build.append(System.lineSeparator());
+ }
+
+ if (!constructors.isEmpty())
+ out.write(build.toString().toCharArray());
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.ftl.directives;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.List;
+import java.util.Map;
+
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.FtlTemplate;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Field;
+
+import com.google.common.collect.Lists;
+
+import freemarker.core.Environment;
+import freemarker.template.SimpleSequence;
+import freemarker.template.TemplateDirectiveBody;
+import freemarker.template.TemplateDirectiveModel;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateModel;
+
+/**
+ * Add fields to freemarker template.
+ */
+public class FieldsDirectiveProg implements TemplateDirectiveModel {
+
+ private static final String OBJECT = "object";
+
+ @Override
+ public void execute(Environment env, Map params, TemplateModel[] loopVars,
+ TemplateDirectiveBody body) throws TemplateException, IOException {
+ Object object = params.get(OBJECT);
+ List<Field> fields = Lists.newArrayList();
+
+ if (object != null) {
+ if (object instanceof SimpleSequence)
+ fields = ((SimpleSequence) object).toList();
+ else if (object instanceof FtlTemplate) {
+ fields = ((FtlTemplate) object).getFields();
+ } else
+ throw new IllegalArgumentException(
+ "Object must be a SimpleSequence or instance of "
+ + FtlTemplate.class + "but was "
+ + object.getClass());
+ }
+
+ Writer out = env.getOut();
+ StringBuilder build = new StringBuilder();
+ for (Field field : fields) {
+ build.append(" private ");
+ for (String mod : field.getModifiers()) {
+ build.append(mod + " ");
+ }
+ build.append(field.getType() + " ");
+ build.append(field.getName());
+ if (field.getDefinition() != null)
+ build.append(" = " + field.getDefinition());
+ build.append(";");
+ build.append(System.lineSeparator());
+ }
+
+ if (!fields.isEmpty())
+ out.write(build.toString().toCharArray());
+ }
+
+ // String templateStr = "Hello ${user}";
+ // Template t = new Template("name", new StringReader(templateStr), new
+ // Configuration());
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.ftl.directives;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.Writer;
+import java.util.Map;
+
+import com.google.common.collect.Maps;
+
+import freemarker.core.Environment;
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import freemarker.template.TemplateDirectiveBody;
+import freemarker.template.TemplateDirectiveModel;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateModel;
+
+/**
+ * Add fields to freemarker template.
+ */
+public class FieldsDirectiveTemplate implements TemplateDirectiveModel {
+
+ private static final String OBJECT = "object";
+
+ @Override
+ public void execute(Environment env, Map params, TemplateModel[] loopVars,
+ TemplateDirectiveBody body) throws TemplateException, IOException {
+ Object object = params.get(OBJECT);
+
+ // TODO check type
+
+ String templateStr = " <#list fields as field>"
+ + "private <#if field.final==true>final </#if> <#if field.static==true>static </#if>"
+ + "${field.type} ${field.name}<#if field.definition??> = ${field.definition}</#if>;"
+ + System.lineSeparator() + " </#list>";
+ Template t = new Template("name", new StringReader(templateStr),
+ new Configuration());
+
+ try {
+ Map<String, Object> map = Maps.newHashMap();
+ map.put("fields", object);
+ Writer out = env.getOut();
+ t.process(map, out);
+ } catch (TemplateException e) {
+ throw new IllegalStateException(
+ "Template error while generating fields" + e);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.ftl.directives;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Date;
+import java.util.Map;
+
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Header;
+
+import com.google.common.base.Preconditions;
+
+import freemarker.core.Environment;
+import freemarker.ext.beans.StringModel;
+import freemarker.template.TemplateDirectiveBody;
+import freemarker.template.TemplateDirectiveModel;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateModel;
+
+/**
+ * Add annotations to freemarker template.
+ */
+public class HeaderDirective implements TemplateDirectiveModel {
+
+ private static final String GENERATOR_CLASS = JMXGenerator.class
+ .getCanonicalName();
+ private static final String OBJECT = "header";
+
+ @Override
+ public void execute(Environment env, Map params, TemplateModel[] loopVars,
+ TemplateDirectiveBody body) throws TemplateException, IOException {
+
+ // FIXME do not allow null header
+ // Preconditions.checkNotNull(object, "Null type declaration");
+ Object object = params.get(OBJECT);
+ Header header = null;
+ if (object != null) {
+ object = ((StringModel) object).getWrappedObject();
+ Preconditions.checkArgument(object instanceof Header,
+ "Template header should be instance of " + Header.class
+ + " but was " + object.getClass());
+
+ header = (Header) object;
+ }
+
+ Writer out = env.getOut();
+ StringBuilder build = new StringBuilder();
+ build.append("/**");
+ build.append(System.lineSeparator());
+ build.append("* ");
+ build.append("Generated file");
+ build.append(System.lineSeparator());
+ build.append(System.lineSeparator());
+ build.append("* ");
+ build.append("Generated from: ");
+ build.append(header != null ? header.toString() : "");
+ build.append(System.lineSeparator());
+ build.append("* ");
+ build.append("Generated by: " + GENERATOR_CLASS);
+ build.append(System.lineSeparator());
+ build.append("* ");
+ build.append("Generated at: " + new Date());
+ build.append(System.lineSeparator());
+ build.append("* ");
+ build.append(System.lineSeparator());
+ build.append("* ");
+ build.append("Do not modify this file unless it is present under src/main directory ");
+ build.append(System.lineSeparator());
+ build.append("*/");
+ build.append(System.lineSeparator());
+
+ out.write(build.toString().toCharArray());
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.ftl.directives;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Map;
+
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.FtlTemplate;
+
+import freemarker.core.Environment;
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateDirectiveBody;
+import freemarker.template.TemplateDirectiveModel;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateModel;
+
+/**
+ * Add javadoc to freemarker template as String.
+ */
+public class JavadocDirective implements TemplateDirectiveModel {
+
+ private static final String OBJECT = "object";
+
+ @Override
+ public void execute(Environment env, Map params, TemplateModel[] loopVars,
+ TemplateDirectiveBody body) throws TemplateException, IOException {
+ Object object = params.get(OBJECT);
+ String javadoc = "";
+
+ if (object != null) {
+ if (object instanceof SimpleScalar)
+ javadoc = ((SimpleScalar) object).getAsString();
+ else if (object instanceof FtlTemplate) {
+ javadoc = ((FtlTemplate) object).getJavadoc();
+ } else
+ throw new IllegalArgumentException(
+ "Object must be a String or instance of "
+ + FtlTemplate.class + "but was "
+ + object.getClass());
+ }
+
+ Writer out = env.getOut();
+ StringBuilder build = new StringBuilder();
+ writeJavadoc(build, javadoc, "");
+ out.write(build.toString().toCharArray());
+ }
+
+ static void writeJavadoc(StringBuilder build, String javadoc,
+ String linePrefix) {
+ build.append(linePrefix + "/**");
+ build.append(System.lineSeparator());
+ build.append(linePrefix + "* ");
+ build.append(javadoc == null ? "" : javadoc);
+ build.append(System.lineSeparator());
+ build.append(linePrefix + "*/");
+ build.append(System.lineSeparator());
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.ftl.directives;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.List;
+import java.util.Map;
+
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.FtlTemplate;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Field;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Method;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.MethodDeclaration;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.MethodDefinition;
+
+import com.google.common.collect.Lists;
+
+import freemarker.core.Environment;
+import freemarker.template.SimpleSequence;
+import freemarker.template.TemplateDirectiveBody;
+import freemarker.template.TemplateDirectiveModel;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateModel;
+
+/**
+ * Add annotations to freemarker template.
+ */
+public class MethodsDirective implements TemplateDirectiveModel {
+
+ private static final String OBJECT = "object";
+
+ @Override
+ public void execute(Environment env, Map params, TemplateModel[] loopVars,
+ TemplateDirectiveBody body) throws TemplateException, IOException {
+ Object object = params.get(OBJECT);
+ List<? extends Method> methods = Lists.newArrayList();
+
+ if (object != null) {
+ if (object instanceof SimpleSequence)
+ methods = ((SimpleSequence) object).toList();
+ else if (object instanceof FtlTemplate) {
+ methods = ((FtlTemplate) object).getMethods();
+ } else
+ throw new IllegalArgumentException(
+ "Object must be a SimpleSequence or instance of "
+ + FtlTemplate.class + "but was "
+ + object.getClass());
+ }
+
+ Writer out = env.getOut();
+ StringBuilder build = new StringBuilder();
+ for (Method method : methods) {
+ if (method.getJavadoc() != null)
+ JavadocDirective.writeJavadoc(build, method.getJavadoc(), " ");
+
+ if (!method.getAnnotations().isEmpty()) {
+ AnnotationsDirective.writeAnnotations(method.getAnnotations(),
+ build, " ");
+ }
+
+ build.append(" " + "public ");
+ for (String mod : method.getModifiers()) {
+ build.append(mod + " ");
+ }
+ build.append(method.getReturnType() + " ");
+
+ build.append(method.getName() + "(");
+ for (Field param : method.getParameters()) {
+ for (String mod : param.getModifiers()) {
+ build.append(mod + " ");
+ }
+ build.append(param.getType() + " ");
+ build.append(param.getName() + ", ");
+ }
+ if (method.getParameters().isEmpty())
+ build.append(")");
+ else {
+ build.deleteCharAt(build.length() - 1);
+ build.deleteCharAt(build.length() - 1);
+ build.append(')');
+ }
+
+ if (method instanceof MethodDeclaration) {
+ build.append(";");
+ build.append(System.lineSeparator());
+ } else if (method instanceof MethodDefinition) {
+ if (!((MethodDefinition) method).getThrowsExceptions()
+ .isEmpty())
+ build.append(" throws ");
+ for (String ex : ((MethodDefinition) method)
+ .getThrowsExceptions()) {
+ build.append(ex + " ");
+ }
+ build.append(" {");
+ build.append(System.lineSeparator());
+ build.append(" ");
+ build.append(((MethodDefinition) method).getBody());
+ build.append(System.lineSeparator());
+ build.append(" ");
+ build.append("}");
+ build.append(System.lineSeparator());
+ }
+ build.append(System.lineSeparator());
+
+ }
+
+ if (!methods.isEmpty())
+ out.write(build.toString().toCharArray());
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.ftl.directives;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.List;
+import java.util.Map;
+
+import org.opendaylight.controller.config.api.JmxAttribute;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.AbstractModuleTemplate;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.ModuleField;
+
+import com.google.common.collect.Lists;
+
+import freemarker.core.Environment;
+import freemarker.template.SimpleSequence;
+import freemarker.template.TemplateDirectiveBody;
+import freemarker.template.TemplateDirectiveModel;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateModel;
+
+/**
+ * Add annotations to freemarker template.
+ */
+public class ModuleFieldsDirective implements TemplateDirectiveModel {
+
+ private static final String OBJECT = "moduleFields";
+
+ @Override
+ public void execute(Environment env, Map params, TemplateModel[] loopVars,
+ TemplateDirectiveBody body) throws TemplateException, IOException {
+ Object object = params.get(OBJECT);
+ List<ModuleField> fields = Lists.newArrayList();
+
+ if (object != null) {
+ if (object instanceof SimpleSequence)
+ fields = ((SimpleSequence) object).toList();
+ else if (object instanceof AbstractModuleTemplate) {
+ fields = ((AbstractModuleTemplate) object).getModuleFields();
+ } else
+ throw new IllegalArgumentException(
+ "Object must be a SimpleSequence or instance of "
+ + AbstractModuleTemplate.class + "but was "
+ + object.getClass());
+ }
+
+ Writer out = env.getOut();
+ StringBuilder build = new StringBuilder();
+ for (ModuleField field : fields) {
+ build.append(" ");
+ build.append("protected final "
+ + JmxAttribute.class.getCanonicalName() + " "
+ + field.getName() + "JmxAttribute = new "
+ + JmxAttribute.class.getCanonicalName() + "(\""
+ + field.getAttributeName() + "\");");
+ build.append(System.lineSeparator());
+
+ build.append(" private ");
+ for (String mod : field.getModifiers()) {
+ build.append(mod + " ");
+ }
+ build.append(field.getType() + " ");
+ build.append(field.getName());
+ if (field.getNullableDefault() != null)
+ build.append(" = " + field.getNullableDefault());
+ build.append(";");
+
+ if (field.isDependent()) {
+ String comment = field.getDependency().isMandatory() ? "mandatory"
+ : "optional";
+ build.append(" // " + comment);
+ }
+ build.append(System.lineSeparator());
+
+ build.append(System.lineSeparator());
+
+ }
+
+ if (!fields.isEmpty())
+ out.write(build.toString().toCharArray());
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.ftl.directives;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Collection;
+import java.util.Map;
+
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.TypeDeclaration;
+
+import com.google.common.base.Preconditions;
+
+import freemarker.core.Environment;
+import freemarker.ext.beans.StringModel;
+import freemarker.template.TemplateDirectiveBody;
+import freemarker.template.TemplateDirectiveModel;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateModel;
+
+/**
+ * Add type declaration to freemarker template.
+ */
+public class TypeDeclarationDirective implements TemplateDirectiveModel {
+
+ private static final String OBJECT = "object";
+
+ @Override
+ public void execute(Environment env, Map params, TemplateModel[] loopVars,
+ TemplateDirectiveBody body) throws TemplateException, IOException {
+ Object object = params.get(OBJECT);
+ Preconditions.checkNotNull(object, "Null type declaration");
+
+ object = ((StringModel) object).getWrappedObject();
+ Preconditions.checkArgument(
+ object instanceof TypeDeclaration,
+ "Type declaration should be instance of "
+ + TypeDeclaration.class + " but was "
+ + object.getClass());
+
+ TypeDeclaration type = (TypeDeclaration) object;
+
+ Writer out = env.getOut();
+ StringBuilder build = new StringBuilder("public ");
+ if (type.isAbstract())
+ build.append("abstract ");
+ if (type.isFinal())
+ build.append("final ");
+ build.append(type.getType() + " ");
+ build.append(type.getName() + " ");
+
+ generateExtendOrImplement(build, "extends", type.getExtended());
+
+ generateExtendOrImplement(build, "implements", type.getImplemented());
+
+ build.append(System.lineSeparator());
+ out.write(build.toString().toCharArray());
+ }
+
+ private void generateExtendOrImplement(StringBuilder build, String prefix,
+ Collection<String> elements) {
+ if (elements.isEmpty())
+ return;
+
+ build.append(prefix + " ");
+
+ for (String extended : elements) {
+ build.append(extended);
+ build.append(", ");
+ }
+ build.deleteCharAt(build.length() - 1);
+ build.deleteCharAt(build.length() - 1);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.ftl.directives;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Map;
+
+import freemarker.core.Environment;
+import freemarker.template.TemplateDirectiveBody;
+import freemarker.template.TemplateDirectiveModel;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateModel;
+
+/**
+ * Add annotations to freemarker template.
+ */
+public class UnimplementedExceptionDirective implements TemplateDirectiveModel {
+
+ @Override
+ public void execute(Environment env, Map params, TemplateModel[] loopVars,
+ TemplateDirectiveBody body) throws TemplateException, IOException {
+
+ Writer out = env.getOut();
+ StringBuilder build = new StringBuilder();
+ build.append(" ");
+ build.append("throw new "
+ + UnsupportedOperationException.class.getCanonicalName()
+ + "(\"Unimplemented stub method\");");
+ build.append(System.lineSeparator());
+
+ out.write(build.toString().toCharArray());
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.ftl.model;
+
+import java.util.List;
+
+import org.opendaylight.controller.config.api.annotations.Description;
+import org.opendaylight.controller.config.api.annotations.RequireInterface;
+import org.opendaylight.controller.config.api.annotations.ServiceInterfaceAnnotation;
+import org.opendaylight.controller.config.yangjmxgenerator.ServiceInterfaceEntry;
+import org.opendaylight.yangtools.yang.common.QName;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+
+public class Annotation {
+ final String name;
+ final List<Parameter> params;
+
+ public Annotation(String name, List<Parameter> params) {
+ this.name = name;
+ this.params = params;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public List<Parameter> getParams() {
+ return params;
+ }
+
+ public static Annotation createDescriptionAnnotation(String description) {
+ Preconditions.checkNotNull(description,
+ "Cannot create annotation from null description");
+ return new Annotation(Description.class.getCanonicalName(),
+ Lists.newArrayList(new Parameter("value", q(description))));
+ }
+
+ public static Annotation createSieAnnotation(QName qname,
+ String exportedClassName) {
+ Preconditions.checkNotNull(qname,
+ "Cannot create annotation from null qname");
+ Preconditions.checkNotNull(exportedClassName,
+ "Cannot create annotation from null exportedClassName");
+
+ List<Parameter> params = Lists.newArrayList(new Parameter("value",
+ q(qname.getLocalName())));
+ params.add(new Parameter("osgiRegistrationType", exportedClassName
+ + ".class"));
+ return new Annotation(
+ ServiceInterfaceAnnotation.class.getCanonicalName(), params);
+ }
+
+ public static Annotation createRequireIfcAnnotation(
+ ServiceInterfaceEntry sie) {
+ String reqIfc = sie.getFullyQualifiedName() + ".class";
+ return new Annotation(RequireInterface.class.getCanonicalName(),
+ Lists.newArrayList(new Parameter("value", reqIfc)));
+ }
+
+ private static final String quote = "\"";
+
+ private static String q(String nullableDescription) {
+ return nullableDescription == null ? null : quote + nullableDescription
+ + quote;
+ }
+
+ public static class Parameter {
+ private final String key, value;
+
+ public Parameter(String key, String value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getValue() {
+ return value;
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.ftl.model;
+
+public class Constructor {
+
+ private final String typeName, body;
+ private final boolean isPublic;
+
+ public Constructor(String typeName, String body, boolean isPublic) {
+ super();
+ this.typeName = typeName;
+ this.body = body;
+ this.isPublic = isPublic;
+ }
+
+ // TODO add arguments if necessary
+
+ public Constructor(String typeName, String body) {
+ this(typeName, body, true);
+ }
+
+ public String getTypeName() {
+ return typeName;
+ }
+
+ public String getBody() {
+ return body;
+ }
+
+ public boolean isPublic() {
+ return isPublic;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.ftl.model;
+
+import java.util.List;
+
+import com.google.common.collect.Lists;
+
+public class Field {
+ private final String type;
+ private final String name;
+ private final String definition;
+ private final List<String> modifiers;
+
+ public Field(String type, String name) {
+ this(Lists.<String> newArrayList(), type, name, null);
+ }
+
+ public Field(List<String> modifiers, String type, String name) {
+ this(modifiers, type, name, null);
+ }
+
+ public Field(List<String> modifiers, String type, String name,
+ String definition) {
+ this.modifiers = modifiers;
+ this.type = type;
+ this.name = name;
+ this.definition = definition;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public List<String> getModifiers() {
+ return modifiers;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getDefinition() {
+ return definition;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.ftl.model;
+
+public class Header {
+ private final String yangModuleName, yangModuleLocalName;
+
+ public Header(String yangModuleName, String yangModuleLocalName) {
+ super();
+ this.yangModuleName = yangModuleName;
+ this.yangModuleLocalName = yangModuleLocalName;
+ }
+
+ public String getYangModuleName() {
+ return yangModuleName;
+ }
+
+ public String getYangModuleLocalName() {
+ return yangModuleLocalName;
+ }
+
+ @Override
+ public String toString() {
+ return "yang module name: " + yangModuleName + " "
+ + " yang module local name: " + yangModuleLocalName;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.ftl.model;
+
+import java.util.List;
+
+public interface Method {
+ List<String> getModifiers();
+
+ String getReturnType();
+
+ String getName();
+
+ List<Field> getParameters();
+
+ String getJavadoc();
+
+ List<Annotation> getAnnotations();
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.ftl.model;
+
+import java.util.Collections;
+import java.util.List;
+
+public class MethodDeclaration implements Method {
+ private final String returnType;
+ private final String name;
+ private final List<Field> parameters;
+ private String javadoc = null;
+ private final List<Annotation> annotations;
+
+ public MethodDeclaration(String returnType, String name,
+ List<Field> parameters) {
+ this(returnType, name, parameters, Collections.<Annotation> emptyList());
+ }
+
+ public MethodDeclaration(String returnType, String name,
+ List<Field> parameters, List<Annotation> annotations) {
+ this.returnType = returnType;
+ this.name = name;
+ this.parameters = parameters;
+ this.annotations = annotations;
+ }
+
+ @Override
+ public List<Annotation> getAnnotations() {
+ return annotations;
+ }
+
+ @Override
+ public String getJavadoc() {
+ return javadoc;
+ }
+
+ public void setJavadoc(String javadoc) {
+ this.javadoc = javadoc;
+ }
+
+ @Override
+ public String getReturnType() {
+ return returnType;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public List<Field> getParameters() {
+ return parameters;
+ }
+
+ @Override
+ public List<String> getModifiers() {
+ return Collections.emptyList();
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.ftl.model;
+
+import java.util.Collections;
+import java.util.List;
+
+public class MethodDefinition implements Method {
+ private final List<String> modifiers;
+ private final String returnType;
+ private final String name;
+ private final List<Field> parameters;
+ private final List<String> throwsExceptions;
+ private final String body;
+ private String javadoc = null;
+ private final List<Annotation> annotations;
+
+ // TODO remove, Constructor is in separate class
+ public static MethodDefinition createConstructor(String name,
+ List<Field> parameters, String body) {
+ return new MethodDefinition("", name, parameters, body);
+
+ }
+
+ public MethodDefinition(String returnType, String name,
+ List<Field> parameters, String body) {
+ this(Collections.<String> emptyList(), returnType, name, parameters,
+ Collections.<String> emptyList(), Collections
+ .<Annotation> emptyList(), body);
+ }
+
+ public MethodDefinition(String returnType, String name,
+ List<Field> parameters, List<Annotation> annotations, String body) {
+ this(Collections.<String> emptyList(), returnType, name, parameters,
+ Collections.<String> emptyList(), annotations, body);
+ }
+
+ public MethodDefinition(List<String> modifiers, String returnType,
+ String name, List<Field> parameters, List<String> throwsExceptions,
+ List<Annotation> annotations, String body) {
+ this.modifiers = modifiers;
+ this.returnType = returnType;
+ this.name = name;
+ this.parameters = parameters;
+ this.throwsExceptions = throwsExceptions;
+ this.body = body;
+ this.annotations = annotations;
+ }
+
+ @Override
+ public List<Annotation> getAnnotations() {
+ return annotations;
+ }
+
+ @Override
+ public String getJavadoc() {
+ return javadoc;
+ }
+
+ public void setJavadoc(String javadoc) {
+ this.javadoc = javadoc;
+ }
+
+ @Override
+ public String getReturnType() {
+ return returnType;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public List<Field> getParameters() {
+ return parameters;
+ }
+
+ public List<String> getThrowsExceptions() {
+ return throwsExceptions;
+ }
+
+ public String getBody() {
+ return body;
+ }
+
+ @Override
+ public List<String> getModifiers() {
+ return modifiers;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.ftl.model;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.opendaylight.controller.config.yangjmxgenerator.attribute.DependencyAttribute.Dependency;
+
+public class ModuleField extends Field {
+
+ private final String nullableDefault, attributeName;
+ private final boolean dependent;
+ private final Dependency dependency;
+
+ public ModuleField(List<String> modifiers, String type, String name,
+ String attributeName, String nullableDefault, boolean isDependency,
+ Dependency dependency) {
+ super(modifiers, type, name);
+ this.nullableDefault = nullableDefault;
+ this.dependent = isDependency;
+ this.dependency = dependency;
+ this.attributeName = attributeName;
+ }
+
+ public ModuleField(String type, String name, String attributeName,
+ String nullableDefault, boolean isDependency, Dependency dependency) {
+ this(Collections.<String> emptyList(), type, name, attributeName,
+ nullableDefault, isDependency, dependency);
+ }
+
+ public Dependency getDependency() {
+ return dependency;
+ }
+
+ public String getNullableDefault() {
+ return nullableDefault;
+ }
+
+ public boolean isDependent() {
+ return dependent;
+ }
+
+ public String getAttributeName() {
+ return attributeName;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.ftl.model;
+
+import java.util.List;
+
+public class TypeDeclaration {
+ private final String type, name;
+ private final List<String> extended, implemented;
+ private final boolean isAbstract, isFinal;
+
+ public TypeDeclaration(String type, String name, List<String> extended,
+ List<String> implemented, boolean isAbstract, boolean isFinal) {
+ super();
+ this.type = type;
+ this.name = name;
+ this.extended = extended;
+ this.implemented = implemented;
+ this.isAbstract = isAbstract;
+ this.isFinal = isFinal;
+ }
+
+ public TypeDeclaration(String type, String name, List<String> extended,
+ List<String> implemented) {
+ this(type, name, extended, implemented, false, false);
+ }
+
+ public boolean isAbstract() {
+ return isAbstract;
+ }
+
+ public boolean isFinal() {
+ return isFinal;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public List<String> getExtended() {
+ return extended;
+ }
+
+ public List<String> getImplemented() {
+ return implemented;
+ }
+
+ @Override
+ public String toString() {
+ return "TypeDeclaration{" + "type='" + type + '\'' + ", name='" + name
+ + '\'' + '}';
+ }
+}
--- /dev/null
+<@headerD header=header/>
+package ${packageName};
+
+<@javadocD object=javadoc/>
+<@annotationsD object=annotations/>
+<#-- class/interface -->
+<@typeDeclarationD object=typeDeclaration/>
+{
+
+<@constructorsD object=constructors>
+</@constructorsD>
+<@fieldsD object=fields/>
+
+<@methodsD object=methods/>
+}
--- /dev/null
+<@headerD header=header/>
+package ${packageName};
+
+<@javadocD object=javadoc/>
+<@typeDeclarationD object=typeDeclaration/>
+{
+
+ public static final java.lang.String NAME = "${globallyUniqueName}";
+ private static final java.util.Set<Class<? extends ${abstractServiceInterfaceType}>> serviceIfcs = new java.util.HashSet<Class<? extends ${abstractServiceInterfaceType}>>();
+ <#if providedServices??>
+ static {
+ <#list providedServices as refId>
+ serviceIfcs.add(${refId});
+ </#list>
+ }
+ </#if>
+
+ @Override
+ public final boolean isModuleImplementingServiceInterface(Class<? extends ${abstractServiceInterfaceType}> serviceInterface) {
+ return serviceIfcs.contains(serviceInterface);
+ }
+
+ @Override
+ public ${moduleType} createModule(String instanceName, ${dependencyResolverType} dependencyResolver) {
+ return instantiateModule(instanceName, dependencyResolver);
+ }
+
+ @Override
+ public ${moduleType} createModule(String instanceName, ${dependencyResolverType} dependencyResolver, ${dynamicMBeanWithInstanceType} old) throws Exception {
+ ${moduleInstanceType} oldModule = null;
+ try {
+ oldModule = (${moduleInstanceType}) old.getModule();
+ } catch(Exception e) {
+ return handleChangedClass(old);
+ }
+ ${moduleInstanceType} module = instantiateModule(instanceName, dependencyResolver, oldModule, old.getInstance());
+
+ <#list fields as attr>
+ module.set${attr.name}(oldModule.get${attr.name}());
+ </#list>
+
+ return module;
+ }
+
+ public ${moduleInstanceType} instantiateModule(String instanceName, ${dependencyResolverType} dependencyResolver, ${moduleInstanceType} oldModule, ${instanceType} oldInstance) {
+ return new ${moduleInstanceType}(new ${moduleNameType}(NAME, instanceName), dependencyResolver, oldModule, oldInstance);
+ }
+
+ public ${moduleInstanceType} instantiateModule(String instanceName, ${dependencyResolverType} dependencyResolver) {
+ return new ${moduleInstanceType}(new ${moduleNameType}(NAME, instanceName), dependencyResolver);
+ }
+
+ @Override
+ public final String getImplementationName() {
+ return NAME;
+ }
+
+
+ public ${moduleInstanceType} handleChangedClass(${dynamicMBeanWithInstanceType} old) throws Exception {
+ throw new UnsupportedOperationException("Class reloading is not supported");
+ }
+
+}
--- /dev/null
+<@headerD header=header/>
+package ${packageName};
+
+<@javadocD object=javadoc/>
+<@annotationsD object=annotations/>
+<@typeDeclarationD object=typeDeclaration/>
+{
+ // attributes
+ <@moduleFieldsD moduleFields=moduleFields/>
+ //attributes end
+
+ private static final ${loggerType} logger = ${loggerFactoryType}.getLogger(${typeDeclaration.name}.class);
+
+ private final ${typeDeclaration.name} oldModule;
+ private final ${instanceType} oldInstance;
+ private ${instanceType} instance;
+ private final ${dependencyResolverType} dependencyResolver;
+ private final ${moduleNameType} name;
+ <#if runtime=true>
+ private ${registratorType} rootRuntimeBeanRegistratorWrapper;
+ </#if>
+
+ public ${typeDeclaration.name}(${moduleNameType} name, ${dependencyResolverType} dependencyResolver) {
+ this.name = name;
+ this.dependencyResolver = dependencyResolver;
+ this.oldInstance = null;
+ this.oldModule = null;
+ }
+
+ public ${typeDeclaration.name}(${moduleNameType} name, ${dependencyResolverType} dependencyResolver, ${typeDeclaration.name} oldModule, ${instanceType} oldInstance) {
+ this.name = name;
+ this.dependencyResolver = dependencyResolver;
+ this.oldInstance = oldInstance;
+ this.oldModule = oldModule;
+ }
+
+ // getters and setters exported into MXBean
+ <@methodsD object=methods/>
+
+ <#if runtime=true>
+ public ${registratorType} getRootRuntimeBeanRegistratorWrapper(){
+ return rootRuntimeBeanRegistratorWrapper;
+ }
+
+ @Override
+ public void setRuntimeBeanRegistrator(${rootRuntimeRegistratorType} rootRuntimeRegistrator){
+ this.rootRuntimeBeanRegistratorWrapper = new ${registratorType}(rootRuntimeRegistrator);
+ }
+ </#if>
+
+ @Override
+ public void validate(){
+ <#list moduleFields as field>
+ <#if field.dependent==true && field.dependency.mandatory==true>
+ dependencyResolver.validateDependency(${field.dependency.sie.fullyQualifiedName}.class, ${field.name}, ${field.name}JmxAttribute);
+ </#if>
+ </#list>
+ }
+
+ // caches of resolved dependencies
+ <#list moduleFields as field>
+ <#if field.dependent==true>
+ private ${field.dependency.sie.exportedOsgiClassName} ${field.name}Dependency;
+ protected final ${field.dependency.sie.exportedOsgiClassName} get${field.attributeName}Dependency(){
+ return ${field.name}Dependency;
+ }
+ </#if>
+ </#list>
+
+
+ @Override
+ public final ${instanceType} getInstance(){
+ if(instance==null) {
+
+ <#list moduleFields as field>
+ <#if field.dependent==true>
+
+ <#if field.dependency.mandatory==false>
+ if(${field.name}!=null) {
+ </#if>
+
+ ${field.name}Dependency = dependencyResolver.resolveInstance(${field.dependency.sie.exportedOsgiClassName}.class, ${field.name}, ${field.name}JmxAttribute);
+
+ <#if field.dependency.mandatory==false>
+ }
+ </#if>
+ </#if>
+ </#list>
+
+ if(oldInstance!=null && canReuseInstance(oldModule)) {
+ instance = reuseInstance(oldInstance);
+ } else {
+ if(oldInstance!=null) {
+ try {
+ oldInstance.close();
+ } catch(Exception e) {
+ logger.error("An error occurred while closing old instance " + oldInstance, e);
+ }
+ }
+ instance = createInstance();
+ }
+ }
+ return instance;
+ }
+
+ @Override
+ public final ${moduleNameType} getName() {
+ return name;
+ }
+
+ public boolean canReuseInstance(${typeDeclaration.name} oldModule){
+ // allow reusing of old instance if no parameters was changed
+ return equals(oldModule);
+ }
+
+ public ${instanceType} reuseInstance(${instanceType} oldInstance){
+ // implement if instance reuse should be supported. Override canReuseInstance to change the criteria.
+ return oldInstance;
+ }
+
+ public abstract ${instanceType} createInstance();
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ ${typeDeclaration.name} other = (${typeDeclaration.name}) obj;
+
+
+ <#list moduleFields as field>
+ <#if field.dependent==true>
+ if (${field.name}Dependency == null) {
+ if (other.${field.name}Dependency != null)
+ return false;
+ } else if (!${field.name}Dependency.equals(other.${field.name}Dependency))
+ return false;
+ <#else>
+ if (${field.name} == null) {
+ if (other.${field.name} != null)
+ return false;
+ } else if (!${field.name}.equals(other.${field.name}))
+ return false;
+ </#if>
+ </#list>
+
+ return true;
+ }
+
+}
--- /dev/null
+<@headerD header=header/>
+package ${packageName};
+
+<@javadocD object=javadoc/>
+<@typeDeclarationD object=typeDeclaration/>
+{
+
+ public ${typeDeclaration.name}(${moduleNameType} name, ${dependencyResolverType} dependencyResolver) {
+ super(name, dependencyResolver);
+ }
+
+ public ${typeDeclaration.name}(${moduleNameType} name, ${dependencyResolverType} dependencyResolver, ${typeDeclaration.name} oldModule, ${instanceType} oldInstance) {
+ super(name, dependencyResolver, oldModule, oldInstance);
+ }
+
+ @Override
+ public void validate(){
+ super.validate();
+ // Add custom validation for module attributes here.
+ }
+
+ @Override
+ public ${instanceType} createInstance() {
+ //TODO:implement
+ <@unimplementedExceptionD/>
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin;
+
+import java.io.File;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.Before;
+import org.opendaylight.controller.config.yangjmxgenerator.AbstractYangTest;
+
+public abstract class AbstractGeneratorTest extends AbstractYangTest {
+ private static final File GENERATOR_OUTPUT_PATH_ROOT = new File(
+ "target/testgen");
+ protected final File generatorOutputPath;
+
+ public AbstractGeneratorTest() {
+ generatorOutputPath = new File(GENERATOR_OUTPUT_PATH_ROOT, getClass()
+ .getSimpleName());
+ }
+
+ @Before
+ public void cleanUpDirectory() throws Exception {
+ FileUtils.deleteDirectory(generatorOutputPath);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.matchers.JUnitMatchers.containsString;
+
+import org.junit.Test;
+import org.opendaylight.controller.config.yangjmxgenerator.ConfigConstants;
+import org.opendaylight.controller.config.yangjmxgenerator.PackageTranslatorTest;
+
+import com.google.common.collect.Sets;
+
+public class JMXGeneratorFileNamesValidationTest extends JMXGeneratorTest {
+
+ @Test
+ public void test() {
+ map.clear();
+ map.put(JMXGenerator.NAMESPACE_TO_PACKAGE_PREFIX + "1",
+ ConfigConstants.CONFIG_NAMESPACE + ":test:files1"
+ + JMXGenerator.NAMESPACE_TO_PACKAGE_DIVIDER
+ + PackageTranslatorTest.EXPECTED_PACKAGE_PREFIX);
+ map.put(JMXGenerator.NAMESPACE_TO_PACKAGE_PREFIX + "2",
+ ConfigConstants.CONFIG_NAMESPACE + ":test:files"
+ + JMXGenerator.NAMESPACE_TO_PACKAGE_DIVIDER
+ + PackageTranslatorTest.EXPECTED_PACKAGE_PREFIX);
+
+ map.put(JMXGenerator.MODULE_FACTORY_FILE_BOOLEAN, "randomValue");
+ jmxGenerator.setAdditionalConfig(map);
+ try {
+ jmxGenerator.generateSources(context, outputBaseDir,
+ Sets.newHashSet(testFilesModule, testFiles1Module));
+ fail();
+ } catch (RuntimeException e) {
+ final Throwable cause = e.getCause();
+ assertNotNull(cause);
+ assertTrue(cause instanceof IllegalStateException);
+ assertThat(cause.getMessage(),
+ containsString("Name conflict in generated files"));
+ assertThat(cause.getMessage(), containsString("DtoA.java"));
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin;
+
+import java.io.File;
+
+import junit.framework.Assert;
+
+import org.junit.Test;
+
+public class JMXGeneratorGeneratedFilesTrackerTest {
+
+ @Test(expected = IllegalStateException.class)
+ public void testGeneratedFilesTracker() throws Exception {
+ JMXGenerator.GeneratedFilesTracker tracker = new JMXGenerator.GeneratedFilesTracker();
+
+ tracker.addFile(new File("./a/b/c"));
+ Assert.assertEquals(1, tracker.getFiles().size());
+ tracker.addFile(new File("./a/b/c"));
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.matchers.JUnitMatchers.containsString;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.maven.plugin.logging.Log;
+import org.apache.maven.project.MavenProject;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.compiler.IProblem;
+import org.eclipse.jdt.core.dom.AST;
+import org.eclipse.jdt.core.dom.ASTParser;
+import org.eclipse.jdt.core.dom.ASTVisitor;
+import org.eclipse.jdt.core.dom.CompilationUnit;
+import org.eclipse.jdt.core.dom.FieldDeclaration;
+import org.eclipse.jdt.core.dom.MethodDeclaration;
+import org.eclipse.jdt.core.dom.NormalAnnotation;
+import org.eclipse.jdt.core.dom.PackageDeclaration;
+import org.eclipse.jdt.core.dom.TypeDeclaration;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.config.api.annotations.Description;
+import org.opendaylight.controller.config.api.annotations.RequireInterface;
+import org.opendaylight.controller.config.api.annotations.ServiceInterfaceAnnotation;
+import org.opendaylight.controller.config.spi.Module;
+import org.opendaylight.controller.config.spi.ModuleFactory;
+import org.opendaylight.controller.config.yangjmxgenerator.ConfigConstants;
+import org.opendaylight.controller.config.yangjmxgenerator.PackageTranslatorTest;
+import org.opendaylight.controller.config.yangjmxgenerator.ServiceInterfaceEntryTest;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.io.Files;
+
+public class JMXGeneratorTest extends AbstractGeneratorTest {
+
+ JMXGenerator jmxGenerator;
+
+ protected final HashMap<String, String> map = Maps.newHashMap();
+ protected File outputBaseDir;
+ File generatedResourcesDir;
+
+ private static final List<String> expectedModuleFileNames = ServiceInterfaceEntryTest
+ .toFileNames("[AbstractAsyncEventBusModule.java, AbstractAsyncEventBusModuleFactory.java, " +
+ "AbstractDynamicThreadPoolModule.java, AbstractDynamicThreadPoolModuleFactory.java, " +
+ "AbstractEventBusModule.java, AbstractEventBusModuleFactory.java, " +
+ "AbstractNamingThreadFactoryModule.java, AbstractNamingThreadFactoryModuleFactory.java, " +
+ "AsyncEventBusModule.java, AsyncEventBusModuleFactory.java, AsyncEventBusModuleMXBean.java, " +
+ "AsyncEventBusRuntimeMXBean.java, AsyncEventBusRuntimeRegistration.java, " +
+ "AsyncEventBusRuntimeRegistrator.java, DynamicThreadPoolModule.java, " +
+ "DynamicThreadPoolModuleFactory.java, DynamicThreadPoolModuleMXBean.java, " +
+ "DynamicThreadPoolRuntimeMXBean.java, DynamicThreadPoolRuntimeRegistration.java, " +
+ "DynamicThreadPoolRuntimeRegistrator.java, EventBusModule.java, EventBusModuleFactory.java, " +
+ "EventBusModuleMXBean.java, EventRuntimeMXBean.java, EventRuntimeRegistration.java, " +
+ "InnerStreamList.java, NamingThreadFactoryModule.java, NamingThreadFactoryModuleFactory.java, " +
+ "NamingThreadFactoryModuleMXBean.java, NamingThreadFactoryRuntimeMXBean.java, NamingThreadFactoryRuntimeRegistration.java, NamingThreadFactoryRuntimeRegistrator.java, Peer.java, StreamRuntimeMXBean.java, StreamRuntimeRegistration.java, ThreadRuntimeMXBean.java, ThreadRuntimeRegistration.java, ThreadStreamRuntimeMXBean.java, ThreadStreamRuntimeRegistration.java]");
+
+ private static final List<String> expectedBGPNames = ServiceInterfaceEntryTest
+ .toFileNames("[AbstractBgpListenerImplModule.java, " + "AbstractBgpListenerImplModuleFactory.java, " +
+ "BgpListenerImplModule.java, " + "BgpListenerImplModuleFactory.java, " +
+ "BgpListenerImplModuleMXBean.java, Peers.java]");
+
+ private static final List<String> expectedNetconfNames = ServiceInterfaceEntryTest
+ .toFileNames("[AbstractNetconfTestImplModule.java, " + "AbstractNetconfTestImplModuleFactory.java, " +
+ "AbstractTestImplModule.java, " + "AbstractTestImplModuleFactory.java, " +
+ "AutoCloseableServiceInterface.java, " + "ComplexDtoBInner.java, ComplexList.java, Deep.java, " +
+ "DtoA.java, DtoA1.java, DtoAInner.java, DtoAInnerInner.java, DtoB.java, DtoC.java," + "NetconfTestImplModule.java, NetconfTestImplModuleFactory.java," + "NetconfTestImplModuleMXBean.java, Peer.java, SimpleList.java, TestImplModule.java, " + "TestImplModuleFactory.java," + " TestImplModuleMXBean.java" + "]");
+ private static final List<String> expectedTestFiles = ServiceInterfaceEntryTest
+ .toFileNames("[AbstractNetconfTestFileImplModule.java, AbstractNetconfTestFileImplModuleFactory.java, " +
+ "AbstractNetconfTestFiles1ImplModule.java, AbstractNetconfTestFiles1ImplModuleFactory.java, " +
+ "AbstractTestFileImplModule.java, AbstractTestFileImplModuleFactory.java, " +
+ "AbstractTestFiles1ImplModule.java, AbstractTestFiles1ImplModuleFactory.java, DtoA.java, " +
+ "DtoA.java, NetconfTestFileImplModuleMXBean.java, NetconfTestFileImplRuntimeMXBean.java, " +
+ "NetconfTestFileImplRuntimeRegistration.java, NetconfTestFileImplRuntimeRegistrator.java, " +
+ "NetconfTestFiles1ImplModule.java, NetconfTestFiles1ImplModuleFactory.java, " +
+ "NetconfTestFiles1ImplModuleMXBean.java, NetconfTestFiles1ImplRuntimeMXBean.java, " +
+ "NetconfTestFiles1ImplRuntimeRegistration.java, NetconfTestFiles1ImplRuntimeRegistrator.java, TestFileImplModule.java, TestFileImplModuleFactory.java, TestFileImplModuleMXBean.java, TestFileImplRuntimeMXBean.java, TestFileImplRuntimeRegistration.java, TestFileImplRuntimeRegistrator.java, TestFiles1ImplModule.java, TestFiles1ImplModuleFactory.java, TestFiles1ImplModuleMXBean.java, TestFiles1ImplRuntimeMXBean.java, TestFiles1ImplRuntimeRegistration.java, TestFiles1ImplRuntimeRegistrator.java]");
+ private static final List<String> expectedAllFileNames = ServiceInterfaceEntryTest
+ .toFileNames("[AbstractAsyncEventBusModule.java, AbstractAsyncEventBusModuleFactory.java, " +
+ "AbstractBgpListenerImplModule.java, AbstractBgpListenerImplModuleFactory.java, " +
+ "AbstractDynamicThreadPoolModule.java, AbstractDynamicThreadPoolModuleFactory.java, " +
+ "AbstractEventBusModule.java, AbstractEventBusModuleFactory.java, " +
+ "AbstractNamingThreadFactoryModule.java, AbstractNamingThreadFactoryModuleFactory.java, " +
+ "AbstractNetconfTestFileImplModule.java, AbstractNetconfTestFileImplModuleFactory.java, " +
+ "AbstractNetconfTestFiles1ImplModule.java, AbstractNetconfTestFiles1ImplModuleFactory.java, " +
+ "AbstractNetconfTestImplModule.java, AbstractNetconfTestImplModuleFactory.java, " +
+ "AbstractTestFileImplModule.java, AbstractTestFileImplModuleFactory.java, " +
+ "AbstractTestFiles1ImplModule.java, AbstractTestFiles1ImplModuleFactory.java, " +
+ "AbstractTestImplModule.java, AbstractTestImplModuleFactory.java, AsyncEventBusModule.java, " +
+ "AsyncEventBusModuleFactory.java, AsyncEventBusModuleMXBean.java, " +
+ "AsyncEventBusRuntimeMXBean.java, AsyncEventBusRuntimeRegistration.java, " +
+ "AsyncEventBusRuntimeRegistrator.java, AutoCloseableServiceInterface.java, " +
+ "BgpListenerImplModule.java, BgpListenerImplModuleFactory.java, BgpListenerImplModuleMXBean.java," +
+ " BgpListenerImplRuntimeMXBean.java, BgpListenerImplRuntimeRegistration.java, " +
+ "BgpListenerImplRuntimeRegistrator.java, ComplexDtoBInner.java, ComplexList.java, Deep.java, " +
+ "DtoA.java, DtoA.java, DtoA.java, DtoA1.java, DtoAInner.java, DtoAInnerInner.java, DtoB.java, " +
+ "DtoC.java, DynamicThreadPoolModule.java, DynamicThreadPoolModuleFactory.java, " +
+ "DynamicThreadPoolModuleMXBean.java, DynamicThreadPoolRuntimeMXBean.java, " +
+ "DynamicThreadPoolRuntimeRegistration.java, DynamicThreadPoolRuntimeRegistrator.java, " +
+ "EventBusModule.java, EventBusModuleFactory.java, EventBusModuleMXBean.java, " +
+ "EventBusServiceInterface.java, EventRuntimeMXBean.java, EventRuntimeRegistration.java, " +
+ "InnerStreamList.java, NamingThreadFactoryModule.java, NamingThreadFactoryModuleFactory.java, " +
+ "NamingThreadFactoryModuleMXBean.java, NamingThreadFactoryRuntimeMXBean.java, " +
+ "NamingThreadFactoryRuntimeRegistration.java, NamingThreadFactoryRuntimeRegistrator.java, " +
+ "NetconfTestFileImplModule.java, NetconfTestFileImplModuleFactory.java, " +
+ "NetconfTestFileImplModuleMXBean.java, NetconfTestFileImplRuntimeMXBean.java, " +
+ "NetconfTestFileImplRuntimeRegistration.java, NetconfTestFileImplRuntimeRegistrator.java, " +
+ "NetconfTestFiles1ImplModule.java, NetconfTestFiles1ImplModuleFactory.java, " +
+ "NetconfTestFiles1ImplModuleMXBean.java, NetconfTestFiles1ImplRuntimeMXBean.java, " +
+ "NetconfTestFiles1ImplRuntimeRegistration.java, NetconfTestFiles1ImplRuntimeRegistrator.java, NetconfTestImplModule.java, NetconfTestImplModuleFactory.java, NetconfTestImplModuleMXBean.java, NetconfTestImplRuntimeMXBean.java, NetconfTestImplRuntimeRegistration.java, NetconfTestImplRuntimeRegistrator.java, Peer.java, Peer.java, PeersRuntimeMXBean.java, PeersRuntimeRegistration.java, ScheduledThreadPoolServiceInterface.java, SimpleList.java, StreamRuntimeMXBean.java, StreamRuntimeRegistration.java, TestFileImplModule.java, TestFileImplModuleFactory.java, TestFileImplModuleMXBean.java, TestFileImplRuntimeMXBean.java, TestFileImplRuntimeRegistration.java, TestFileImplRuntimeRegistrator.java, TestFiles1ImplModule.java, TestFiles1ImplModuleFactory.java, TestFiles1ImplModuleMXBean.java, TestFiles1ImplRuntimeMXBean.java, TestFiles1ImplRuntimeRegistration.java, TestFiles1ImplRuntimeRegistrator.java, TestImplModule.java, TestImplModuleFactory.java, TestImplModuleMXBean.java, TestImplRuntimeMXBean.java, TestImplRuntimeRegistration.java, TestImplRuntimeRegistrator.java, ThreadFactoryServiceInterface.java, ThreadPoolServiceInterface.java, ThreadRuntimeMXBean.java, ThreadRuntimeRegistration.java, ThreadStreamRuntimeMXBean.java, ThreadStreamRuntimeRegistration.java]");
+ private static final List<String> expectedGenerateMBEsListNames = ServiceInterfaceEntryTest
+ .toFileNames("[AbstractBgpListenerImplModule.java, AbstractBgpListenerImplModuleFactory.java, BgpListenerImplModule.java, BgpListenerImplModuleFactory.java, BgpListenerImplModuleMXBean.java, BgpListenerImplRuntimeMXBean.java, BgpListenerImplRuntimeRegistration.java, BgpListenerImplRuntimeRegistrator.java, PeersRuntimeMXBean.java, PeersRuntimeRegistration.java]");
+
+ @Before
+ public void setUp() {
+ map.put(JMXGenerator.NAMESPACE_TO_PACKAGE_PREFIX + "1",
+ ConfigConstants.CONFIG_NAMESPACE
+ + JMXGenerator.NAMESPACE_TO_PACKAGE_DIVIDER
+ + PackageTranslatorTest.EXPECTED_PACKAGE_PREFIX);
+ map.put(JMXGenerator.MODULE_FACTORY_FILE_BOOLEAN, "false");
+ jmxGenerator = new JMXGenerator(new FreeMarkerCodeWriterImpl());
+ jmxGenerator.setAdditionalConfig(map);
+ File targetDir = new File(generatorOutputPath, "target");
+ generatedResourcesDir = new File(targetDir, "generated-resources");
+ jmxGenerator.setResourceBaseDir(generatedResourcesDir);
+ Log mockedLog = mock(Log.class);
+ doReturn(false).when(mockedLog).isDebugEnabled();
+ doNothing().when(mockedLog).debug(any(CharSequence.class));
+ doNothing().when(mockedLog).info(any(CharSequence.class));
+ doNothing().when(mockedLog).error(any(CharSequence.class),
+ any(Throwable.class));
+ jmxGenerator.setLog(mockedLog);
+ MavenProject project = mock(MavenProject.class);
+ doReturn(generatorOutputPath).when(project).getBasedir();
+ jmxGenerator.setMavenProject(project);
+ outputBaseDir = JMXGenerator.concatFolders(targetDir,
+ "generated-sources", "config");
+ }
+
+ @Test
+ public void generateSIsMBsTest() {
+ Collection<File> files = jmxGenerator.generateSources(context,
+ outputBaseDir, context.getModules());
+ List<String> expectedFileNames = new ArrayList<>();
+ expectedFileNames
+ .addAll(ServiceInterfaceEntryTest.expectedSIEFileNames);
+ expectedFileNames.addAll(expectedModuleFileNames);
+
+ expectedFileNames.addAll(expectedBGPNames);
+ expectedFileNames.addAll(expectedNetconfNames);
+ expectedFileNames.addAll(expectedTestFiles);
+ Collections.sort(expectedFileNames);
+ // TODO: separate expectedAllFileNames into expectedBGPNames,
+ // expectedNetconfNames
+ assertEquals(expectedAllFileNames, toFileNames(files));
+
+ verifyModuleFactoryFile(false);
+ }
+
+ private void verifyModuleFactoryFile(boolean shouldBePresent) {
+ File factoryFile = new File(generatedResourcesDir, "META-INF"
+ + File.separator + "services" + File.separator
+ + ModuleFactory.class.getName());
+ if (!shouldBePresent)
+ assertFalse("Factory file should not be generated",
+ factoryFile.exists());
+ else
+ assertTrue("Factory file should be generated", factoryFile.exists());
+ }
+
+ public static List<String> toFileNames(Collection<File> files) {
+ List<String> result = new ArrayList<>();
+ for (File f : files) {
+ result.add(f.getName());
+ }
+ Collections.sort(result);
+ return result;
+ }
+
+ @Test
+ public void generateSIEsTest() throws IOException {
+ Collection<File> files = jmxGenerator.generateSources(context,
+ outputBaseDir, Sets.newHashSet(threadsModule));
+ assertEquals(ServiceInterfaceEntryTest.expectedSIEFileNames,
+ toFileNames(files));
+
+ Map<String, ASTVisitor> verifiers = Maps.newHashMap();
+
+ for (File file : files) {
+ verifiers.put(file.getName(), new SieASTVisitor());
+ }
+
+ processGeneratedCode(files, verifiers);
+
+ for (File file : files) {
+ String fileName = file.getName();
+ SieASTVisitor verifier = (SieASTVisitor) verifiers.get(fileName);
+
+ assertEquals(fileName.substring(0, fileName.length() - 5),
+ verifier.type);
+ assertThat(
+ verifier.extnds,
+ containsString("org.opendaylight.controller.config.api.annotations.AbstractServiceInterface"));
+ assertEquals(PackageTranslatorTest.EXPECTED_PACKAGE_PREFIX
+ + ".threads", verifier.packageName);
+ assertNotNull(verifier.javadoc);
+
+ if ("ThreadPoolServiceInterface.java".equals(fileName)) {
+ assertContains(verifier.descriptionAnotValue,
+ "A simple pool of threads able to execute work.");
+ assertContains(verifier.sieAnnotValue, "threadpool");
+ assertContains(verifier.sieAnnotOsgiRegistrationType,
+ PackageTranslatorTest.EXPECTED_PACKAGE_PREFIX
+ + ".threadpool.ThreadPool.class");
+ } else if ("ScheduledThreadPoolServiceInterface.java"
+ .equals(fileName)) {
+ assertContains(verifier.extnds,
+ PackageTranslatorTest.EXPECTED_PACKAGE_PREFIX
+ + ".threads.ThreadPoolServiceInterface");
+ assertContains(
+ verifier.descriptionAnotValue,
+ "An extension of the simple pool of threads able to schedule work to be executed at some point in time.");
+ assertContains(verifier.sieAnnotValue, "scheduled-threadpool");
+ assertContains(verifier.sieAnnotOsgiRegistrationType,
+ PackageTranslatorTest.EXPECTED_PACKAGE_PREFIX
+ + ".threadpool.ScheduledThreadPool.class");
+ } else if ("EventBusServiceInterface.java".equals(fileName)) {
+ assertContains(
+ verifier.descriptionAnotValue,
+ "Service representing an event bus. The service acts as message router between event producers and event consumers");
+ assertContains(verifier.sieAnnotValue, "eventbus");
+ assertContains(verifier.sieAnnotOsgiRegistrationType,
+ "com.google.common.eventbus.EventBus.class");
+ } else if ("ThreadFactoryServiceInterface.java".equals(fileName)) {
+ assertContains(
+ verifier.descriptionAnotValue,
+ "Service representing a ThreadFactory instance. It is directly useful in Java world, where various library pieces need to create threads and you may want to inject a customized thread implementation.");
+ assertContains(verifier.sieAnnotValue, "threadfactory");
+ assertContains(verifier.sieAnnotOsgiRegistrationType,
+ "java.util.concurrent.ThreadFactory.class");
+
+ } else if ("ScheduledExecutorServiceServiceInterface.java"
+ .equals(fileName)) {
+ assertContains(verifier.sieAnnotOsgiRegistrationType,
+ "java.util.concurrent.ScheduledExecutorService.class");
+ } else {
+ fail("Unknown generated sie " + fileName);
+ }
+ }
+ }
+
+ @Test
+ public void generateMBEsListTest() throws Exception {
+ // default value for module factory file is true
+ map.put(JMXGenerator.MODULE_FACTORY_FILE_BOOLEAN, "randomValue");
+ jmxGenerator.setAdditionalConfig(map);
+
+ Collection<File> files = jmxGenerator.generateSources(context,
+ outputBaseDir, Sets.newHashSet(bgpListenerJavaModule));
+
+ assertEquals(expectedGenerateMBEsListNames, toFileNames(files));
+ }
+
+ @Test
+ public void generateMBEsTest() throws Exception {
+ // default value for module factory file is true
+ map.put(JMXGenerator.MODULE_FACTORY_FILE_BOOLEAN, "randomValue");
+ jmxGenerator.setAdditionalConfig(map);
+
+ Collection<File> files = jmxGenerator.generateSources(context,
+ outputBaseDir, Sets.newHashSet(threadsJavaModule));
+
+ assertEquals(expectedModuleFileNames, toFileNames(files));
+
+ Map<String, ASTVisitor> verifiers = Maps.newHashMap();
+
+ Collection<File> xmlFiles = Collections2.filter(files,
+ new Predicate<File>() {
+
+ @Override
+ public boolean apply(File input) {
+ return input.getName().endsWith("xml");
+ }
+ });
+
+ Collection<File> javaFiles = Collections2.filter(files,
+ new Predicate<File>() {
+
+ @Override
+ public boolean apply(File input) {
+ return input.getName().endsWith("java");
+ }
+ });
+
+ MbeASTVisitor abstractDynamicThreadPoolModuleVisitor = null;
+ MbeASTVisitor asyncEventBusModuleMXBeanVisitor = null;
+ MbeASTVisitor abstractNamingThreadFactoryModuleFactoryVisitor = null;
+ MbeASTVisitor asyncEventBusModuleVisitor = null;
+ MbeASTVisitor eventBusModuleFactoryVisitor = null;
+
+ for (File file : javaFiles) {
+ String name = file.getName();
+ MbeASTVisitor visitor = new MbeASTVisitor();
+ verifiers.put(name, visitor);
+ if (name.equals("AbstractDynamicThreadPoolModule.java"))
+ abstractDynamicThreadPoolModuleVisitor = visitor;
+ if (name.equals("AsyncEventBusModuleMXBean.java"))
+ asyncEventBusModuleMXBeanVisitor = visitor;
+ if (name.equals("AbstractNamingThreadFactoryModuleFactory.java"))
+ abstractNamingThreadFactoryModuleFactoryVisitor = visitor;
+ if (name.equals("AsyncEventBusModule.java"))
+ asyncEventBusModuleVisitor = visitor;
+ if (name.equals("EventBusModuleFactory.java"))
+ eventBusModuleFactoryVisitor = visitor;
+ }
+
+ processGeneratedCode(javaFiles, verifiers);
+
+ assertAbstractDynamicThreadPoolModule(abstractDynamicThreadPoolModuleVisitor);
+ assertAsyncEventBusModuleMXBean(asyncEventBusModuleMXBeanVisitor);
+ assertAbstractNamingThreadFactoryModuleFactory(abstractNamingThreadFactoryModuleFactoryVisitor);
+ assertAsyncEventBusModule(asyncEventBusModuleVisitor);
+ assertEventBusModuleFactory(eventBusModuleFactoryVisitor);
+
+ verifyXmlFiles(xmlFiles);
+ // verify ModuleFactory file
+ File moduleFactoryFile = JMXGenerator.concatFolders(
+ generatedResourcesDir, "META-INF", "services",
+ ModuleFactory.class.getName());
+ assertThat(moduleFactoryFile.exists(), is(true));
+ Set<String> lines = Sets.newHashSet(FileUtils
+ .readLines(moduleFactoryFile));
+ Set<String> expectedLines = Sets.newHashSet(//
+ PackageTranslatorTest.EXPECTED_PACKAGE_PREFIX
+ + ".threads.java.EventBusModuleFactory",//
+ PackageTranslatorTest.EXPECTED_PACKAGE_PREFIX
+ + ".threads.java.AsyncEventBusModuleFactory", //
+ PackageTranslatorTest.EXPECTED_PACKAGE_PREFIX
+ + ".threads.java.DynamicThreadPoolModuleFactory",//
+ PackageTranslatorTest.EXPECTED_PACKAGE_PREFIX
+ + ".threads.java.NamingThreadFactoryModuleFactory");
+ assertThat(lines, equalTo(expectedLines));
+
+ }
+
+ private void verifyXmlFiles(Collection<File> xmlFiles) throws Exception {
+ ErrorHandler errorHandler = new ErrorHandler() {
+
+ @Override
+ public void warning(SAXParseException exception)
+ throws SAXException {
+ fail("Generated blueprint xml is not well formed "
+ + exception.getMessage());
+ }
+
+ @Override
+ public void fatalError(SAXParseException exception)
+ throws SAXException {
+ fail("Generated blueprint xml is not well formed "
+ + exception.getMessage());
+ }
+
+ @Override
+ public void error(SAXParseException exception) throws SAXException {
+ fail("Generated blueprint xml is not well formed "
+ + exception.getMessage());
+ }
+ };
+
+ for (File file : xmlFiles) {
+ DocumentBuilderFactory factory = DocumentBuilderFactory
+ .newInstance();
+ factory.setValidating(false);
+ factory.setNamespaceAware(true);
+
+ DocumentBuilder builder = factory.newDocumentBuilder();
+
+ builder.setErrorHandler(errorHandler);
+ builder.parse(new InputSource(file.getPath()));
+ }
+
+ }
+
+ private void assertEventBusModuleFactory(MbeASTVisitor visitor) {
+ assertEquals(PackageTranslatorTest.EXPECTED_PACKAGE_PREFIX
+ + ".threads.java", visitor.packageName);
+ assertEquals("EventBusModuleFactory", visitor.type);
+ assertContains(visitor.extnds,
+ PackageTranslatorTest.EXPECTED_PACKAGE_PREFIX
+ + ".threads.java.AbstractEventBusModuleFactory");
+
+ assertEquals(0, visitor.fieldDeclarations.size());
+
+ assertEquals("Incorrenct number of generated methods", 0,
+ visitor.methods.size());
+ assertEquals("Incorrenct number of generated constructors", 0,
+ visitor.constructors.size());
+ assertEquals("Incorrenct number of generated method descriptions", 0,
+ visitor.methodDescriptions.size());
+ assertEquals("Incorrenct number of generated method javadoc", 0,
+ visitor.methodJavadoc.size());
+ }
+
+ private void assertAsyncEventBusModule(MbeASTVisitor visitor) {
+ assertEquals(PackageTranslatorTest.EXPECTED_PACKAGE_PREFIX
+ + ".threads.java", visitor.packageName);
+ assertEquals("AsyncEventBusModule", visitor.type);
+ assertContains(visitor.extnds,
+ PackageTranslatorTest.EXPECTED_PACKAGE_PREFIX
+ + ".threads.java.AbstractAsyncEventBusModule");
+
+ assertEquals(0, visitor.fieldDeclarations.size());
+
+ assertEquals("Incorrenct number of generated methods", 2,
+ visitor.methods.size());
+ assertEquals("Incorrenct number of generated constructors", 2,
+ visitor.constructors.size());
+ assertEquals("Incorrenct number of generated method descriptions", 0,
+ visitor.methodDescriptions.size());
+ assertEquals("Incorrenct number of generated method javadoc", 0,
+ visitor.methodJavadoc.size());
+ }
+
+ private void assertAbstractNamingThreadFactoryModuleFactory(
+ MbeASTVisitor visitor) {
+ assertEquals(PackageTranslatorTest.EXPECTED_PACKAGE_PREFIX
+ + ".threads.java", visitor.packageName);
+ assertEquals("AbstractNamingThreadFactoryModuleFactory", visitor.type);
+ assertContains(visitor.implmts,
+ "org.opendaylight.controller.config.spi.ModuleFactory");
+ Set<String> fieldDeclarations = visitor.fieldDeclarations;
+ assertDeclaredField(fieldDeclarations,
+ "public static final java.lang.String NAME=\"threadfactory-naming\"");
+ assertDeclaredField(
+ fieldDeclarations,
+ "private static final java.util.Set<Class<? extends org.opendaylight.controller.config.api.annotations.AbstractServiceInterface>> serviceIfcs=new java.util.HashSet<Class<? extends org.opendaylight.controller.config.api.annotations.AbstractServiceInterface>>()");
+
+ assertEquals(2, fieldDeclarations.size());
+
+ assertEquals("Incorrenct number of generated methods", 5,
+ visitor.methods.size());
+ assertEquals("Incorrenct number of generated method descriptions", 0,
+ visitor.methodDescriptions.size());
+ assertEquals("Incorrenct number of generated method javadoc", 0,
+ visitor.methodJavadoc.size());
+
+ }
+
+ private void assertAsyncEventBusModuleMXBean(MbeASTVisitor visitor) {
+ assertEquals(PackageTranslatorTest.EXPECTED_PACKAGE_PREFIX
+ + ".threads.java", visitor.packageName);
+ assertEquals("AsyncEventBusModuleMXBean", visitor.type);
+
+ assertEquals("Incorrenct number of generated methods", 2,
+ visitor.methods.size());
+ }
+
+ private void assertAbstractDynamicThreadPoolModule(MbeASTVisitor visitor) {
+ assertEquals(PackageTranslatorTest.EXPECTED_PACKAGE_PREFIX
+ + ".threads.java", visitor.packageName);
+ assertNotNull(visitor.javadoc);
+ assertContains(visitor.descriptionAnotValue,
+ "threadpool-dynamic description");
+ assertEquals("AbstractDynamicThreadPoolModule", visitor.type);
+ assertContains(visitor.implmts,
+ PackageTranslatorTest.EXPECTED_PACKAGE_PREFIX
+ + ".threads.java.DynamicThreadPoolModuleMXBean",
+ Module.class.getCanonicalName(),
+ PackageTranslatorTest.EXPECTED_PACKAGE_PREFIX
+ + ".threads.ScheduledThreadPoolServiceInterface",
+ PackageTranslatorTest.EXPECTED_PACKAGE_PREFIX
+ + ".threads.ThreadPoolServiceInterface");
+ assertEquals(2, visitor.constructors.size());
+ Set<String> fieldDeclarations = visitor.fieldDeclarations;
+ assertDeclaredField(fieldDeclarations,
+ "private java.lang.Long maximumSize");
+ assertDeclaredField(fieldDeclarations,
+ "private javax.management.ObjectName threadfactory");
+ assertDeclaredField(fieldDeclarations,
+ "private java.util.concurrent.ThreadFactory threadfactoryDependency");
+ assertDeclaredField(fieldDeclarations,
+ "private java.lang.Long keepAlive=10");
+ assertDeclaredField(fieldDeclarations,
+ "private java.lang.Long coreSize");
+ assertDeclaredField(fieldDeclarations, "private byte[] binary");
+ assertEquals(22, fieldDeclarations.size());
+
+ assertEquals(1, visitor.requireIfc.size());
+ String reqIfc = visitor.requireIfc.get("setThreadfactory");
+ assertNotNull("Missing generated setter for threadfactory", reqIfc);
+ assertContains(reqIfc, PackageTranslatorTest.EXPECTED_PACKAGE_PREFIX
+ + ".threads.ThreadFactoryServiceInterface");
+
+ assertEquals("Incorrenct number of generated methods", 24,
+ visitor.methods.size());
+ assertEquals("Incorrenct number of generated method descriptions", 3,
+ visitor.methodDescriptions.size());
+ assertEquals("Incorrenct number of generated method javadoc", 3,
+ visitor.methodJavadoc.size());
+ assertNotNull("Missing javadoc for setMaximumSize method",
+ visitor.methodJavadoc.get("setMaximumSize"));
+ }
+
+ private void assertDeclaredField(Set<String> fieldDeclarations,
+ String declaration) {
+ assertTrue("Missing field " + declaration + ", got: "
+ + fieldDeclarations,
+ fieldDeclarations.contains(declaration + ";\n"));
+ }
+
+ private static class SieASTVisitor extends ASTVisitor {
+ protected String packageName, descriptionAnotValue, sieAnnotValue,
+ sieAnnotOsgiRegistrationType, type, extnds, javadoc;
+ protected Map<String, String> methodDescriptions = Maps.newHashMap();
+
+ @Override
+ public boolean visit(PackageDeclaration node) {
+ packageName = node.getName().toString();
+ return super.visit(node);
+ }
+
+ @Override
+ public boolean visit(NormalAnnotation node) {
+ if (node.getTypeName().toString()
+ .equals(Description.class.getCanonicalName())) {
+ if (node.getParent() instanceof TypeDeclaration) {
+ descriptionAnotValue = node.values().get(0).toString();
+ } else if (node.getParent() instanceof MethodDeclaration) {
+ String descr = node.values().get(0).toString();
+ methodDescriptions.put(((MethodDeclaration) node
+ .getParent()).getName().toString(), descr);
+ }
+ } else if (node
+ .getTypeName()
+ .toString()
+ .equals(ServiceInterfaceAnnotation.class.getCanonicalName())) {
+ String text1 = node.values().get(0).toString();
+ String text2 = node.values().get(1).toString();
+ if (text1.contains("value")) {
+ sieAnnotValue = text1;
+ sieAnnotOsgiRegistrationType = text2;
+ } else {
+ sieAnnotValue = text2;
+ sieAnnotOsgiRegistrationType = text1;
+ }
+ }
+ return super.visit(node);
+ }
+
+ @Override
+ public boolean visit(TypeDeclaration node) {
+ javadoc = node.getJavadoc() == null ? null : node.getJavadoc()
+ .toString();
+ type = node.getName().toString();
+ List<?> superIfcs = node.superInterfaceTypes();
+ extnds = superIfcs != null && !superIfcs.isEmpty() ? superIfcs
+ .toString() : null;
+ return super.visit(node);
+ }
+ }
+
+ private static class MbeASTVisitor extends SieASTVisitor {
+ private String implmts;
+ private final Set<String> fieldDeclarations = Sets.newHashSet();
+ private final Set<String> constructors = Sets.newHashSet();
+ private final Map<String, String> methods = Maps.newHashMap();
+ private final Map<String, String> requireIfc = Maps.newHashMap();
+ private final Map<String, String> methodJavadoc = Maps.newHashMap();
+
+ @Override
+ public boolean visit(NormalAnnotation node) {
+ boolean result = super.visit(node);
+ if (node.getTypeName().toString()
+ .equals(RequireInterface.class.getCanonicalName())
+ && node.getParent() instanceof MethodDeclaration) {
+ // remember only top level description annotation
+ String reqVal = node.values().get(0).toString();
+ requireIfc.put(((MethodDeclaration) node.getParent()).getName()
+ .toString(), reqVal);
+ }
+ return result;
+ }
+
+ @Override
+ public boolean visit(FieldDeclaration node) {
+ fieldDeclarations.add(node.toString());
+ return super.visit(node);
+ }
+
+ @Override
+ public boolean visit(MethodDeclaration node) {
+ if (node.isConstructor())
+ constructors.add(node.toString());
+ else {
+ String methodName = node.getName().toString();
+ if (node.getJavadoc() != null)
+ methodJavadoc.put(methodName, node.getJavadoc().toString());
+ methods.put(methodName, node.toString());
+ }
+ return super.visit(node);
+ }
+
+ @Override
+ public boolean visit(TypeDeclaration node) {
+ boolean visit = super.visit(node);
+ List<?> superIfcs = node.superInterfaceTypes();
+ implmts = superIfcs != null && !superIfcs.isEmpty() ? superIfcs
+ .toString() : null;
+ extnds = node.getSuperclassType() == null ? null : node
+ .getSuperclassType().toString();
+ return visit;
+ }
+
+ }
+
+ private void assertContains(String source, String... contained) {
+ for (String string : contained) {
+ assertThat(source, containsString(string));
+ }
+ }
+
+ private void processGeneratedCode(Collection<File> files,
+ Map<String, ASTVisitor> verifiers) throws IOException {
+ ASTParser parser = ASTParser.newParser(AST.JLS3);
+ Map<?, ?> options = JavaCore.getOptions();
+ JavaCore.setComplianceOptions(JavaCore.VERSION_1_7, options);
+ parser.setCompilerOptions(options);
+
+ parser.setKind(ASTParser.K_COMPILATION_UNIT);
+
+ for (File file : files) {
+ char[] source = readFileAsChars(file);
+ parser.setSource(source);
+ parser.setResolveBindings(true);
+
+ CompilationUnit cu = (CompilationUnit) parser.createAST(null);
+ // Check for compilation problems in generated file
+ for (IProblem c : cu.getProblems()) {
+ // 1610613332 = Syntax error, annotations are only available if
+ // source level is 5.0
+ if (c.getID() == 1610613332)
+ continue;
+ // 1610613332 = Syntax error, parameterized types are only
+ // available if source level is 5.0
+ if (c.getID() == 1610613329)
+ continue;
+ fail("Error in generated source code " + file + ":"
+ + c.getSourceLineNumber() + " " + c.toString());
+ }
+
+ ASTVisitor visitor = verifiers.get(file.getName());
+ if (visitor == null)
+ fail("Unknown generated file " + file.getName());
+ cu.accept(visitor);
+
+ }
+ }
+
+ public static char[] readFileAsChars(File file) throws IOException {
+ List<String> readLines = Files
+ .readLines(file, Charset.forName("utf-8"));
+ char[] retVal = new char[0];
+ for (String string : readLines) {
+ char[] line = string.toCharArray();
+ int beforeLength = retVal.length;
+ retVal = Arrays.copyOf(retVal, retVal.length + line.length + 1);
+ System.arraycopy(line, 0, retVal, beforeLength, line.length);
+ retVal[retVal.length - 1] = '\n';
+ }
+ return retVal;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Test;
+import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntry;
+import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntryTest;
+import org.opendaylight.controller.config.yangjmxgenerator.RuntimeBeanEntry;
+import org.opendaylight.controller.config.yangjmxgenerator.attribute.AttributeIfc;
+import org.opendaylight.controller.config.yangjmxgenerator.attribute.JavaAttribute;
+import org.opendaylight.controller.config.yangjmxgenerator.attribute.ListAttribute;
+import org.opendaylight.controller.config.yangjmxgenerator.attribute.TOAttribute;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.FtlTemplate;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.GeneralClassTemplate;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.GeneralInterfaceTemplate;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.TemplateFactory;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Method;
+
+public class ModuleMXBeanEntryPluginTest extends ModuleMXBeanEntryTest {
+
+ @Test
+ public void testThreadsJavaPlugin() {
+ Map<String /* identity local name */, ModuleMXBeanEntry> namesToMBEs = loadThreadsJava();
+ {// check threadfactory-naming
+ ModuleMXBeanEntry threadFactoryNaming = namesToMBEs
+ .get(THREADFACTORY_NAMING_MXB_NAME);
+ Collection<RuntimeBeanEntry> runtimeBeans = threadFactoryNaming
+ .getRuntimeBeans();
+ assertThat(runtimeBeans.size(), is(4));
+ // first one should be root
+ {
+ RuntimeBeanEntry rootRB = findFirstByYangName(runtimeBeans,
+ THREADFACTORY_NAMING_MXB_NAME);
+ assertThat(rootRB.isRoot(), is(true));
+ assertThat(rootRB.getAttributes().size(), is(1));
+ JavaAttribute attribute = (JavaAttribute) rootRB
+ .getAttributes().iterator().next();
+ assertThat(attribute.getAttributeYangName(),
+ is("created-sessions"));
+ assertThat(rootRB.getYangName(),
+ is(THREADFACTORY_NAMING_MXB_NAME));
+ Map<String, FtlTemplate> ftlMap = TemplateFactory
+ .getTOAndMXInterfaceFtlFiles(rootRB);
+ assertThat(ftlMap.size(), is(1));
+ GeneralInterfaceTemplate rootGeneratorInterface = (GeneralInterfaceTemplate) ftlMap
+ .get("NamingThreadFactoryRuntimeMXBean.java");
+ assertNotNull(rootGeneratorInterface);
+ assertThat(rootGeneratorInterface.getPackageName(),
+ is(PACKAGE_NAME));
+ assertThat(rootGeneratorInterface.getFullyQualifiedName(),
+ is(PACKAGE_NAME + ".NamingThreadFactoryRuntimeMXBean"));
+ assertThat(
+ rootGeneratorInterface.getTypeDeclaration()
+ .getExtended(),
+ is(Arrays
+ .asList("org.opendaylight.controller.config.api.runtime.RuntimeBean")));
+
+ assertThat(rootGeneratorInterface.getMethods().size(), is(1));
+ Method getCreatedSessions = findFirstMethodByName(
+ rootGeneratorInterface.getMethods(),
+ "getCreatedSessions");
+ assertThat(getCreatedSessions.getName(),
+ is("getCreatedSessions"));
+ assertThat(getCreatedSessions.getParameters().isEmpty(),
+ is(true));
+ assertThat(getCreatedSessions.getReturnType(),
+ is(Long.class.getName()));
+ }
+ }
+ {
+ ModuleMXBeanEntry threadFactoryNaming = namesToMBEs
+ .get(THREADFACTORY_NAMING_MXB_NAME);
+ Collection<RuntimeBeanEntry> runtimeBeans = threadFactoryNaming
+ .getRuntimeBeans();
+ assertThat(runtimeBeans.size(), is(4));
+
+ {
+ RuntimeBeanEntry streamRB = findFirstByYangName(runtimeBeans,
+ "stream");
+ assertNotNull(streamRB);
+ assertFalse(streamRB.getKeyYangName().isPresent());
+ assertFalse(streamRB.getKeyJavaName().isPresent());
+ Map<String, AttributeIfc> attributeMap = streamRB
+ .getYangPropertiesToTypesMap();
+ assertEquals(4, attributeMap.size());
+ TOAttribute toAttr = (TOAttribute) attributeMap.get("peer");
+ assertNotNull(toAttr);
+ JavaAttribute timestampAttr = (JavaAttribute) attributeMap
+ .get("timestamp");
+ assertNotNull(timestampAttr);
+ JavaAttribute stateAttr = (JavaAttribute) attributeMap
+ .get("state");
+ assertNotNull(stateAttr);
+ ListAttribute innerStreamList = (ListAttribute) attributeMap
+ .get("inner-stream-list");
+ assertNotNull(innerStreamList);
+
+ Map<String, FtlTemplate> ftlMap = TemplateFactory
+ .getTOAndMXInterfaceFtlFiles(streamRB);
+ assertThat(ftlMap.size(), is(3));
+ GeneralInterfaceTemplate streamGeneralInterface = (GeneralInterfaceTemplate) ftlMap
+ .get("ThreadStreamRuntimeMXBean.java");
+ assertThat(streamGeneralInterface.getMethods().size(), is(4));
+ Method getPeer = findFirstMethodByName(
+ streamGeneralInterface.getMethods(), "getPeer");
+ assertNotNull(getPeer);
+ assertThat(getPeer.getReturnType(), is(PACKAGE_NAME + ".Peer"));
+
+ // test TO
+ GeneralClassTemplate peerTO = (GeneralClassTemplate) ftlMap
+ .get("pack2.Peer");
+ assertThat(peerTO.getPackageName(), is(PACKAGE_NAME));
+ assertThat(peerTO.getTypeDeclaration().getExtended().isEmpty(),
+ is(true));
+ assertThat(peerTO.getFullyQualifiedName(), is(PACKAGE_NAME
+ + ".Peer"));
+ assertThat(peerTO.getMethods().size(), is(4));
+ Method getPort = findFirstMethodByName(peerTO.getMethods(),
+ "getPort");
+ assertNotNull(getPort);
+ Method setPort = findFirstMethodByName(peerTO.getMethods(),
+ "setPort");
+ assertNotNull(setPort);
+ Method getCoreSize = findFirstMethodByName(peerTO.getMethods(),
+ "getCoreSize");
+ Method setCoreSize = findFirstMethodByName(peerTO.getMethods(),
+ "setCoreSize");
+ assertNotNull(setCoreSize);
+ assertNotNull(getCoreSize);
+
+ }
+ }
+ }
+
+ private Method findFirstMethodByName(List<? extends Method> methods,
+ String name) {
+ for (Method ms : methods) {
+ if (name.equals(ms.getName())) {
+ return ms;
+ }
+ }
+ throw new IllegalArgumentException("Method with given name not found");
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin;
+
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.junit.Test;
+import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntry;
+import org.opendaylight.controller.config.yangjmxgenerator.attribute.AttributeIfc;
+import org.opendaylight.controller.config.yangjmxgenerator.attribute.JavaAttribute;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.AbstractFactoryTemplate;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.TemplateFactory;
+import org.opendaylight.yangtools.sal.binding.model.api.Type;
+
+import com.google.common.collect.Maps;
+
+public class ModuleMXBeanEntryTemplatesTest {
+
+ @Test
+ public void test() {
+ ModuleMXBeanEntry mbe = mockMbe("package");
+ AbstractFactoryTemplate template = TemplateFactory
+ .abstractFactoryTemplateFromMbe(mbe);
+ assertNotNull(template);
+ }
+
+ private ModuleMXBeanEntry mockMbe(String packageName) {
+ ModuleMXBeanEntry mbe = mock(ModuleMXBeanEntry.class);
+ Map<String, AttributeIfc> a = Maps.newHashMap();
+ JavaAttribute attr = mockJavaAttr();
+
+ a.put("attr1", attr);
+ doReturn(a).when(mbe).getAttributes();
+ doReturn(packageName).when(mbe).getPackageName();
+ doReturn(Collections.emptyMap()).when(mbe).getProvidedServices();
+ doReturn("yang-module").when(mbe).getYangModuleName();
+ doReturn("local").when(mbe).getYangModuleLocalname();
+ doReturn("AbstractType").when(mbe).getAbstractFactoryName();
+ doReturn("Module").when(mbe).getStubModuleName();
+ doReturn("fullA").when(mbe).getFullyQualifiedName(anyString());
+ doReturn("uniq").when(mbe).getGloballyUniqueName();
+ return mbe;
+ }
+
+ private JavaAttribute mockJavaAttr() {
+ JavaAttribute attr = mock(JavaAttribute.class);
+ Type typeA = mock(Type.class);
+ doReturn("package").when(typeA).getName();
+ doReturn("type").when(typeA).getPackageName();
+ doReturn("package.type").when(typeA).getFullyQualifiedName();
+ doReturn(typeA).when(attr).getType();
+ doReturn("Type").when(attr).getUpperCaseCammelCase();
+ return attr;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.internal.matchers.StringContains.containsString;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+
+import org.junit.Test;
+import org.opendaylight.controller.config.yangjmxgenerator.RuntimeBeanEntry;
+import org.opendaylight.controller.config.yangjmxgenerator.RuntimeRegistratorTest;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.FtlFilePersister;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.FtlTemplate;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.RuntimeRegistratorFtlTemplate;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.util.FormattingUtil;
+
+public class RuntimeRegistratorFtlFileTest extends RuntimeRegistratorTest {
+ private final FtlFilePersister ftlFilePersister = new FtlFilePersister();
+
+ @Test
+ public void testRootWithoutAnything() {
+ RuntimeBeanEntry rootRB = prepareRootRB(Collections
+ .<RuntimeBeanEntry> emptyList());
+ Map<String, FtlTemplate> createdFtls = RuntimeRegistratorFtlTemplate
+ .create(rootRB);
+ assertThat(createdFtls.size(), is(2));
+ String rootRegistratorName = RuntimeRegistratorFtlTemplate
+ .getJavaNameOfRuntimeRegistration(rootRB.getJavaNamePrefix());
+ FtlTemplate rootFtlFile = createdFtls.get(rootRegistratorName);
+ assertNotNull(rootFtlFile);
+
+ Map<FtlTemplate, String> serializedFtls = ftlFilePersister
+ .serializeFtls(createdFtls.values());
+ assertThat(serializedFtls.size(), is(2));
+ }
+
+ @Test
+ public void testHierarchy2() {
+ RuntimeBeanEntry grandChildRB = prepareChildRB(
+ Collections.<RuntimeBeanEntry> emptyList(), "grand");
+ RuntimeBeanEntry childRB = prepareChildRB(Arrays.asList(grandChildRB),
+ "");
+ RuntimeBeanEntry rootRB = prepareRootRB(Arrays.asList(childRB));
+
+ Map<String, FtlTemplate> createdFtls = RuntimeRegistratorFtlTemplate
+ .create(rootRB);
+ Map<FtlTemplate, String> serializedFtls = ftlFilePersister
+ .serializeFtls(createdFtls.values());
+ assertThat(serializedFtls.size(), is(4));
+
+ assertThat(
+ findRegistrationOutput(createdFtls, grandChildRB,
+ serializedFtls), not(containsString(" register(")));
+
+ FtlTemplate registrator = createdFtls.get(RuntimeRegistratorFtlTemplate
+ .getJavaNameOfRuntimeRegistrator(rootRB));
+ FormattingUtil.cleanUpEmptyLinesAndIndent(serializedFtls
+ .get(registrator));
+
+ }
+
+ private String findRegistrationOutput(Map<String, FtlTemplate> createdFtls,
+ RuntimeBeanEntry rb, Map<FtlTemplate, String> serializedFtls) {
+ RuntimeRegistratorFtlTemplate rbFtlFile = (RuntimeRegistratorFtlTemplate) createdFtls
+ .get(RuntimeRegistratorFtlTemplate.getJavaNameOfRuntimeRegistration(rb.getJavaNamePrefix()));
+ assertNotNull(rbFtlFile);
+ String unformatted = serializedFtls.get(rbFtlFile);
+ assertNotNull(unformatted);
+ return FormattingUtil.cleanUpEmptyLinesAndIndent(unformatted);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.ftl;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Field;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.MethodDeclaration;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.util.FormattingUtil;
+
+import com.google.common.collect.Lists;
+
+public class FtlFilePersisterTest {
+ private final FtlFilePersister tested = new FtlFilePersister();
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testGeneralInterface() {
+ String packageName = "pa.cka.ge";
+ String name = "GeneralClassImpl";
+ List<String> extendedInterfaces = Arrays.asList("List", "Set");
+ List<MethodDeclaration> methods = new ArrayList<>();
+ methods.add(new MethodDeclaration("String", "executeOperation",
+ Collections.<Field> emptyList()));
+
+ List<String> mods = Lists.newArrayList();
+ List<String> mods2 = Lists.newArrayList("final");
+ methods.add(new MethodDeclaration("String", "executeOperation", Arrays
+ .asList(new Field(mods, "int", "param1"), new Field(mods2, "long", "param2"))));
+
+ GeneralInterfaceTemplate generalInterface = new GeneralInterfaceTemplate(
+ null, packageName, name, extendedInterfaces, methods);
+
+ Map<FtlTemplate, String> abstractFtlFileStringMap = tested
+ .serializeFtls(Arrays.asList(generalInterface));
+ String content = FormattingUtil
+ .cleanUpEmptyLinesAndIndent(abstractFtlFileStringMap.get(generalInterface));
+
+ // skip header
+ content = content.substring(content.indexOf("package"));
+
+ String expected = "package pa.cka.ge;\n"
+ + "/**\n"
+ + "*\n"
+ + "*/\n"
+ + "public interface GeneralClassImpl extends List, Set\n{\n"
+ + "public String executeOperation();\n"
+ + "public String executeOperation(int param1, final long param2);\n"
+ + "}\n";
+
+ assertEquals(expected, content);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.util;
+
+import java.util.Scanner;
+
+public class FormattingUtil {
+
+ public static String cleanUpEmptyLinesAndIndent(String input) {
+ StringBuffer output = new StringBuffer();
+ Scanner scanner = new Scanner(input);
+ while (scanner.hasNextLine()) {
+ String line = scanner.nextLine();
+ line = line.replaceAll("\t", " ");
+ while (line.contains(" ")) {
+ line = line.replaceAll(" ", " ");
+ }
+ line = line.trim();
+ if (line.length() > 0) {
+ output.append(line);
+ output.append("\n");
+ }
+ }
+
+ return output.toString();
+ }
+}
--- /dev/null
+<?xml version="1.0"?>
+<project
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+ xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <artifactId>config-subsystem</artifactId>
+ <groupId>org.opendaylight</groupId>
+ <version>0.2.1-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>yang-jmx-generator</artifactId>
+ <name>${project.artifactId}</name>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>binding-generator-util</artifactId>
+ <version>${opendaylight.binding.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>binding-generator-spi</artifactId>
+ <version>${opendaylight.binding.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.opendaylight.bgpcep</groupId>
+ <artifactId>mockito-configuration</artifactId>
+ <version>0.2.0-SNAPSHOT</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-parser-impl</artifactId>
+ <version>${opendaylight.yang.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>config-api</artifactId>
+ <version>0.2.1-SNAPSHOT</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>binding-generator-impl</artifactId>
+ <version>${opendaylight.binding.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Private-Package>
+ org.opendaylight.controller.config.yangjmxgenerator.plugin.util,
+ </Private-Package>
+ <Import-Package>
+ org.slf4j,
+ com.google.common.base,
+ com.google.common.collect,
+ javax.management.*,
+ </Import-Package>
+ <Export-Package>
+ org.opendaylight.controller.config.yangjmxgenerator,
+ org.opendaylight.controller.config.yangjmxgenerator.attribute,
+ <!-- OPENDAYLIGHT NEEDED BY YANG-STORE -->
+ org.opendaylight.yangtools.binding.generator.util,
+ org.opendaylight.yangtools.sal.binding.generator.spi,
+ org.opendaylight.yangtools.sal.binding.model.api,
+ org.opendaylight.yangtools.yang.binding,
+ org.opendaylight.yangtools.yang.common,
+ org.opendaylight.yangtools.yang.model.api,
+ org.opendaylight.yangtools.yang.model.api.type,
+
+ org.opendaylight.yangtools.binding.generator.util.generated.type.builder,
+ org.opendaylight.yangtools.sal.binding.model.api.type.builder,
+ org.opendaylight.yangtools.yang.binding,
+ </Export-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ <!-- test jar -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+</project>
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator;
+
+/**
+ * Base for entries that provides information about their yang source.
+ */
+public class AbstractEntry {
+
+ private String yangModuleName;
+ private String yangModuleLocalname;
+
+ public AbstractEntry() {
+ super();
+ }
+
+ protected void setYangModuleName(String name) {
+ this.yangModuleName = name;
+ }
+
+ public String getYangModuleLocalname() {
+ return yangModuleLocalname;
+ }
+
+ protected void setYangModuleLocalname(String yangModuleLocalname) {
+ this.yangModuleLocalname = yangModuleLocalname;
+ }
+
+ public String getYangModuleName() {
+ return yangModuleName;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator;
+
+import java.net.URI;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.opendaylight.yangtools.yang.common.QName;
+
+public class ConfigConstants {
+
+ public static final String CONFIG_NAMESPACE = "urn:opendaylight:params:xml:ns:yang:controller:config";
+
+ public static final String CONFIG_MODULE = "config";
+ public static final String CONFIG_THREADS_MODULE = "config-threads";
+ public static final String IETF_INET_TYPES = "ietf-inet-types";
+
+ public static final QName SERVICE_TYPE_Q_NAME = createConfigQName("service-type");
+ public static final QName MODULE_TYPE_Q_NAME = createConfigQName("module-type");
+ public static final QName JAVA_CLASS_EXTENSION_QNAME = createConfigQName("java-class");
+ public static final QName REQUIRED_IDENTITY_EXTENSION_QNAME = createConfigQName("required-identity");
+ public static final QName INNER_STATE_BEAN_EXTENSION_QNAME = createConfigQName("inner-state-bean");
+ public static final QName PROVIDED_SERVICE_EXTENSION_QNAME = createConfigQName("provided-service");
+ public static final QName JAVA_NAME_PREFIX_EXTENSION_QNAME = createConfigQName("java-name-prefix");
+ public static final QName RPC_CONTEXT_REF_GROUPING_QNAME = createRpcXQName("rpc-context-ref");
+ public static final QName RPC_CONTEXT_REF_GROUPING_LEAF = createRpcXQName("context-instance");
+ public static final QName RPC_CONTEXT_INSTANCE_EXTENSION_QNAME = createRpcXQName("rpc-context-instance");
+
+ public static QName createConfigQName(String localName) {
+ return createQName(CONFIG_NAMESPACE, "2013-04-05", localName);
+ }
+
+ public static QName createRpcXQName(String localName) {
+ return createQName("urn:ietf:params:xml:ns:yang:rpc-context",
+ "2013-06-17", localName);
+ }
+
+ /**
+ *
+ * @param uri
+ * @param revisionDate
+ * in format yyyy-MM-dd
+ * @param localName
+ * @return
+ */
+ private static QName createQName(String uri, String revisionDate,
+ String localName) {
+ SimpleDateFormat revisionFormat = new SimpleDateFormat("yyyy-MM-dd");
+ Date revision;
+ try {
+ revision = revisionFormat.parse(revisionDate);
+ } catch (ParseException e) {
+ throw new RuntimeException(e);
+ }
+ return new QName(URI.create(uri), revision, localName);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static java.lang.String.format;
+import static org.opendaylight.controller.config.yangjmxgenerator.ConfigConstants.createConfigQName;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.opendaylight.controller.config.yangjmxgenerator.attribute.AttributeIfc;
+import org.opendaylight.controller.config.yangjmxgenerator.attribute.DependencyAttribute;
+import org.opendaylight.controller.config.yangjmxgenerator.attribute.JavaAttribute;
+import org.opendaylight.controller.config.yangjmxgenerator.attribute.ListAttribute;
+import org.opendaylight.controller.config.yangjmxgenerator.attribute.TOAttribute;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.util.FullyQualifiedNameHelper;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.util.NameConflictException;
+import org.opendaylight.yangtools.binding.generator.util.BindingGeneratorUtil;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
+import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.ModuleImport;
+import org.opendaylight.yangtools.yang.model.api.RevisionAwareXPath;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.UsesNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Sets;
+
+/**
+ * Represents part of yang model that describes a module.
+ *
+ * Example:
+ * <p>
+ * <blockquote>
+ *
+ * <pre>
+ * identity threadpool-dynamic {
+ * base config:module-type;
+ * description "threadpool-dynamic description";
+ * config:provided-service "th2:threadpool";
+ * config:provided-service "th2:scheduled-threadpool";
+ * config:java-name-prefix DynamicThreadPool
+ * }
+ * augment "/config:modules/config:module/config:module-type" {
+ * case threadpool-dynamic {
+ * when "/config:modules/config:module/config:module-type = 'threadpool-dynamic'";
+ *
+ * container "configuration" {
+ * // regular java attribute
+ * leaf core-size {
+ * type uint32;
+ * }
+ *
+ * ...
+ * // dependency
+ * container threadfactory {
+ * uses config:service-ref {
+ * refine type {
+ * config:required-identity th:threadfactory;
+ * }
+ * }
+ * }
+ * }
+ * }
+ * </pre>
+ *
+ * </blockquote>
+ * </p>
+ */
+public class ModuleMXBeanEntry extends AbstractEntry {
+ private static final Logger logger = LoggerFactory
+ .getLogger(ModuleMXBeanEntry.class);
+
+ // TODO: the XPath should be parsed by code generator IMO
+ private static final String MAGIC_STRING = "MAGIC_STRING";
+ private static final String MODULE_CONDITION_XPATH_TEMPLATE = "^/MAGIC_STRING:modules/MAGIC_STRING:module/MAGIC_STRING:type\\s*=\\s*['\"](.+)['\"]$";
+ private static final SchemaPath expectedConfigurationAugmentationSchemaPath = new SchemaPath(
+ Arrays.asList(createConfigQName("modules"),
+ createConfigQName("module"),
+ createConfigQName("configuration")), true);
+ private static final SchemaPath expectedStateAugmentationSchemaPath = new SchemaPath(
+ Arrays.asList(createConfigQName("modules"),
+ createConfigQName("module"), createConfigQName("state")),
+ true);
+
+ private static final Pattern PREFIX_COLON_LOCAL_NAME = Pattern
+ .compile("^(.+):(.+)$");
+
+ private static final String MODULE_SUFFIX = "Module";
+ private static final String FACTORY_SUFFIX = MODULE_SUFFIX + "Factory";
+ private static final String CLASS_NAME_SUFFIX = MODULE_SUFFIX + "MXBean";
+ private static final String ABSTRACT_PREFIX = "Abstract";
+
+ /*
+ * threadpool-dynamic from the example above, taken from when condition, not
+ * the case name
+ */
+ private final String globallyUniqueName;
+
+ private Map<String, AttributeIfc> yangToAttributes;
+
+ private final String nullableDescription, packageName, javaNamePrefix,
+ namespace;
+
+ private final Map<String, String> providedServices;
+
+ private Collection<RuntimeBeanEntry> runtimeBeans;
+
+ public ModuleMXBeanEntry(IdentitySchemaNode id,
+ Map<String, AttributeIfc> yangToAttributes, String packageName,
+ Map<String, String> providedServices2, String javaNamePrefix,
+ String namespace, Collection<RuntimeBeanEntry> runtimeBeans) {
+ this.globallyUniqueName = id.getQName().getLocalName();
+ this.yangToAttributes = yangToAttributes;
+ this.nullableDescription = id.getDescription();
+ this.packageName = packageName;
+ this.javaNamePrefix = checkNotNull(javaNamePrefix);
+ this.namespace = checkNotNull(namespace);
+ this.providedServices = Collections.unmodifiableMap(providedServices2);
+ this.runtimeBeans = runtimeBeans;
+ }
+
+ public String getMXBeanInterfaceName() {
+ return javaNamePrefix + CLASS_NAME_SUFFIX;
+ }
+
+ public String getStubFactoryName() {
+ return javaNamePrefix + FACTORY_SUFFIX;
+ }
+
+ public String getAbstractFactoryName() {
+ return ABSTRACT_PREFIX + getStubFactoryName();
+ }
+
+ public String getStubModuleName() {
+ return javaNamePrefix + MODULE_SUFFIX;
+ }
+
+ public String getAbstractModuleName() {
+ return ABSTRACT_PREFIX + getStubModuleName();
+ }
+
+ public String getFullyQualifiedName(String typeName) {
+ return FullyQualifiedNameHelper.getFullyQualifiedName(packageName,
+ typeName);
+ }
+
+ public String getGloballyUniqueName() {
+ return globallyUniqueName;
+ }
+
+ public String getPackageName() {
+ return packageName;
+ }
+
+ public Map<String, String> getProvidedServices() {
+ return providedServices;
+ }
+
+ public void setRuntimeBeans(Collection<RuntimeBeanEntry> newRuntimeBeans) {
+ runtimeBeans = newRuntimeBeans;
+ }
+
+ public Collection<RuntimeBeanEntry> getRuntimeBeans() {
+ return runtimeBeans;
+ }
+
+ public String getJavaNamePrefix() {
+ return javaNamePrefix;
+ }
+
+ public String getNamespace() {
+ return namespace;
+ }
+
+ @VisibleForTesting
+ static Matcher getWhenConditionMatcher(String prefix,
+ RevisionAwareXPath whenConstraint) {
+ String xpathRegex = MODULE_CONDITION_XPATH_TEMPLATE.replace(
+ MAGIC_STRING, prefix);
+ Pattern pattern = Pattern.compile(xpathRegex);
+ return pattern.matcher(whenConstraint.toString());
+ }
+
+ static String getConfigModulePrefixFromImport(Module currentModule) {
+ for (ModuleImport currentImport : currentModule.getImports()) {
+ if (currentImport.getModuleName().equals(
+ ConfigConstants.CONFIG_MODULE)) {
+ return currentImport.getPrefix();
+ }
+ }
+ throw new IllegalArgumentException("Cannot find import "
+ + ConfigConstants.CONFIG_MODULE + " in " + currentModule);
+ }
+
+ /**
+ * Transform module to zero or more ModuleMXBeanEntry instances. Each
+ * instance must have a globally unique local name.
+ *
+ * @return Map of identity local names as keys, and ModuleMXBeanEntry
+ * instances as values
+ */
+ public static Map<String/* identity local name */, ModuleMXBeanEntry> create(
+ Module currentModule,
+ Map<QName, ServiceInterfaceEntry> qNamesToSIEs,
+ SchemaContext schemaContext,
+ TypeProviderWrapper typeProviderWrapper, String packageName) {
+ Map<String, QName> uniqueGeneratedClassesNames = new HashMap<>();
+ logger.debug("Generating ModuleMXBeans of {} to package {}",
+ currentModule.getNamespace(), packageName);
+ String configModulePrefix;
+ try {
+ configModulePrefix = getConfigModulePrefixFromImport(currentModule);
+ } catch (IllegalArgumentException e) {
+ // this module does not import config module
+ return Collections.emptyMap();
+ }
+
+ // get identities of base config:module-type
+ Map<String, IdentitySchemaNode> moduleIdentities = new HashMap<>();
+
+ for (IdentitySchemaNode id : currentModule.getIdentities()) {
+ if (id.getBaseIdentity() != null
+ && ConfigConstants.MODULE_TYPE_Q_NAME.equals(id
+ .getBaseIdentity().getQName())) {
+ String identityLocalName = id.getQName().getLocalName();
+ if (moduleIdentities.containsKey(identityLocalName)) {
+ throw new IllegalStateException(
+ "Module name already defined in this module: "
+ + identityLocalName);
+ } else {
+ moduleIdentities.put(identityLocalName, id);
+ logger.debug("Found identity {}", identityLocalName);
+ }
+ // validation check on unknown schema nodes
+ boolean providedServiceWasSet = false;
+ for (UnknownSchemaNode unknownNode : id.getUnknownSchemaNodes()) {
+ // TODO: test this
+ if (ConfigConstants.PROVIDED_SERVICE_EXTENSION_QNAME
+ .equals(unknownNode.getNodeType())) {
+ // no op: 0 or more provided identities are allowed
+ } else if (ConfigConstants.JAVA_NAME_PREFIX_EXTENSION_QNAME
+ .equals(unknownNode.getNodeType())) {
+ // 0..1 allowed
+ checkState(
+ providedServiceWasSet == false,
+ format("More than one language extension %s is not allowed here: %s",
+ ConfigConstants.JAVA_NAME_PREFIX_EXTENSION_QNAME,
+ id));
+ providedServiceWasSet = true;
+ } else {
+ throw new IllegalStateException(
+ "Unexpected language extension "
+ + unknownNode.getNodeType());
+ }
+ }
+ }
+ }
+ Map<String, ModuleMXBeanEntry> result = new HashMap<>();
+ // each module name should have an augmentation defined
+ Map<String, IdentitySchemaNode> unaugmentedModuleIdentities = new HashMap<>(
+ moduleIdentities);
+ for (AugmentationSchema augmentation : currentModule.getAugmentations()) {
+ Set<DataSchemaNode> childNodes = augmentation.getChildNodes();
+ if (childNodes.size() == 1) {
+ DataSchemaNode when = childNodes.iterator().next();
+ if (when instanceof ChoiceCaseNode) {
+ ChoiceCaseNode choiceCaseNode = (ChoiceCaseNode) when;
+ if (choiceCaseNode.getConstraints() == null
+ || choiceCaseNode.getConstraints()
+ .getWhenCondition() == null) {
+ continue;
+ }
+ RevisionAwareXPath xPath = choiceCaseNode.getConstraints()
+ .getWhenCondition();
+ Matcher matcher = getWhenConditionMatcher(
+ configModulePrefix, xPath);
+ if (matcher.matches() == false) {
+ continue;
+ }
+ String moduleLocalNameFromXPath = matcher.group(1);
+ IdentitySchemaNode moduleIdentity = moduleIdentities
+ .get(moduleLocalNameFromXPath);
+ unaugmentedModuleIdentities
+ .remove(moduleLocalNameFromXPath);
+ checkState(moduleIdentity != null, "Cannot find identity "
+ + moduleLocalNameFromXPath
+ + " matching augmentation " + augmentation);
+ Map<String, String> providedServices = findProvidedServices(
+ moduleIdentity, currentModule, qNamesToSIEs,
+ schemaContext);
+
+ if (moduleIdentity == null) {
+ throw new IllegalStateException(
+ "Cannot find identity specified by augmentation xpath constraint: "
+ + moduleLocalNameFromXPath + " of "
+ + augmentation);
+ }
+ String javaNamePrefix = findJavaNamePrefix(moduleIdentity);
+
+ Map<String, AttributeIfc> yangToAttributes = null;
+ // runtime-data
+ Collection<RuntimeBeanEntry> runtimeBeans = null;
+
+ if (expectedConfigurationAugmentationSchemaPath
+ .equals(augmentation.getTargetPath())) {
+ logger.debug("Parsing configuration of {}",
+ moduleLocalNameFromXPath);
+ yangToAttributes = fillConfiguration(choiceCaseNode,
+ currentModule, typeProviderWrapper,
+ qNamesToSIEs, schemaContext);
+ checkUniqueAttributesWithGeneratedClass(
+ uniqueGeneratedClassesNames, when.getQName(),
+ yangToAttributes);
+ } else if (expectedStateAugmentationSchemaPath
+ .equals(augmentation.getTargetPath())) {
+ logger.debug("Parsing state of {}",
+ moduleLocalNameFromXPath);
+ try {
+ runtimeBeans = fillRuntimeBeans(choiceCaseNode,
+ currentModule, typeProviderWrapper,
+ packageName, moduleLocalNameFromXPath,
+ javaNamePrefix);
+ } catch (NameConflictException e) {
+ throw new NameConflictException(
+ e.getConflictingName(), when.getQName(),
+ when.getQName());
+ }
+
+ checkUniqueRuntimeBeansGeneratedClasses(
+ uniqueGeneratedClassesNames, when, runtimeBeans);
+ Set<RuntimeBeanEntry> runtimeBeanEntryValues = Sets
+ .newHashSet(runtimeBeans);
+ for (RuntimeBeanEntry entry : runtimeBeanEntryValues) {
+ checkUniqueAttributesWithGeneratedClass(
+ uniqueGeneratedClassesNames,
+ when.getQName(),
+ entry.getYangPropertiesToTypesMap());
+ }
+
+ } else {
+ throw new IllegalArgumentException(
+ "Cannot parse augmentation " + augmentation);
+ }
+ if (result.containsKey(moduleLocalNameFromXPath)) {
+ // either fill runtimeBeans or yangToAttributes
+ ModuleMXBeanEntry moduleMXBeanEntry = result
+ .get(moduleLocalNameFromXPath);
+ if (yangToAttributes != null
+ && moduleMXBeanEntry.getAttributes() == null) {
+ moduleMXBeanEntry
+ .setYangToAttributes(yangToAttributes);
+ } else if (runtimeBeans != null
+ && moduleMXBeanEntry.getRuntimeBeans() == null) {
+ moduleMXBeanEntry.setRuntimeBeans(runtimeBeans);
+ }
+ } else {
+ // construct ModuleMXBeanEntry
+ ModuleMXBeanEntry moduleMXBeanEntry = new ModuleMXBeanEntry(
+ moduleIdentity, yangToAttributes, packageName,
+ providedServices, javaNamePrefix, currentModule
+ .getNamespace().toString(),
+ runtimeBeans);
+ moduleMXBeanEntry.setYangModuleName(currentModule
+ .getName());
+ moduleMXBeanEntry
+ .setYangModuleLocalname(moduleLocalNameFromXPath);
+ result.put(moduleLocalNameFromXPath, moduleMXBeanEntry);
+ }
+ } // skip if child node is not ChoiceCaseNode
+ } // skip if childNodes != 1
+ }
+ // clean up nulls
+ for (Entry<String, ModuleMXBeanEntry> entry : result.entrySet()) {
+ ModuleMXBeanEntry module = entry.getValue();
+ if (module.getAttributes() == null) {
+ module.setYangToAttributes(Collections
+ .<String, AttributeIfc> emptyMap());
+ } else if (module.getRuntimeBeans() == null) {
+ module.setRuntimeBeans(Collections
+ .<RuntimeBeanEntry> emptyList());
+ }
+ }
+ if (unaugmentedModuleIdentities.size() > 0) {
+ logger.warn("Augmentation not found for all module identities: {}",
+ unaugmentedModuleIdentities.keySet());
+ }
+
+ logger.debug("Number of ModuleMXBeans to be generated: {}",
+ result.size());
+ return result;
+ }
+
+ private static void checkUniqueRuntimeBeansGeneratedClasses(
+ Map<String, QName> uniqueGeneratedClassesNames,
+ DataSchemaNode when, Collection<RuntimeBeanEntry> runtimeBeans) {
+ for (RuntimeBeanEntry runtimeBean : runtimeBeans) {
+ final String javaNameOfRuntimeMXBean = runtimeBean
+ .getJavaNameOfRuntimeMXBean();
+ if (uniqueGeneratedClassesNames
+ .containsKey(javaNameOfRuntimeMXBean)) {
+ QName firstDefinedQName = uniqueGeneratedClassesNames
+ .get(javaNameOfRuntimeMXBean);
+ throw new NameConflictException(javaNameOfRuntimeMXBean,
+ firstDefinedQName, when.getQName());
+ }
+ uniqueGeneratedClassesNames.put(javaNameOfRuntimeMXBean,
+ when.getQName());
+ }
+ }
+
+ private static void checkUniqueAttributesWithGeneratedClass(
+ Map<String, QName> uniqueGeneratedClassNames, QName parentQName,
+ Map<String, AttributeIfc> yangToAttributes) {
+ for (Entry<String, AttributeIfc> attr : yangToAttributes.entrySet()) {
+ if (attr.getValue() instanceof TOAttribute) {
+ checkUniqueTOAttr(uniqueGeneratedClassNames, parentQName,
+ (TOAttribute) attr.getValue());
+ } else if (attr.getValue() instanceof ListAttribute
+ && ((ListAttribute) attr.getValue()).getInnerAttribute() instanceof TOAttribute) {
+ checkUniqueTOAttr(uniqueGeneratedClassNames, parentQName,
+ (TOAttribute) ((ListAttribute) attr.getValue())
+ .getInnerAttribute());
+ }
+ }
+ }
+
+ private static void checkUniqueTOAttr(
+ Map<String, QName> uniqueGeneratedClassNames, QName parentQName,
+ TOAttribute attr) {
+ final String upperCaseCammelCase = attr.getUpperCaseCammelCase();
+ if (uniqueGeneratedClassNames.containsKey(upperCaseCammelCase)) {
+ QName firstDefinedQName = uniqueGeneratedClassNames
+ .get(upperCaseCammelCase);
+ throw new NameConflictException(upperCaseCammelCase,
+ firstDefinedQName, parentQName);
+ } else {
+ uniqueGeneratedClassNames.put(upperCaseCammelCase, parentQName);
+ }
+ }
+
+ private static Collection<RuntimeBeanEntry> fillRuntimeBeans(
+ ChoiceCaseNode choiceCaseNode, Module currentModule,
+ TypeProviderWrapper typeProviderWrapper, String packageName,
+ String moduleLocalNameFromXPath, String javaNamePrefix) {
+
+ return RuntimeBeanEntry.extractClassNameToRuntimeBeanMap(packageName,
+ choiceCaseNode, moduleLocalNameFromXPath, typeProviderWrapper,
+ javaNamePrefix, currentModule).values();
+
+ }
+
+ private static Map<String, AttributeIfc> fillConfiguration(
+ ChoiceCaseNode choiceCaseNode, Module currentModule,
+ TypeProviderWrapper typeProviderWrapper,
+ Map<QName, ServiceInterfaceEntry> qNamesToSIEs,
+ SchemaContext schemaContext) {
+ Map<String, AttributeIfc> yangToAttributes = new HashMap<>();
+ for (DataSchemaNode attrNode : choiceCaseNode.getChildNodes()) {
+ AttributeIfc attributeValue = getAttributeValue(attrNode,
+ currentModule, qNamesToSIEs, typeProviderWrapper,
+ schemaContext);
+ yangToAttributes.put(attributeValue.getAttributeYangName(),
+ attributeValue);
+ }
+ return yangToAttributes;
+ }
+
+ private static Map<String, String> findProvidedServices(
+ IdentitySchemaNode moduleIdentity, Module currentModule,
+ Map<QName, ServiceInterfaceEntry> qNamesToSIEs,
+ SchemaContext schemaContext) {
+ Map<String, String> result = new HashMap<>();
+ for (UnknownSchemaNode unknownNode : moduleIdentity
+ .getUnknownSchemaNodes()) {
+ if (ConfigConstants.PROVIDED_SERVICE_EXTENSION_QNAME
+ .equals(unknownNode.getNodeType())) {
+ String prefixAndIdentityLocalName = unknownNode
+ .getNodeParameter();
+ ServiceInterfaceEntry sie = findSIE(prefixAndIdentityLocalName,
+ currentModule, qNamesToSIEs, schemaContext);
+ result.put(sie.getFullyQualifiedName(), sie.getQName()
+ .getLocalName());
+ }
+ }
+ return result;
+ }
+
+ /**
+ * For input node, find if it contains config:java-name-prefix extension. If
+ * not found, convert local name of node converted to cammel case.
+ */
+ public static String findJavaNamePrefix(SchemaNode schemaNode) {
+ return convertToJavaName(schemaNode, true);
+ }
+
+ public static String findJavaParameter(SchemaNode schemaNode) {
+ return convertToJavaName(schemaNode, false);
+ }
+
+ public static String convertToJavaName(SchemaNode schemaNode,
+ boolean capitalizeFirstLetter) {
+ for (UnknownSchemaNode unknownNode : schemaNode.getUnknownSchemaNodes()) {
+ if (ConfigConstants.JAVA_NAME_PREFIX_EXTENSION_QNAME
+ .equals(unknownNode.getNodeType())) {
+ String value = unknownNode.getNodeParameter();
+ return convertToJavaName(value, capitalizeFirstLetter);
+ }
+ }
+ return convertToJavaName(schemaNode.getQName().getLocalName(),
+ capitalizeFirstLetter);
+ }
+
+ public static String convertToJavaName(String localName,
+ boolean capitalizeFirstLetter) {
+ if (capitalizeFirstLetter) {
+ return BindingGeneratorUtil.parseToClassName(localName);
+ } else {
+ return BindingGeneratorUtil.parseToValidParamName(localName);
+ }
+ }
+
+ private static int getChildNodeSizeWithoutUses(ContainerSchemaNode csn) {
+ int result = 0;
+ for (DataSchemaNode dsn : csn.getChildNodes()) {
+ if (dsn.isAddedByUses() == false)
+ result++;
+ }
+ return result;
+ }
+
+ private static AttributeIfc getAttributeValue(DataSchemaNode attrNode,
+ Module currentModule,
+ Map<QName, ServiceInterfaceEntry> qNamesToSIEs,
+ TypeProviderWrapper typeProviderWrapper, SchemaContext schemaContext) {
+
+ if (attrNode instanceof LeafSchemaNode) {
+ // simple type
+ LeafSchemaNode leaf = (LeafSchemaNode) attrNode;
+ return new JavaAttribute(leaf, typeProviderWrapper);
+ } else if (attrNode instanceof ContainerSchemaNode) {
+ // reference or TO
+ ContainerSchemaNode containerSchemaNode = (ContainerSchemaNode) attrNode;
+ if (containerSchemaNode.getUses().size() == 1
+ && getChildNodeSizeWithoutUses(containerSchemaNode) == 0) {
+ // reference
+ UsesNode usesNode = containerSchemaNode.getUses().iterator()
+ .next();
+ checkState(usesNode.getRefines().size() == 1,
+ "Unexpected 'refine' child node size of "
+ + containerSchemaNode);
+ LeafSchemaNode refine = (LeafSchemaNode) usesNode.getRefines()
+ .values().iterator().next();
+ checkState(refine.getUnknownSchemaNodes().size() == 1,
+ "Unexpected unknown schema node size of " + refine);
+ UnknownSchemaNode requiredIdentity = refine
+ .getUnknownSchemaNodes().iterator().next();
+ checkState(
+ ConfigConstants.REQUIRED_IDENTITY_EXTENSION_QNAME.equals(requiredIdentity
+ .getNodeType()),
+ "Unexpected language extension " + requiredIdentity);
+ String prefixAndIdentityLocalName = requiredIdentity
+ .getNodeParameter();
+ // import should point to a module
+ ServiceInterfaceEntry serviceInterfaceEntry = findSIE(
+ prefixAndIdentityLocalName, currentModule,
+ qNamesToSIEs, schemaContext);
+ boolean mandatory = refine.getConstraints().isMandatory();
+ return new DependencyAttribute(attrNode, serviceInterfaceEntry,
+ mandatory, attrNode.getDescription());
+ } else {
+ return TOAttribute.create(containerSchemaNode,
+ typeProviderWrapper);
+ }
+ } else if (attrNode instanceof LeafListSchemaNode) {
+ return ListAttribute.create((LeafListSchemaNode) attrNode,
+ typeProviderWrapper);
+ } else if (attrNode instanceof ListSchemaNode) {
+ return ListAttribute.create((ListSchemaNode) attrNode,
+ typeProviderWrapper);
+ } else {
+ throw new UnsupportedOperationException(
+ "Unknown configuration node " + attrNode.toString());
+ }
+ }
+
+ private static ServiceInterfaceEntry findSIE(
+ String prefixAndIdentityLocalName, Module currentModule,
+ Map<QName, ServiceInterfaceEntry> qNamesToSIEs,
+ SchemaContext schemaContext) {
+
+ Matcher m = PREFIX_COLON_LOCAL_NAME.matcher(prefixAndIdentityLocalName);
+ Module foundModule;
+ String localSIName;
+ if (m.matches()) {
+ // if there is a prefix, look for ModuleImport with this prefix. Get
+ // Module from SchemaContext
+ String prefix = m.group(1);
+ ModuleImport moduleImport = findModuleImport(currentModule, prefix);
+ foundModule = schemaContext.findModuleByName(
+ moduleImport.getModuleName(), moduleImport.getRevision());
+ checkState(
+ foundModule != null,
+ format("Module not found in SchemaContext by %s",
+ moduleImport));
+ localSIName = m.group(2);
+ } else {
+ foundModule = currentModule; // no prefix => SIE is in currentModule
+ localSIName = prefixAndIdentityLocalName;
+ }
+ QName siQName = new QName(foundModule.getNamespace(),
+ foundModule.getRevision(), localSIName);
+ ServiceInterfaceEntry sie = qNamesToSIEs.get(siQName);
+ checkState(sie != null, "Cannot find referenced Service Interface by "
+ + prefixAndIdentityLocalName);
+ return sie;
+ }
+
+ private static ModuleImport findModuleImport(Module module, String prefix) {
+ for (ModuleImport moduleImport : module.getImports()) {
+ if (moduleImport.getPrefix().equals(prefix)) {
+ return moduleImport;
+ }
+ }
+ throw new IllegalStateException(format(
+ "Import not found with prefix %s in %s", prefix, module));
+ }
+
+ public Map<String, AttributeIfc> getAttributes() {
+ return yangToAttributes;
+ }
+
+ private void setYangToAttributes(Map<String, AttributeIfc> newAttributes) {
+ this.yangToAttributes = newAttributes;
+
+ }
+
+ public String getNullableDescription() {
+ return nullableDescription;
+ }
+
+ @Override
+ public String toString() {
+ return "ModuleMXBeanEntry{" + "globallyUniqueName='"
+ + globallyUniqueName + '\'' + ", packageName='" + packageName
+ + '\'' + '}';
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator;
+
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.opendaylight.yangtools.binding.generator.util.BindingGeneratorUtil;
+import org.opendaylight.yangtools.yang.model.api.Module;
+
+/**
+ * Maps from module namespaces to java package names using a Map<String,
+ * String>, where key is namespace prefix and value is package that replaces
+ * matched prefix.
+ */
+public class PackageTranslator {
+ private final Map<String, String> namespacePrefixToPackageMap;
+
+ public PackageTranslator(Map<String, String> namespacePrefixToPackageMap) {
+ this.namespacePrefixToPackageMap = namespacePrefixToPackageMap;
+ }
+
+ /**
+ * Based on mapping, find longest matching key and return value plus the
+ * remaining part of namespace, with colons replaced by dots. Example:
+ * Mapping [ 'urn:opendaylight:params:xml:ns:yang:controller' :
+ * 'org.opendaylight.controller'] and module with namespace
+ * 'urn:opendaylight:params:xml:ns:yang:controller:threads:api' will result
+ * in 'org.opendaylight.controller.threads.api' .
+ *
+ * @throws IllegalStateException
+ * if there is no mapping found.
+ */
+ public String getPackageName(Module module) {
+ Entry<String, String> longestMatch = null;
+ int longestMatchLength = 0;
+ String namespace = module.getNamespace().toString();
+ for (Entry<String, String> entry : namespacePrefixToPackageMap
+ .entrySet()) {
+ if (namespace.startsWith(entry.getKey())
+ && entry.getKey().length() > longestMatchLength) {
+ longestMatch = entry;
+ longestMatchLength = entry.getKey().length();
+ }
+ }
+ if (longestMatch != null) {
+ return longestMatch.getValue()
+ + sanitizePackage(namespace.substring(longestMatchLength));
+ } else {
+ return BindingGeneratorUtil.moduleNamespaceToPackageName(module);
+ }
+ }
+
+ // TODO add to PackageTranslator
+ private static String sanitizePackage(String namespace) {
+ namespace = namespace.replace("://", ".");
+ namespace = namespace.replace("/", ".");
+ namespace = namespace.replace(":", ".");
+ namespace = namespace.replace("-", "_");
+ namespace = namespace.replace("@", ".");
+ namespace = namespace.replace("$", ".");
+ namespace = namespace.replace("#", ".");
+ namespace = namespace.replace("'", ".");
+ namespace = namespace.replace("*", ".");
+ namespace = namespace.replace("+", ".");
+ namespace = namespace.replace(",", ".");
+ namespace = namespace.replace(";", ".");
+ namespace = namespace.replace("=", ".");
+ return namespace;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.opendaylight.controller.config.yangjmxgenerator.attribute.AttributeIfc;
+import org.opendaylight.controller.config.yangjmxgenerator.attribute.JavaAttribute;
+import org.opendaylight.controller.config.yangjmxgenerator.attribute.ListAttribute;
+import org.opendaylight.controller.config.yangjmxgenerator.attribute.TOAttribute;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.util.FullyQualifiedNameHelper;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.util.NameConflictException;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.UsesNode;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Optional;
+import com.google.common.collect.Lists;
+
+/**
+ * Holds information about runtime bean to be generated. There are two kinds of
+ * RuntimeBeanEntry instances: if isRoot flag is set to true, this bean
+ * represents state that must be present at time of configuration module
+ * instantiation. Root RB must have depthLevel set to 0 and cannot have
+ * children. There might be other RBs defined in yang, but no other RB can have
+ * isRoot set to true. At least one RB must be root and all other RBs must be
+ * lined via children so that a tree with all beans can be created.
+ */
+public class RuntimeBeanEntry {
+ private final String packageName;
+ private final String yangName, javaNamePrefix;
+ private final boolean isRoot;
+ private final Optional<String> keyYangName, keyJavaName;
+ private final Map<String, AttributeIfc> attributeMap;
+ private final List<RuntimeBeanEntry> children;
+ private final Set<Rpc> rpcs;
+
+ @VisibleForTesting
+ public RuntimeBeanEntry(String packageName,
+ DataSchemaNode nodeForReporting, String yangName,
+ String javaNamePrefix, boolean isRoot,
+ Optional<String> keyYangName, List<AttributeIfc> attributes,
+ List<RuntimeBeanEntry> children, Set<Rpc> rpcs) {
+
+ checkArgument(isRoot == false || keyYangName.isPresent() == false,
+ "Root RuntimeBeanEntry must not have key " + "set");
+ this.packageName = packageName;
+ this.isRoot = isRoot;
+ this.yangName = yangName;
+ this.javaNamePrefix = javaNamePrefix;
+ this.children = Collections.unmodifiableList(children);
+ this.rpcs = Collections.unmodifiableSet(rpcs);
+
+ this.keyYangName = keyYangName;
+ Map<String, AttributeIfc> map = new HashMap<>();
+
+ for (AttributeIfc a : attributes) {
+ checkState(map.containsKey(a.getAttributeYangName()) == false,
+ "Attribute already defined: " + a.getAttributeYangName()
+ + " in " + nodeForReporting);
+ map.put(a.getAttributeYangName(), a);
+ }
+
+ if (keyYangName.isPresent()) {
+ AttributeIfc keyJavaName = map.get(keyYangName.get());
+ checkArgument(keyJavaName != null, "Key " + keyYangName.get()
+ + " not found in attribute " + "list " + attributes
+ + " in " + nodeForReporting);
+ this.keyJavaName = Optional
+ .of(keyJavaName.getUpperCaseCammelCase());
+ } else {
+ keyJavaName = Optional.absent();
+ }
+ attributeMap = Collections.unmodifiableMap(map);
+ }
+
+ /**
+ * @return map containing all class names as key, extracted RuntimeBeans as
+ * values. If more than zero values is returned, exactly one
+ * RuntimeBeanEntry will have isRoot set to true, even if yang does
+ * not contain special configuration for it.
+ */
+ public static Map<String, RuntimeBeanEntry> extractClassNameToRuntimeBeanMap(
+ String packageName, ChoiceCaseNode container,
+ String moduleYangName, TypeProviderWrapper typeProviderWrapper,
+ String javaNamePrefix, Module currentModule) {
+
+ Map<QName, Set<RpcDefinition>> identitiesToRpcs = getIdentitiesToRpcs(currentModule);
+
+ AttributesRpcsAndRuntimeBeans attributesRpcsAndRuntimeBeans = extractSubtree(
+ packageName, container, typeProviderWrapper, currentModule,
+ identitiesToRpcs);
+ Map<String, RuntimeBeanEntry> result = new HashMap<>();
+
+ List<AttributeIfc> attributes;
+ Set<Rpc> rpcs;
+ if (attributesRpcsAndRuntimeBeans.isEmpty() == false) {
+ attributes = attributesRpcsAndRuntimeBeans.getAttributes();
+ rpcs = attributesRpcsAndRuntimeBeans.getRpcs();
+ } else {
+ // create artificial root if not defined in yang
+ attributes = Collections.emptyList();
+ rpcs = Collections.emptySet();
+ }
+ RuntimeBeanEntry rootRuntimeBeanEntry = createRoot(packageName,
+ container, moduleYangName, attributes, javaNamePrefix,
+ attributesRpcsAndRuntimeBeans.getRuntimeBeanEntries(), rpcs);
+
+ Deque<RuntimeBeanEntry> stack = new LinkedList<>();
+ stack.add(rootRuntimeBeanEntry);
+
+ while (stack.isEmpty() == false) {
+ RuntimeBeanEntry first = stack.pollFirst();
+ if (result.containsKey(first.getJavaNameOfRuntimeMXBean())) {
+ throw new NameConflictException(
+ first.getJavaNameOfRuntimeMXBean(), null, null);
+ }
+ result.put(first.getJavaNameOfRuntimeMXBean(), first);
+ stack.addAll(first.getChildren());
+ }
+ return result;
+ }
+
+ private static Map<QName/* of identity */, Set<RpcDefinition>> getIdentitiesToRpcs(
+ Module currentModule) {
+ // currently only looks for local identities (found in currentModule)
+ Map<QName, Set<RpcDefinition>> result = new HashMap<>();
+ for (IdentitySchemaNode identity : currentModule.getIdentities()) {
+ // add all
+ result.put(identity.getQName(), new HashSet<RpcDefinition>());
+ }
+
+ for (RpcDefinition rpc : currentModule.getRpcs()) {
+ ContainerSchemaNode input = rpc.getInput();
+ for (UsesNode uses : input.getUses()) {
+
+ if (uses.getGroupingPath().getPath().size() != 1)
+ continue;
+
+ // check grouping path
+ QName qname = uses.getGroupingPath().getPath().get(0);
+ if (false == qname
+ .equals(ConfigConstants.RPC_CONTEXT_REF_GROUPING_QNAME))
+ continue;
+
+ for (SchemaNode refinedNode : uses.getRefines().values()) {
+
+ for (UnknownSchemaNode unknownSchemaNode : refinedNode
+ .getUnknownSchemaNodes()) {
+ if (ConfigConstants.RPC_CONTEXT_INSTANCE_EXTENSION_QNAME
+ .equals(unknownSchemaNode.getNodeType())) {
+ String localIdentityName = unknownSchemaNode
+ .getNodeParameter();
+ QName identityQName = new QName(
+ currentModule.getNamespace(),
+ currentModule.getRevision(),
+ localIdentityName);
+ Set<RpcDefinition> rpcDefinitions = result
+ .get(identityQName);
+ if (rpcDefinitions == null) {
+ throw new IllegalArgumentException(
+ "Identity referenced by rpc not found. Identity:"
+ + localIdentityName + " , rpc "
+ + rpc);
+ }
+ rpcDefinitions.add(rpc);
+ }
+ }
+
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Get direct descendants of this subtree, together with attributes defined
+ * in subtree.
+ */
+ private static AttributesRpcsAndRuntimeBeans extractSubtree(
+ String packageName, DataNodeContainer subtree,
+ TypeProviderWrapper typeProviderWrapper, Module currentModule,
+ Map<QName, Set<RpcDefinition>> identitiesToRpcs) {
+
+ List<AttributeIfc> attributes = Lists.newArrayList();
+ // List<JavaAttribute> javaAttributes = new ArrayList<>();
+ // List<TOAttribute> toAttributes = new ArrayList<>();
+ List<RuntimeBeanEntry> runtimeBeanEntries = new ArrayList<>();
+ for (DataSchemaNode child : subtree.getChildNodes()) {
+ // child leaves can be java attributes, TO attributes, or child
+ // runtime beans
+ if (child instanceof LeafSchemaNode) {
+ // just save the attribute
+ LeafSchemaNode leaf = (LeafSchemaNode) child;
+ attributes.add(new JavaAttribute(leaf, typeProviderWrapper));
+ } else if (child instanceof ContainerSchemaNode) {
+ ContainerSchemaNode container = (ContainerSchemaNode) child;
+ // this can be either TO or hierarchical RB
+ TOAttribute toAttribute = TOAttribute.create(container,
+ typeProviderWrapper);
+ attributes.add(toAttribute);
+ } else if (child instanceof ListSchemaNode) {
+ if (isInnerStateBean(child)) {
+ ListSchemaNode listSchemaNode = (ListSchemaNode) child;
+ RuntimeBeanEntry hierarchicalChild = createHierarchical(
+ packageName, listSchemaNode, typeProviderWrapper,
+ currentModule, identitiesToRpcs);
+ runtimeBeanEntries.add(hierarchicalChild);
+ } else /* ordinary list attribute */{
+ ListAttribute listAttribute = ListAttribute.create(
+ (ListSchemaNode) child, typeProviderWrapper);
+ attributes.add(listAttribute);
+ }
+
+ } else {
+ throw new IllegalStateException("Unknown running-data node "
+ + child + " , " + "" + "expected leaf or container");
+ }
+ }
+ Set<Rpc> rpcs = new HashSet<>();
+ SchemaNode subtreeSchemaNode = (SchemaNode) subtree;
+ for (UnknownSchemaNode unknownSchemaNode : subtreeSchemaNode
+ .getUnknownSchemaNodes()) {
+ if (ConfigConstants.RPC_CONTEXT_INSTANCE_EXTENSION_QNAME
+ .equals(unknownSchemaNode.getNodeType())) {
+ String localIdentityName = unknownSchemaNode.getNodeParameter();
+ QName identityQName = new QName(currentModule.getNamespace(),
+ currentModule.getRevision(), localIdentityName);
+ Set<RpcDefinition> rpcDefinitions = identitiesToRpcs
+ .get(identityQName);
+ if (rpcDefinitions == null) {
+ throw new IllegalArgumentException("Cannot find identity "
+ + localIdentityName + " to be used as "
+ + "context reference when resolving "
+ + unknownSchemaNode);
+ }
+ // convert RpcDefinition to Rpc
+ for (RpcDefinition rpcDefinition : rpcDefinitions) {
+ String name = ModuleMXBeanEntry
+ .findJavaParameter(rpcDefinition);
+ String returnType;
+ if (rpcDefinition.getOutput() == null
+ || rpcDefinition.getOutput().getChildNodes().size() == 0) {
+ returnType = "void";
+ } else if (rpcDefinition.getOutput().getChildNodes().size() == 1) {
+ DataSchemaNode returnDSN = rpcDefinition.getOutput()
+ .getChildNodes().iterator().next();
+ checkArgument(
+ returnDSN instanceof LeafSchemaNode,
+ "Unexpected type of rpc return type. "
+ + "Currently only leafs and empty output nodes are supported, got "
+ + returnDSN);
+ LeafSchemaNode returnLeaf = (LeafSchemaNode) returnDSN;
+ // We currently expect leaf defined in output element in yang to be named result
+ // FIXME: value of result is fully qualified name - should be extended to accept TOs
+ String localName = returnLeaf.getQName().getLocalName();
+ checkArgument(
+ localName.equals("result"),
+ "Unexpected name of leaf in output element, expected leaf named result, was %s at %s",
+ localName, currentModule.getName());
+
+ returnType = typeProviderWrapper.getType(returnLeaf)
+ .getFullyQualifiedName();
+ } else {
+ throw new IllegalArgumentException(
+ "More than one child node in rpc output is not supported. "
+ + "Error occured in " + rpcDefinition);
+ }
+ List<JavaAttribute> parameters = new ArrayList<>();
+ for (DataSchemaNode childNode : rpcDefinition.getInput()
+ .getChildNodes()) {
+ if (childNode.isAddedByUses() == false) { // skip
+ // refined
+ // context-instance
+ checkArgument(childNode instanceof LeafSchemaNode, "Unexpected type of rpc input type. "
+ + "Currently only leafs and empty output nodes are supported, got " + childNode);
+ JavaAttribute javaAttribute = new JavaAttribute(
+ (LeafSchemaNode) childNode,
+ typeProviderWrapper);
+ parameters.add(javaAttribute);
+ }
+ }
+ Rpc newRpc = new Rpc(returnType, name, rpcDefinition
+ .getQName().getLocalName(), parameters);
+ rpcs.add(newRpc);
+ }
+ }
+ }
+ return new AttributesRpcsAndRuntimeBeans(runtimeBeanEntries,
+ attributes, rpcs);
+ }
+
+ private static boolean isInnerStateBean(DataSchemaNode child) {
+ for (UnknownSchemaNode unknownSchemaNode : child
+ .getUnknownSchemaNodes()) {
+ if (unknownSchemaNode.getNodeType().equals(
+ ConfigConstants.INNER_STATE_BEAN_EXTENSION_QNAME))
+ return true;
+ }
+ return false;
+ }
+
+ private static RuntimeBeanEntry createHierarchical(String packageName,
+ ListSchemaNode listSchemaNode,
+ TypeProviderWrapper typeProviderWrapper, Module currentModule,
+ Map<QName, Set<RpcDefinition>> identitiesToRpcs) {
+
+ // supported are numeric types, strings, enums
+ // get all attributes
+ AttributesRpcsAndRuntimeBeans attributesRpcsAndRuntimeBeans = extractSubtree(
+ packageName, listSchemaNode, typeProviderWrapper,
+ currentModule, identitiesToRpcs);
+
+ Optional<String> keyYangName;
+ if (listSchemaNode.getKeyDefinition().size() == 0) {
+ keyYangName = Optional.absent();
+ } else if (listSchemaNode.getKeyDefinition().size() == 1) {
+ // key must be either null or one of supported key types
+ QName keyQName = listSchemaNode.getKeyDefinition().iterator()
+ .next();
+ keyYangName = Optional.of(keyQName.getLocalName());
+
+ } else {
+ throw new IllegalArgumentException(
+ "More than one key is not supported in " + listSchemaNode);
+ }
+
+ String javaNamePrefix = ModuleMXBeanEntry
+ .findJavaNamePrefix(listSchemaNode);
+
+ RuntimeBeanEntry rbFromAttributes = new RuntimeBeanEntry(packageName,
+ listSchemaNode, listSchemaNode.getQName().getLocalName(),
+ javaNamePrefix, false, keyYangName,
+ attributesRpcsAndRuntimeBeans.getAttributes(),
+ attributesRpcsAndRuntimeBeans.getRuntimeBeanEntries(),
+ attributesRpcsAndRuntimeBeans.getRpcs());
+
+ return rbFromAttributes;
+ }
+
+ private static RuntimeBeanEntry createRoot(String packageName,
+ DataSchemaNode nodeForReporting, String attributeYangName,
+ List<AttributeIfc> attributes, String javaNamePrefix,
+ List<RuntimeBeanEntry> children, Set<Rpc> rpcs) {
+ return new RuntimeBeanEntry(packageName, nodeForReporting,
+ attributeYangName, javaNamePrefix, true,
+ Optional.<String> absent(), attributes, children, rpcs);
+ }
+
+ public boolean isRoot() {
+ return isRoot;
+ }
+
+ public Optional<String> getKeyYangName() {
+ return keyYangName;
+ }
+
+ public Optional<String> getKeyJavaName() {
+ return keyJavaName;
+ }
+
+ public Collection<AttributeIfc> getAttributes() {
+ return attributeMap.values();
+ }
+
+ public Map<String, AttributeIfc> getYangPropertiesToTypesMap() {
+ return attributeMap;
+ }
+
+ public String getYangName() {
+ return yangName;
+ }
+
+ public String getPackageName() {
+ return packageName;
+ }
+
+ public String getJavaNamePrefix() {
+ return javaNamePrefix;
+ }
+
+ public List<RuntimeBeanEntry> getChildren() {
+ return children;
+ }
+
+ public Set<Rpc> getRpcs() {
+ return rpcs;
+ }
+
+ private static class AttributesRpcsAndRuntimeBeans {
+ private final List<RuntimeBeanEntry> runtimeBeanEntries;
+ private final List<AttributeIfc> attributes;
+ private final Set<Rpc> rpcs;
+
+ public AttributesRpcsAndRuntimeBeans(
+ List<RuntimeBeanEntry> runtimeBeanEntries,
+ List<AttributeIfc> attributes, Set<Rpc> rpcs) {
+ this.runtimeBeanEntries = runtimeBeanEntries;
+ this.attributes = attributes;
+ this.rpcs = rpcs;
+ }
+
+ private List<AttributeIfc> getAttributes() {
+ return attributes;
+ }
+
+ public List<RuntimeBeanEntry> getRuntimeBeanEntries() {
+ return runtimeBeanEntries;
+ }
+
+ public boolean isEmpty() {
+ return attributes.isEmpty() && rpcs.isEmpty();
+ }
+
+ private Set<Rpc> getRpcs() {
+ return rpcs;
+ }
+ }
+
+ public static class Rpc {
+ private final String name;
+ private final List<JavaAttribute> parameters;
+ private final String returnType;
+ private final String yangName;
+
+ Rpc(String returnType, String name, String yangName,
+ List<JavaAttribute> parameters) {
+ this.returnType = returnType;
+ this.name = name;
+ this.parameters = parameters;
+ this.yangName = yangName;
+ }
+
+ public String getYangName() {
+ return yangName;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public List<JavaAttribute> getParameters() {
+ return parameters;
+ }
+
+ public String getReturnType() {
+ return returnType;
+ }
+ }
+
+ private static final String MXBEAN_SUFFIX = "RuntimeMXBean";
+
+ public String getJavaNameOfRuntimeMXBean() {
+ return getJavaNameOfRuntimeMXBean(javaNamePrefix);
+ }
+
+ public String getFullyQualifiedName(String typeName) {
+ return FullyQualifiedNameHelper.getFullyQualifiedName(packageName,
+ typeName);
+ }
+
+ private static String getJavaNameOfRuntimeMXBean(String javaNamePrefix) {
+ return javaNamePrefix + MXBEAN_SUFFIX;
+ }
+
+ @Override
+ public String toString() {
+ return "RuntimeBeanEntry{" + "isRoot=" + isRoot + ", yangName='"
+ + yangName + '\'' + ", packageName='" + packageName + '\''
+ + ", keyYangName=" + keyYangName + '}';
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.lang.String.format;
+import static org.opendaylight.controller.config.yangjmxgenerator.ConfigConstants.SERVICE_TYPE_Q_NAME;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Optional;
+
+/**
+ * Represents identity derived from {@link ConfigConstants#SERVICE_TYPE_Q_NAME}.
+ * Example:
+ * <p>
+ * <blockquote>
+ *
+ * <pre>
+ * identity eventbus {
+ * description
+ * "Service representing an event bus. The service acts as message
+ * router between event producers and event consumers";
+ *
+ * base "config:service-type";
+ * config:java-class "com.google.common.eventbus.EventBus";
+ * }
+ * </pre>
+ *
+ * </blockquote>
+ * </p>
+ */
+public class ServiceInterfaceEntry extends AbstractEntry {
+ private static final Logger logger = LoggerFactory
+ .getLogger(ServiceInterfaceEntry.class);
+
+ private static final String CLASS_NAME_SUFFIX = "ServiceInterface";
+ private final Optional<ServiceInterfaceEntry> maybeBaseCache;
+ private final String exportedOsgiClassName;
+ private final QName qName;
+ private final String nullableDescription, packageName, typeName;
+
+ private ServiceInterfaceEntry(IdentitySchemaNode id, String packageName) {
+ this(Optional.<ServiceInterfaceEntry> absent(), id, packageName);
+ }
+
+ private ServiceInterfaceEntry(Optional<ServiceInterfaceEntry> base,
+ IdentitySchemaNode id, String packageName) {
+ checkNotNull(base);
+ this.maybeBaseCache = base;
+ List<UnknownSchemaNode> unknownSchemaNodes = id.getUnknownSchemaNodes();
+ List<String> exportedOsgiClassNames = new ArrayList<>(
+ unknownSchemaNodes.size());
+ for (UnknownSchemaNode usn : unknownSchemaNodes) {
+ if (ConfigConstants.JAVA_CLASS_EXTENSION_QNAME.equals(usn
+ .getNodeType())) {
+ String localName = usn.getNodeParameter();
+ exportedOsgiClassNames.add(localName);
+ } else {
+ throw new IllegalStateException(format(
+ "Unexpected unknown schema node. Expected %s, got %s",
+ ConfigConstants.JAVA_CLASS_EXTENSION_QNAME,
+ usn.getNodeType()));
+ }
+ }
+ if (exportedOsgiClassNames.size() != 1) {
+ throw new IllegalArgumentException(
+ format("Cannot find one to one mapping from %s to "
+ + "java class defined by %s language extension in %s",
+ getClass(),
+ ConfigConstants.JAVA_CLASS_EXTENSION_QNAME, id));
+ }
+ this.exportedOsgiClassName = exportedOsgiClassNames.get(0);
+ qName = id.getQName();
+ nullableDescription = id.getDescription();
+ typeName = getSimpleName(exportedOsgiClassName) + CLASS_NAME_SUFFIX;
+ this.packageName = packageName;
+ }
+
+ private static final String getSimpleName(String fullyQualifiedName) {
+ int lastDotPosition = fullyQualifiedName.lastIndexOf(".");
+ return fullyQualifiedName.substring(lastDotPosition + 1);
+ }
+
+ public String getNullableDescription() {
+ return nullableDescription;
+ }
+
+ public Optional<ServiceInterfaceEntry> getBase() {
+ return maybeBaseCache;
+ }
+
+ public String getExportedOsgiClassName() {
+ return exportedOsgiClassName;
+ }
+
+ public QName getQName() {
+ return qName;
+ }
+
+ /**
+ * @return Map of QNames as keys and ServiceInterfaceEntry instances as
+ * values
+ */
+ public static Map<QName, ServiceInterfaceEntry> create(Module module,
+ String packageName) {
+ logger.debug("Generating ServiceInterfaces from {} to package {}",
+ module.getNamespace(), packageName);
+
+ Map<IdentitySchemaNode, ServiceInterfaceEntry> identitiesToSIs = new HashMap<>();
+ Set<IdentitySchemaNode> notVisited = new HashSet<>(
+ module.getIdentities());
+ int lastSize = notVisited.size() + 1;
+ while (notVisited.size() > 0) {
+ if (notVisited.size() == lastSize) {
+ logger.debug(
+ "Following identities will be ignored while generating ServiceInterfaces, as they are not derived from {} : {}",
+ SERVICE_TYPE_Q_NAME, notVisited);
+ break;
+ }
+ lastSize = notVisited.size();
+ for (Iterator<IdentitySchemaNode> iterator = notVisited.iterator(); iterator
+ .hasNext();) {
+ IdentitySchemaNode identity = iterator.next();
+ ServiceInterfaceEntry created = null;
+ if (identity.getBaseIdentity() == null) {
+ // this can happen while loading config module, just skip
+ // the identity
+ continue;
+ } else if (identity.getBaseIdentity().getQName()
+ .equals(SERVICE_TYPE_Q_NAME)) {
+ // this is a base type
+ created = new ServiceInterfaceEntry(identity, packageName);
+ } else {
+ ServiceInterfaceEntry foundBase = identitiesToSIs
+ .get(identity.getBaseIdentity());
+ // derived type, did we convert the parent?
+ if (foundBase != null) {
+ created = new ServiceInterfaceEntry(
+ Optional.of(foundBase), identity, packageName);
+ }
+ }
+ if (created != null) {
+ created.setYangModuleName(module.getName());
+ // TODO how to get local name
+ created.setYangModuleLocalname(identity.getQName()
+ .getLocalName());
+ identitiesToSIs.put(identity, created);
+ iterator.remove();
+ }
+ }
+ }
+ // create result map
+ Map<QName, ServiceInterfaceEntry> resultMap = new HashMap<>();
+ for (ServiceInterfaceEntry sie : identitiesToSIs.values()) {
+ resultMap.put(sie.getQName(), sie);
+ }
+ logger.debug("Number of ServiceInterfaces to be generated: {}",
+ resultMap.size());
+ return resultMap;
+ }
+
+ public String getFullyQualifiedName() {
+ return packageName + "." + typeName;
+ }
+
+ public String getPackageName() {
+ return packageName;
+ }
+
+ public String getTypeName() {
+ return typeName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ ServiceInterfaceEntry that = (ServiceInterfaceEntry) o;
+
+ if (!maybeBaseCache.equals(that.maybeBaseCache))
+ return false;
+ if (!nullableDescription.equals(that.nullableDescription))
+ return false;
+ if (!exportedOsgiClassName.equals(that.exportedOsgiClassName))
+ return false;
+ if (!qName.equals(that.qName))
+ return false;
+ if (!packageName.equals(that.packageName))
+ return false;
+ if (!typeName.equals(that.typeName))
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = maybeBaseCache.hashCode();
+ result = 31 * result + exportedOsgiClassName.hashCode();
+ result = 31 * result + nullableDescription.hashCode();
+ result = 31 * result + typeName.hashCode();
+ result = 31 * result + packageName.hashCode();
+ result = 31 * result + qName.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "ServiceInterfaceEntry{" + "maybeBaseCache=" + maybeBaseCache
+ + ", qName='" + qName + '\'' + ", fullyQualifiedName='"
+ + getFullyQualifiedName() + '\'' + ", exportedOsgiClassName="
+ + exportedOsgiClassName + ", nullableDescription='"
+ + nullableDescription + '\'' + '}';
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator;
+
+import org.opendaylight.yangtools.sal.binding.generator.spi.TypeProvider;
+import org.opendaylight.yangtools.sal.binding.model.api.Type;
+import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+
+public class TypeProviderWrapper {
+ private final TypeProvider typeProvider;
+
+ public TypeProviderWrapper(TypeProvider typeProvider) {
+ this.typeProvider = typeProvider;
+ }
+
+ public Type getType(LeafSchemaNode leaf) {
+ Type javaType;
+ try {
+ javaType = typeProvider.javaTypeForSchemaDefinitionType(
+ leaf.getType(), leaf);
+ if (javaType == null)
+ throw new IllegalArgumentException("Unknown type received for "
+ + leaf.toString());
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException("Error while resolving type of "
+ + leaf, e);
+ }
+ return javaType;
+ }
+
+ // there is no getType in common interface
+ public Type getType(LeafListSchemaNode leaf) {
+ Type javaType;
+ try {
+ javaType = typeProvider.javaTypeForSchemaDefinitionType(
+ leaf.getType(), leaf);
+ if (javaType == null)
+ throw new IllegalArgumentException(
+ "Unknown type received for " + leaf.toString());
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException("Error while resolving type of "
+ + leaf, e);
+ }
+ return javaType;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.attribute;
+
+import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntry;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+
+public abstract class AbstractAttribute implements AttributeIfc {
+ private final String attributeYangName, upperCaseCammelCase,
+ lowerCaseCammelCase;
+ private final DataSchemaNode node;
+
+ private static String getLocalName(DataSchemaNode attrNode) {
+ return attrNode.getQName().getLocalName();
+ }
+
+ AbstractAttribute(DataSchemaNode attrNode) {
+ this.attributeYangName = getLocalName(attrNode);
+ this.node = attrNode;
+ this.upperCaseCammelCase = ModuleMXBeanEntry.findJavaNamePrefix(node);
+ this.lowerCaseCammelCase = ModuleMXBeanEntry.findJavaParameter(node);
+ }
+
+ @Override
+ public String getAttributeYangName() {
+ return attributeYangName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (!(o instanceof AbstractAttribute))
+ return false;
+
+ AbstractAttribute that = (AbstractAttribute) o;
+
+ if (attributeYangName != null ? !attributeYangName
+ .equals(that.attributeYangName)
+ : that.attributeYangName != null)
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return attributeYangName != null ? attributeYangName.hashCode() : 0;
+ }
+
+ /**
+ *
+ * @return Yang name converted to cammel case, starting with a capital
+ * letter. For details see
+ * {@link ModuleMXBeanEntry#findJavaNamePrefix(org.opendaylight.yangtools.yang.model.api.SchemaNode)}
+ */
+ @Override
+ public String getUpperCaseCammelCase() {
+ return upperCaseCammelCase;
+ }
+
+ public String getLowerCaseCammelCase() {
+ return lowerCaseCammelCase;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.attribute;
+
+import javax.management.openmbean.OpenType;
+
+public interface AttributeIfc {
+
+ /**
+ * Name of attribute, starting with low case
+ */
+ String getAttributeYangName();
+
+ String getNullableDescription();
+
+ String getNullableDefault();
+
+ String getUpperCaseCammelCase();
+
+ String getLowerCaseCammelCase();
+
+ OpenType<?> getOpenType();
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.attribute;
+
+import javax.management.ObjectName;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+
+import org.opendaylight.controller.config.yangjmxgenerator.ServiceInterfaceEntry;
+import org.opendaylight.yangtools.binding.generator.util.Types;
+import org.opendaylight.yangtools.sal.binding.model.api.Type;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+
+public class DependencyAttribute extends AbstractAttribute implements
+ TypedAttribute {
+
+ private final Dependency dependency;
+ private final String nullableDescription, nullableDefault;
+
+ public DependencyAttribute(DataSchemaNode attrNode,
+ ServiceInterfaceEntry sie, boolean mandatory,
+ String nullableDescription) {
+ super(attrNode);
+ dependency = new Dependency(sie, mandatory);
+ this.nullableDescription = nullableDescription;
+ nullableDefault = null;
+ }
+
+ @Override
+ public Type getType() {
+ return Types.typeForClass(ObjectName.class);
+ }
+
+ public Dependency getDependency() {
+ return dependency;
+ }
+
+ @Override
+ public String getNullableDescription() {
+ return nullableDescription;
+ }
+
+ @Override
+ public String getNullableDefault() {
+ return nullableDefault;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ if (!super.equals(o))
+ return false;
+
+ DependencyAttribute that = (DependencyAttribute) o;
+
+ if (dependency != null ? !dependency.equals(that.dependency)
+ : that.dependency != null)
+ return false;
+ if (nullableDefault != null ? !nullableDefault
+ .equals(that.nullableDefault) : that.nullableDefault != null)
+ return false;
+ if (nullableDescription != null ? !nullableDescription
+ .equals(that.nullableDescription)
+ : that.nullableDescription != null)
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + (dependency != null ? dependency.hashCode() : 0);
+ result = 31
+ * result
+ + (nullableDescription != null ? nullableDescription.hashCode()
+ : 0);
+ result = 31 * result
+ + (nullableDefault != null ? nullableDefault.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "DependencyAttribute{" + getAttributeYangName() + ","
+ + "dependency=" + dependency + '}';
+ }
+
+ @Override
+ public OpenType<?> getOpenType() {
+ return SimpleType.OBJECTNAME;
+ }
+
+ public static class Dependency {
+ private final ServiceInterfaceEntry sie;
+ private final boolean mandatory;
+
+ public Dependency(ServiceInterfaceEntry sie, boolean mandatory) {
+ this.sie = sie;
+ this.mandatory = mandatory;
+ }
+
+ public ServiceInterfaceEntry getSie() {
+ return sie;
+ }
+
+ public boolean isMandatory() {
+ return mandatory;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ Dependency that = (Dependency) o;
+
+ if (mandatory != that.mandatory)
+ return false;
+ if (!sie.equals(that.sie))
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = sie.hashCode();
+ result = 31 * result + (mandatory ? 1 : 0);
+ return result;
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.attribute;
+
+import javax.management.openmbean.ArrayType;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+
+import org.opendaylight.controller.config.yangjmxgenerator.TypeProviderWrapper;
+import org.opendaylight.yangtools.sal.binding.model.api.Type;
+import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+
+public class JavaAttribute extends AbstractAttribute implements TypedAttribute {
+ private final Type type;
+ private final String nullableDescription, nullableDefault;
+
+ public JavaAttribute(LeafSchemaNode leaf,
+ TypeProviderWrapper typeProviderWrapper) {
+ super(leaf);
+ this.type = typeProviderWrapper.getType(leaf);
+ this.nullableDefault = leaf.getDefault();
+ this.nullableDescription = leaf.getDescription();
+ }
+
+ public JavaAttribute(LeafListSchemaNode leaf,
+ TypeProviderWrapper typeProviderWrapper) {
+ super(leaf);
+ this.type = typeProviderWrapper.getType(leaf);
+ this.nullableDefault = null;
+ this.nullableDescription = leaf.getDescription();
+ }
+
+ @Override
+ public Type getType() {
+ return type;
+ }
+
+ @Override
+ public String getNullableDescription() {
+ return nullableDescription;
+ }
+
+ @Override
+ public String getNullableDefault() {
+ return nullableDefault;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ if (!super.equals(o))
+ return false;
+
+ JavaAttribute that = (JavaAttribute) o;
+
+ if (nullableDefault != null ? !nullableDefault
+ .equals(that.nullableDefault) : that.nullableDefault != null)
+ return false;
+ if (nullableDescription != null ? !nullableDescription
+ .equals(that.nullableDescription)
+ : that.nullableDescription != null)
+ return false;
+ if (type != null ? !type.equals(that.type) : that.type != null)
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + (type != null ? type.hashCode() : 0);
+ result = 31
+ * result
+ + (nullableDescription != null ? nullableDescription.hashCode()
+ : 0);
+ result = 31 * result
+ + (nullableDefault != null ? nullableDefault.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "JavaAttribute{" + getAttributeYangName() + "," + "type=" + type
+ + '}';
+ }
+
+ @Override
+ public OpenType<?> getOpenType() {
+ // If is array => arrayType
+ if (isArray(getType())) {
+ String innerTypeFullyQName = getInnerType(getType());
+ SimpleType<?> innerSimpleType = SimpleTypeResolver
+ .getSimpleType(innerTypeFullyQName);
+ try {
+ ArrayType<Object> arrayType = isPrimitive(innerTypeFullyQName) ? new ArrayType<>(
+ innerSimpleType, true) : new ArrayType<>(1,
+ innerSimpleType);
+ return arrayType;
+ } catch (OpenDataException e) {
+ throw new RuntimeException("Unable to create "
+ + ArrayType.class + " with inner element of type "
+ + innerSimpleType, e);
+ }
+ }
+ // else simple type
+ SimpleType<?> simpleType = SimpleTypeResolver.getSimpleType(getType());
+ return simpleType;
+ }
+
+ // TODO verify
+ private boolean isPrimitive(String innerTypeFullyQName) {
+ if (innerTypeFullyQName.contains("."))
+ return false;
+
+ return true;
+ }
+
+ private static String getInnerType(Type type) {
+ String fullyQualifiedName = type.getFullyQualifiedName();
+ return fullyQualifiedName.substring(0, fullyQualifiedName.length() - 2);
+ }
+
+ private static boolean isArray(Type type) {
+ return type.getName().endsWith("[]");
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.attribute;
+
+import javax.management.openmbean.ArrayType;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.OpenType;
+
+import org.opendaylight.controller.config.yangjmxgenerator.TypeProviderWrapper;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+
+public class ListAttribute extends AbstractAttribute {
+
+ private final String nullableDescription, nullableDefault;
+ private final AttributeIfc innerAttribute;
+
+ public static ListAttribute create(ListSchemaNode node,
+ TypeProviderWrapper typeProvider) {
+
+ AttributeIfc innerAttribute = TOAttribute.create(node, typeProvider);
+
+ return new ListAttribute(node, innerAttribute, node.getDescription());
+ }
+
+ public static ListAttribute create(LeafListSchemaNode node,
+ TypeProviderWrapper typeProvider) {
+
+ AttributeIfc innerAttribute = new JavaAttribute(node, typeProvider);
+
+ return new ListAttribute(node, innerAttribute, node.getDescription());
+ }
+
+ ListAttribute(DataSchemaNode attrNode, AttributeIfc innerAttribute,
+ String description) {
+ super(attrNode);
+ this.nullableDescription = description;
+ this.innerAttribute = innerAttribute;
+ this.nullableDefault = null;
+ }
+
+ @Override
+ public String getNullableDescription() {
+ return nullableDescription;
+ }
+
+ @Override
+ public String getNullableDefault() {
+ return nullableDefault;
+ }
+
+ public AttributeIfc getInnerAttribute() {
+ return innerAttribute;
+ }
+
+ @Override
+ public String toString() {
+ return "ListAttribute{" + getAttributeYangName() + "," + "to="
+ + innerAttribute + '}';
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31
+ * result
+ + (nullableDescription != null ? nullableDescription.hashCode()
+ : 0);
+ result = 31 * result
+ + (nullableDefault != null ? nullableDefault.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ if (!super.equals(o))
+ return false;
+
+ ListAttribute that = (ListAttribute) o;
+
+ if (nullableDefault != null ? !nullableDefault
+ .equals(that.nullableDefault) : that.nullableDefault != null)
+ return false;
+ if (nullableDescription != null ? !nullableDescription
+ .equals(that.nullableDescription)
+ : that.nullableDescription != null)
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public ArrayType<?> getOpenType() {
+ OpenType<?> inerOpenType = innerAttribute.getOpenType();
+ try {
+ return new ArrayType<>(1, inerOpenType);
+ } catch (OpenDataException e) {
+ throw new RuntimeException("Unable to create " + ArrayType.class
+ + " with inner element of type " + inerOpenType, e);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.attribute;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Date;
+import java.util.Map;
+
+import javax.management.openmbean.SimpleType;
+
+import org.opendaylight.yangtools.sal.binding.model.api.Type;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Maps;
+
+public class SimpleTypeResolver {
+
+ public static SimpleType<?> getSimpleType(Type type) {
+ SimpleType<?> expectedSimpleType = JAVA_TYPE_TO_SIMPLE_TYPE.get(type
+ .getFullyQualifiedName());
+ Preconditions.checkState(expectedSimpleType != null,
+ "Cannot find simple type for " + type.getFullyQualifiedName());
+ return expectedSimpleType;
+ }
+
+ public static SimpleType<?> getSimpleType(String fullyQualifiedName) {
+ SimpleType<?> expectedSimpleType = JAVA_TYPE_TO_SIMPLE_TYPE
+ .get(fullyQualifiedName);
+ Preconditions.checkState(expectedSimpleType != null,
+ "Cannot find simple type for " + fullyQualifiedName);
+ return expectedSimpleType;
+ }
+
+ private static final Map<String, SimpleType<?>> JAVA_TYPE_TO_SIMPLE_TYPE = Maps
+ .newHashMap();
+ static {
+ // TODO add all
+ JAVA_TYPE_TO_SIMPLE_TYPE.put(Integer.class.getName(),
+ SimpleType.INTEGER);
+ JAVA_TYPE_TO_SIMPLE_TYPE.put(int.class.getName(), SimpleType.INTEGER);
+ JAVA_TYPE_TO_SIMPLE_TYPE.put(Short.class.getName(), SimpleType.SHORT);
+ JAVA_TYPE_TO_SIMPLE_TYPE.put(short.class.getName(), SimpleType.SHORT);
+ JAVA_TYPE_TO_SIMPLE_TYPE.put(Long.class.getName(), SimpleType.LONG);
+ JAVA_TYPE_TO_SIMPLE_TYPE.put(long.class.getName(), SimpleType.LONG);
+ JAVA_TYPE_TO_SIMPLE_TYPE.put(String.class.getName(), SimpleType.STRING);
+ JAVA_TYPE_TO_SIMPLE_TYPE.put(Boolean.class.getName(),
+ SimpleType.BOOLEAN);
+ JAVA_TYPE_TO_SIMPLE_TYPE.put(boolean.class.getName(),
+ SimpleType.BOOLEAN);
+ JAVA_TYPE_TO_SIMPLE_TYPE.put(BigInteger.class.getName(),
+ SimpleType.BIGINTEGER);
+ JAVA_TYPE_TO_SIMPLE_TYPE.put(BigDecimal.class.getName(),
+ SimpleType.BIGDECIMAL);
+ JAVA_TYPE_TO_SIMPLE_TYPE.put(Byte.class.getName(), SimpleType.BYTE);
+ JAVA_TYPE_TO_SIMPLE_TYPE.put(byte.class.getName(), SimpleType.BYTE);
+ JAVA_TYPE_TO_SIMPLE_TYPE.put(Date.class.getName(), SimpleType.DATE);
+ JAVA_TYPE_TO_SIMPLE_TYPE.put(Double.class.getName(), SimpleType.DOUBLE);
+ JAVA_TYPE_TO_SIMPLE_TYPE.put(double.class.getName(), SimpleType.DOUBLE);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.attribute;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.OpenType;
+
+import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntry;
+import org.opendaylight.controller.config.yangjmxgenerator.TypeProviderWrapper;
+import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+public class TOAttribute extends AbstractAttribute {
+
+ private final String nullableDescription, nullableDefault;
+ private final Map<String, AttributeIfc> yangNameToAttributeMap;
+ private final Map<String, String> attributeNameMap;
+
+ private static final Set<Class<? extends DataSchemaNode>> ALLOWED_CHILDREN = Sets
+ .newHashSet();
+ static {
+ ALLOWED_CHILDREN.add(LeafListSchemaNode.class);
+ ALLOWED_CHILDREN.add(ListSchemaNode.class);
+ ALLOWED_CHILDREN.add(LeafSchemaNode.class);
+ ALLOWED_CHILDREN.add(ContainerSchemaNode.class);
+ }
+
+ public static <T extends DataNodeContainer & AugmentationTarget & DataSchemaNode> TOAttribute create(
+ T containerSchemaNode, TypeProviderWrapper typeProviderWrapper) {
+ // Transfer Object: get the leaves
+ Map<String, AttributeIfc> map = new HashMap<>();
+ Map<String, String> attributeNameMap = new HashMap<>();
+ for (DataSchemaNode dataSchemaNode : containerSchemaNode
+ .getChildNodes()) {
+ try {
+ String yangName = dataSchemaNode.getQName().getLocalName();
+ map.put(yangName,
+ createInnerAttribute(dataSchemaNode,
+ typeProviderWrapper));
+ } catch (IllegalArgumentException e) {
+ throw new IllegalStateException("Unable to create TO");
+ }
+ }
+ return new TOAttribute(containerSchemaNode, map, attributeNameMap,
+ containerSchemaNode.getDescription());
+ }
+
+ private static AttributeIfc createInnerAttribute(
+ DataSchemaNode dataSchemaNode,
+ TypeProviderWrapper typeProviderWrapper) {
+ Class<? extends DataSchemaNode> type = isAllowedType(dataSchemaNode);
+
+ if (type.equals(LeafSchemaNode.class))
+ return new JavaAttribute((LeafSchemaNode) dataSchemaNode,
+ typeProviderWrapper);
+ else if (type.equals(ListSchemaNode.class))
+ return ListAttribute.create((ListSchemaNode) dataSchemaNode,
+ typeProviderWrapper);
+ else if (type.equals(LeafListSchemaNode.class))
+ return ListAttribute.create((LeafListSchemaNode) dataSchemaNode,
+ typeProviderWrapper);
+ else if (type.equals(ContainerSchemaNode.class))
+ return TOAttribute.create((ContainerSchemaNode) dataSchemaNode,
+ typeProviderWrapper);
+
+ throw new IllegalStateException("This should never happen");
+ }
+
+ private static Class<? extends DataSchemaNode> isAllowedType(
+ DataSchemaNode dataSchemaNode) {
+ for (Class<? extends DataSchemaNode> allowedType : ALLOWED_CHILDREN) {
+ if (allowedType.isAssignableFrom(dataSchemaNode.getClass()) == true)
+ return allowedType;
+ }
+ throw new IllegalArgumentException("Illegal child node for TO: "
+ + dataSchemaNode.getClass() + " allowed node types: "
+ + ALLOWED_CHILDREN);
+ }
+
+ private TOAttribute(DataSchemaNode attrNode,
+ Map<String, AttributeIfc> transferObject,
+ Map<String, String> attributeNameMap, String nullableDescription) {
+ super(attrNode);
+ yangNameToAttributeMap = transferObject;
+ this.attributeNameMap = attributeNameMap;
+ this.nullableDescription = nullableDescription;
+ nullableDefault = null;
+ }
+
+ public Map<String, String> getAttributeNameMap() {
+ return attributeNameMap;
+ }
+
+ public Map<String, AttributeIfc> getCapitalizedPropertiesToTypesMap() {
+ Map<String, AttributeIfc> capitalizedPropertiesToTypesMap = Maps
+ .newHashMap();
+ for (Entry<String, AttributeIfc> entry : yangNameToAttributeMap
+ .entrySet()) {
+
+ capitalizedPropertiesToTypesMap.put(
+ ModuleMXBeanEntry.convertToJavaName(entry.getKey(), true),
+ entry.getValue());
+ }
+ return capitalizedPropertiesToTypesMap;
+ }
+
+ public Map<String, AttributeIfc> getJmxPropertiesToTypesMap() {
+ Map<String, AttributeIfc> jmxPropertiesToTypesMap = Maps.newHashMap();
+ for (Entry<String, AttributeIfc> entry : yangNameToAttributeMap
+ .entrySet()) {
+
+ jmxPropertiesToTypesMap.put(
+ ModuleMXBeanEntry.convertToJavaName(entry.getKey(), false),
+ entry.getValue());
+ }
+ return jmxPropertiesToTypesMap;
+ }
+
+ public Map<String, AttributeIfc> getYangPropertiesToTypesMap() {
+ return yangNameToAttributeMap;
+ }
+
+ @Override
+ public String getNullableDescription() {
+ return nullableDescription;
+ }
+
+ @Override
+ public String getNullableDefault() {
+ return nullableDefault;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ if (!super.equals(o))
+ return false;
+
+ TOAttribute that = (TOAttribute) o;
+
+ if (nullableDefault != null ? !nullableDefault
+ .equals(that.nullableDefault) : that.nullableDefault != null)
+ return false;
+ if (nullableDescription != null ? !nullableDescription
+ .equals(that.nullableDescription)
+ : that.nullableDescription != null)
+ return false;
+ if (yangNameToAttributeMap != null ? !yangNameToAttributeMap
+ .equals(that.yangNameToAttributeMap)
+ : that.yangNameToAttributeMap != null)
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31
+ * result
+ + (nullableDescription != null ? nullableDescription.hashCode()
+ : 0);
+ result = 31 * result
+ + (nullableDefault != null ? nullableDefault.hashCode() : 0);
+ result = 31
+ * result
+ + (yangNameToAttributeMap != null ? yangNameToAttributeMap
+ .hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "TOAttribute{" + getAttributeYangName() + "," + "to="
+ + yangNameToAttributeMap + '}';
+ }
+
+ @Override
+ public OpenType<?> getOpenType() {
+ String description = getNullableDescription() == null ? getAttributeYangName()
+ : getNullableDescription();
+ final String[] itemNames = new String[yangNameToAttributeMap.keySet()
+ .size()];
+ String[] itemDescriptions = itemNames;
+ FunctionImpl functionImpl = new FunctionImpl(itemNames);
+ Map<String, AttributeIfc> jmxPropertiesToTypesMap = getJmxPropertiesToTypesMap();
+ OpenType<?>[] itemTypes = Collections2.transform(
+ jmxPropertiesToTypesMap.entrySet(), functionImpl).toArray(
+ new OpenType<?>[] {});
+ try {
+ // TODO add package name to create fully qualified name for this
+ // type
+ CompositeType compositeType = new CompositeType(
+ getUpperCaseCammelCase(), description, itemNames,
+ itemDescriptions, itemTypes);
+ return compositeType;
+ } catch (OpenDataException e) {
+ throw new RuntimeException("Unable to create CompositeType for "
+ + this, e);
+ }
+ }
+
+ private static final class FunctionImpl implements
+ Function<Entry<String, AttributeIfc>, OpenType<?>> {
+ private final String[] itemNames;
+ int i = 0;
+
+ private FunctionImpl(String[] itemNames) {
+ this.itemNames = itemNames;
+ }
+
+ @Override
+ public OpenType<?> apply(Entry<String, AttributeIfc> input) {
+ AttributeIfc innerType = input.getValue();
+ itemNames[i++] = input.getKey();
+ return innerType.getOpenType();
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.attribute;
+
+import org.opendaylight.yangtools.sal.binding.model.api.Type;
+
+public interface TypedAttribute extends AttributeIfc {
+
+ Type getType();
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.attribute;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+final class Util {
+
+ /**
+ * Used for date <-> xml serialization
+ */
+ private static final SimpleDateFormat dateFormat = new SimpleDateFormat(
+ "yyyy-MM-dd");
+
+ public static String writeDate(Date date) {
+ return dateFormat.format(date);
+ }
+
+ public static Date readDate(String s) throws ParseException {
+ return dateFormat.parse(s);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.util;
+
+public class FullyQualifiedNameHelper {
+ public static String getFullyQualifiedName(String packageName,
+ String className) {
+ if (packageName.isEmpty())
+ return className;
+ return packageName + "." + className;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.util;
+
+import org.opendaylight.yangtools.yang.common.QName;
+
+public class NameConflictException extends RuntimeException {
+
+ private static final String messageBlueprint = "Name conflict for name: %s, first defined in: %s, then defined in: %s";
+ private final String conflictingName;
+ private final QName secondParentQName;
+ private final QName firstParentQName;
+
+ public NameConflictException(String conflictingName,
+ QName firstDefinedParentQName, QName secondDefinedParentQName) {
+ super(String.format(messageBlueprint, conflictingName,
+ firstDefinedParentQName, secondDefinedParentQName));
+ this.conflictingName = conflictingName;
+ this.firstParentQName = firstDefinedParentQName;
+ this.secondParentQName = secondDefinedParentQName;
+ }
+
+ // TODO add yang local names
+
+ public String getConflictingName() {
+ return conflictingName;
+ }
+
+ public QName getSecondParentQName() {
+ return secondParentQName;
+ }
+
+ public QName getFirstParentQName() {
+ return firstParentQName;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator;
+
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.format;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.junit.Before;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.util.YangModelSearchUtils;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl;
+
+import com.google.common.base.Preconditions;
+
+public abstract class AbstractYangTest {
+ protected SchemaContext context;
+ protected Map<String, Module> namesToModules; // are module names globally
+ // unique?
+ protected Module configModule, rpcContextModule, threadsModule,
+ threadsJavaModule, bgpListenerJavaModule, ietfInetTypesModule,
+ jmxModule, jmxImplModule, testFilesModule, testFiles1Module;
+
+ @Before
+ public void loadYangFiles() throws Exception {
+ List<InputStream> yangISs = new ArrayList<>();
+ yangISs.addAll(getStreams("/test-config-threads.yang",
+ "/test-config-threads-java.yang",
+ "/config-bgp-listener-impl.yang", "/ietf-inet-types.yang",
+ "/config-jmx-it.yang", "/config-jmx-it-impl.yang",
+ "/test-config-files.yang", "/test-config-files1.yang"));
+
+ yangISs.addAll(getConfigApiYangInputStreams());
+
+ YangParserImpl parser = new YangParserImpl();
+ Set<Module> modulesToBuild = parser.parseYangModelsFromStreams(yangISs);
+ // close ISs
+ for (InputStream is : yangISs) {
+ is.close();
+ }
+ context = parser.resolveSchemaContext(modulesToBuild);
+ namesToModules = YangModelSearchUtils.mapModulesByNames(context
+ .getModules());
+ configModule = namesToModules.get(ConfigConstants.CONFIG_MODULE);
+ rpcContextModule = namesToModules.get(ConfigConstants.CONFIG_MODULE);
+ threadsModule = namesToModules
+ .get(ConfigConstants.CONFIG_THREADS_MODULE);
+ threadsJavaModule = namesToModules.get("config-threads-java");
+ bgpListenerJavaModule = namesToModules.get("config-bgp-listener-impl");
+ ietfInetTypesModule = namesToModules
+ .get(ConfigConstants.IETF_INET_TYPES);
+ jmxModule = namesToModules.get("config-jmx-it");
+ jmxImplModule = namesToModules.get("config-jmx-it-impl");
+ testFilesModule = namesToModules.get("test-config-files");
+ testFiles1Module = namesToModules.get("test-config-files1");
+
+ }
+
+ public static List<InputStream> getConfigApiYangInputStreams() {
+ return getStreams("/META-INF/yang/config.yang",
+ "/META-INF/yang/rpc-context.yang");
+ }
+
+ public Map<QName, IdentitySchemaNode> mapIdentitiesByQNames(Module module) {
+ Map<QName, IdentitySchemaNode> result = new HashMap<>();
+ for (IdentitySchemaNode identitySchemaNode : module.getIdentities()) {
+ QName qName = identitySchemaNode.getQName();
+ Preconditions.checkArgument(
+ result.containsKey(qName) == false,
+ format("Two identities of %s contain same " + "qname %s",
+ module, qName));
+ result.put(qName, identitySchemaNode);
+ }
+ return result;
+ }
+
+ protected static List<InputStream> getStreams(String... paths) {
+ List<InputStream> result = new ArrayList<>();
+ for (String path : paths) {
+ InputStream is = AbstractYangTest.class.getResourceAsStream(path);
+ assertNotNull(path + " is null", is);
+ result.add(is);
+ }
+ return result;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.junit.Test;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.util.NameConflictException;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.util.YangModelSearchUtils;
+import org.opendaylight.yangtools.sal.binding.yang.types.TypeProviderImpl;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Preconditions;
+
+public class ModuleMXBeanEntryNameConflictTest extends AbstractYangTest {
+
+ private static final Logger logger = LoggerFactory
+ .getLogger(ModuleMXBeanEntryNameConflictTest.class);
+
+ public static final String PACKAGE_NAME = "pack2";
+ Map<File, String> testedFilesToYangModules = new HashMap<>();
+ Map<String, String> testedYangModulesToExpectedConflictingName = new HashMap<>();
+
+ @Test
+ public void testNameConflicts() throws Exception {
+ prepareSamples();
+ prepareExceptionAssertions();
+
+ for (Map.Entry<File, String> currentTestEntry : testedFilesToYangModules
+ .entrySet()) {
+ final String moduleName = currentTestEntry.getValue();
+ final File yangFile = currentTestEntry.getKey();
+ Module testedModule = loadYangs(yangFile, moduleName);
+
+ try {
+ logger.debug("Testing {}", yangFile);
+ ModuleMXBeanEntry.create(testedModule,
+ new HashMap<QName, ServiceInterfaceEntry>(), context,
+ new TypeProviderWrapper(new TypeProviderImpl(context)),
+ PACKAGE_NAME);
+ fail(yangFile.toString()
+ + " did not cause a name conflict and should");
+ } catch (NameConflictException e) {
+ assertEquals(
+ testedYangModulesToExpectedConflictingName
+ .get(moduleName),
+ e.getConflictingName());
+ }
+ }
+ }
+
+ private void prepareSamples() {
+ File first = new File(getClass().getResource(
+ "/duplicates/config-test-duplicate-attribute-in-list.yang")
+ .getFile());
+ File dir = first.getParentFile();
+
+ for (File testYang : dir.listFiles()) {
+ String moduleName = getYangModuleName(testYang.getName());
+ testedFilesToYangModules.put(testYang, moduleName);
+ }
+ }
+
+ private void prepareExceptionAssertions() {
+ testedYangModulesToExpectedConflictingName.put(
+ "config-test-duplicate-attribute", "DtoA");
+ testedYangModulesToExpectedConflictingName.put(
+ "config-test-duplicate-attribute-in-list", "DtoA");
+ testedYangModulesToExpectedConflictingName.put(
+ "config-test-duplicate-attribute-runtime-bean", "DtoA");
+ testedYangModulesToExpectedConflictingName.put(
+ "config-test-generated-attributes-name-conflict", "StateB");
+ testedYangModulesToExpectedConflictingName.put(
+ "config-test-runtime-bean-list-name-conflict",
+ "StateARuntimeMXBean");
+ testedYangModulesToExpectedConflictingName.put(
+ "config-test-runtime-bean-list-name-conflict2",
+ "StateARuntimeMXBean");
+ testedYangModulesToExpectedConflictingName
+ .put("config-test-runtime-bean-name-conflict", "StateARuntimeMXBean");
+ testedYangModulesToExpectedConflictingName.put(
+ "config-test-runtime-bean-name-conflict2",
+ "StateARuntimeMXBean");
+ }
+
+ private String getYangModuleName(String name) {
+ int startIndex = 0;
+ int endIndex = name.indexOf(".yang");
+ return name.substring(startIndex, endIndex);
+ }
+
+ private Module loadYangs(File testedModule, String moduleName)
+ throws Exception {
+ List<InputStream> yangISs = new ArrayList<>();
+ yangISs.addAll(getStreams("/ietf-inet-types.yang"));
+
+ yangISs.add(new FileInputStream(testedModule));
+
+ yangISs.addAll(getConfigApiYangInputStreams());
+
+ YangParserImpl parser = new YangParserImpl();
+ Set<Module> modulesToBuild = parser.parseYangModelsFromStreams(yangISs);
+ // close ISs
+ for (InputStream is : yangISs) {
+ is.close();
+ }
+ context = parser.resolveSchemaContext(modulesToBuild);
+ namesToModules = YangModelSearchUtils.mapModulesByNames(context
+ .getModules());
+ configModule = namesToModules.get(ConfigConstants.CONFIG_MODULE);
+ final Module module = namesToModules.get(moduleName);
+ Preconditions.checkNotNull(module, "Cannot get module %s from %s",
+ moduleName, namesToModules.keySet());
+ return module;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+
+import javax.management.openmbean.ArrayType;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.SimpleType;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.config.yangjmxgenerator.attribute.AttributeIfc;
+import org.opendaylight.controller.config.yangjmxgenerator.attribute.DependencyAttribute;
+import org.opendaylight.controller.config.yangjmxgenerator.attribute.JavaAttribute;
+import org.opendaylight.controller.config.yangjmxgenerator.attribute.ListAttribute;
+import org.opendaylight.controller.config.yangjmxgenerator.attribute.TOAttribute;
+import org.opendaylight.controller.config.yangjmxgenerator.attribute.TypedAttribute;
+import org.opendaylight.yangtools.binding.generator.util.Types;
+import org.opendaylight.yangtools.sal.binding.model.api.Type;
+import org.opendaylight.yangtools.sal.binding.yang.types.TypeProviderImpl;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.RevisionAwareXPath;
+
+import com.google.common.collect.Sets;
+
+public class ModuleMXBeanEntryTest extends AbstractYangTest {
+ public static final String EVENTBUS_MXB_NAME = "eventbus";
+ public static final String ASYNC_EVENTBUS_MXB_NAME = "async-eventbus";
+ public static final String THREADFACTORY_NAMING_MXB_NAME = "threadfactory-naming";
+ public static final String THREADPOOL_DYNAMIC_MXB_NAME = "threadpool-dynamic";
+
+ public static final String BGP_LISTENER_IMPL_MXB_NAME = "bgp-listener-impl";
+
+ public static final String PACKAGE_NAME = "pack2";
+
+ protected static final URI THREADS_NAMESPACE;
+ protected static final Date THREADS_REVISION_DATE;
+
+ static {
+ try {
+ THREADS_NAMESPACE = new URI(ConfigConstants.CONFIG_NAMESPACE
+ + ":threads");
+ } catch (URISyntaxException e) {
+ throw new Error(e);
+ }
+ SimpleDateFormat revisionFormat = new SimpleDateFormat("yyyy-MM-dd");
+ try {
+ THREADS_REVISION_DATE = revisionFormat.parse("2013-04-09");
+ } catch (ParseException e) {
+ throw new Error(e);
+ }
+ }
+
+ protected Map<QName, ServiceInterfaceEntry> modulesToSIEs;
+
+ protected Map<String /* identity local name */, ModuleMXBeanEntry> loadThreadsJava() {
+ Map<String /* identity local name */, ModuleMXBeanEntry> namesToMBEs = ModuleMXBeanEntry
+ .create(threadsJavaModule, modulesToSIEs, context, new TypeProviderWrapper(new TypeProviderImpl
+ (context)), PACKAGE_NAME);
+ assertNotNull(namesToMBEs);
+ Set<String> expectedMXBeanNames = Sets.newHashSet(EVENTBUS_MXB_NAME,
+ ASYNC_EVENTBUS_MXB_NAME, THREADFACTORY_NAMING_MXB_NAME,
+ THREADPOOL_DYNAMIC_MXB_NAME);
+ assertThat(namesToMBEs.keySet(), is(expectedMXBeanNames));
+ return namesToMBEs;
+ }
+
+ @Before
+ public void setUp() {
+ modulesToSIEs = ServiceInterfaceEntry.create(threadsModule,
+ "packages.sis");
+ }
+
+ @Test
+ public void test_jmxImplModule() {
+ Map<QName, ServiceInterfaceEntry> modulesToSIEs = ServiceInterfaceEntry
+ .create(threadsModule, PACKAGE_NAME);
+ modulesToSIEs.putAll(ServiceInterfaceEntry.create(jmxModule,
+ PACKAGE_NAME));
+ Map<String /* identity local name */, ModuleMXBeanEntry> namesToMBEs = ModuleMXBeanEntry
+ .create(jmxImplModule, modulesToSIEs, context, new TypeProviderWrapper(new TypeProviderImpl(context))
+ , PACKAGE_NAME);
+ Map<String, AttributeIfc> attributes = namesToMBEs.get("impl-netconf")
+ .getAttributes();
+ //
+ DependencyAttribute threadFactoryAttribute = (DependencyAttribute) attributes
+ .get("thread-factory");
+ assertNotNull(threadFactoryAttribute);
+ assertFalse(threadFactoryAttribute.getDependency().isMandatory());
+ assertThat(threadFactoryAttribute.getDependency().getSie()
+ .getTypeName(), is("ThreadFactoryServiceInterface"));
+ assertThat(threadFactoryAttribute.getAttributeYangName(),
+ is("thread-factory"));
+ assertThat(threadFactoryAttribute.getLowerCaseCammelCase(),
+ is("threadFactory"));
+ assertThat(threadFactoryAttribute.getUpperCaseCammelCase(),
+ is("ThreadFactory"));
+ assertThat(threadFactoryAttribute.getOpenType(), is(SimpleType.class));
+ assertNull(threadFactoryAttribute.getNullableDefault());
+ assertNull(threadFactoryAttribute.getNullableDescription());
+ assertThat(threadFactoryAttribute.getType().getName(), is("ObjectName"));
+ }
+
+ protected RuntimeBeanEntry findFirstByYangName(
+ Collection<RuntimeBeanEntry> runtimeBeans, String yangName) {
+ for (RuntimeBeanEntry rb : runtimeBeans) {
+ if (yangName.equals(rb.getYangName()))
+ return rb;
+ }
+ throw new IllegalArgumentException("Yang name not found:" + yangName
+ + " in " + runtimeBeans);
+ }
+
+ @Test
+ public void testGetWhenConditionMatcher() {
+ assertMatches("config",
+ "/config:modules/config:module/config:type = 'threadpool-dynamic'");
+ assertMatches("ns",
+ "/ns:modules/ns:module/ns:type = 'threadpool-dynamic'");
+ assertMatches("config",
+ "/config:modules/config:module/config:type=\"threadpool-dynamic\"");
+ }
+
+ private void assertMatches(String prefix, String input) {
+ RevisionAwareXPath whenConstraint = mock(RevisionAwareXPath.class);
+ doReturn(input).when(whenConstraint).toString();
+ Matcher output = ModuleMXBeanEntry.getWhenConditionMatcher(prefix,
+ whenConstraint);
+ assertTrue(output.matches());
+ assertEquals("threadpool-dynamic", output.group(1));
+ }
+
+ @Test
+ public void testThreadsJava() {
+ Map<String /* identity local name */, ModuleMXBeanEntry> namesToMBEs = loadThreadsJava();
+
+ { // check threadpool-dynamic
+ ModuleMXBeanEntry dynamicThreadPool = namesToMBEs
+ .get(THREADPOOL_DYNAMIC_MXB_NAME);
+ Map<String, AttributeIfc> attributes = dynamicThreadPool
+ .getAttributes();
+ // core-size, keepalive, maximum-size
+ // threadfactory
+ Set<String> longAttribs = Sets.newHashSet("core-size",
+ "maximum-size");
+ for (String longAttrib : longAttribs) {
+
+ TypedAttribute attribute = (TypedAttribute) attributes
+ .get(longAttrib);
+ assertThat("Failed to check " + longAttrib,
+ attribute.getType(),
+ is((Type) Types.typeForClass(Long.class)));
+ }
+ // check dependency on thread factory
+ QName threadfactoryQName = new QName(THREADS_NAMESPACE,
+ THREADS_REVISION_DATE, "threadfactory");
+ ServiceInterfaceEntry threadFactorySIEntry = modulesToSIEs
+ .get(threadfactoryQName);
+ assertNotNull(threadFactorySIEntry);
+ boolean expectedMandatory = true;
+ TypedAttribute actualThreadFactory = (TypedAttribute) attributes
+ .get("threadfactory");
+
+ DataSchemaNode mockedDataSchemaNode = mock(DataSchemaNode.class);
+ doReturn(Collections.emptyList()).when(mockedDataSchemaNode)
+ .getUnknownSchemaNodes();
+ doReturn(threadfactoryQName).when(mockedDataSchemaNode).getQName();
+ AttributeIfc expectedDependencyAttribute = new DependencyAttribute(
+ mockedDataSchemaNode, threadFactorySIEntry,
+ expectedMandatory, "threadfactory description");
+ assertThat(actualThreadFactory, is(expectedDependencyAttribute));
+ assertThat(
+ dynamicThreadPool
+ .getFullyQualifiedName("DynamicThreadPoolModuleMXBean"),
+ is(PACKAGE_NAME + ".DynamicThreadPoolModuleMXBean"));
+ assertThat(dynamicThreadPool.getNullableDescription(),
+ is("threadpool-dynamic description"));
+ assertThat(dynamicThreadPool.getYangModuleName(),
+ is("config-threads-java"));
+ assertThat(dynamicThreadPool.getYangModuleLocalname(),
+ is(THREADPOOL_DYNAMIC_MXB_NAME));
+
+ // check root runtime bean
+ Collection<RuntimeBeanEntry> runtimeBeans = dynamicThreadPool
+ .getRuntimeBeans();
+ assertThat(runtimeBeans.size(), is(1));
+ RuntimeBeanEntry rootRB = findFirstByYangName(runtimeBeans,
+ THREADPOOL_DYNAMIC_MXB_NAME);
+ assertThat(rootRB.isRoot(), is(true));
+ assertThat(rootRB.getAttributes().size(), is(1));
+ JavaAttribute attribute = (JavaAttribute) rootRB.getAttributes()
+ .iterator().next();
+ assertThat(attribute.getAttributeYangName(), is("created-sessions"));
+ assertThat(rootRB.getYangName(), is(THREADPOOL_DYNAMIC_MXB_NAME));
+ assertThat(attribute.getType().getFullyQualifiedName(),
+ is(Long.class.getName()));
+ }
+ {// check threadfactory-naming
+ ModuleMXBeanEntry threadFactoryNaming = namesToMBEs
+ .get(THREADFACTORY_NAMING_MXB_NAME);
+ Collection<RuntimeBeanEntry> runtimeBeans = threadFactoryNaming
+ .getRuntimeBeans();
+ assertThat(runtimeBeans.size(), is(4));
+ {
+ RuntimeBeanEntry threadRB = findFirstByYangName(runtimeBeans,
+ "thread");
+ assertNotNull(threadRB);
+ assertFalse(threadRB.isRoot());
+ assertEquals("name", threadRB.getKeyYangName().get());
+ assertEquals("Name", threadRB.getKeyJavaName().get());
+ assertThat(threadRB.getAttributes().size(), is(1));
+ AttributeIfc threadNameAttr = threadRB.getAttributes()
+ .iterator().next();
+ assertThat(threadNameAttr.getAttributeYangName(), is("name"));
+ assertTrue(threadNameAttr instanceof JavaAttribute);
+ assertThat(((JavaAttribute) threadNameAttr).getType()
+ .getFullyQualifiedName(), is(String.class.getName()));
+ assertThat(threadRB.getRpcs().size(), is(2));
+ }
+ {
+ RuntimeBeanEntry streamRB = findFirstByYangName(runtimeBeans,
+ "stream");
+ assertNotNull(streamRB);
+ assertFalse(streamRB.getKeyYangName().isPresent());
+ assertFalse(streamRB.getKeyJavaName().isPresent());
+ Map<String, AttributeIfc> attributeMap = streamRB
+ .getYangPropertiesToTypesMap();
+ assertEquals(4, attributeMap.size());
+
+ TOAttribute toAttr = (TOAttribute) attributeMap.get("peer");
+ assertNotNull(toAttr);
+ assertThat(toAttr.getAttributeYangName(), is("peer"));
+ assertThat(toAttr.getLowerCaseCammelCase(), is("peer"));
+ assertThat(toAttr.getUpperCaseCammelCase(), is("Peer"));
+ assertThat(toAttr.getOpenType(), is(CompositeType.class));
+ Set<String> propsExpected = new HashSet<String>(2);
+ propsExpected.add("port");
+ propsExpected.add("core-size");
+ assertThat(toAttr.getYangPropertiesToTypesMap().keySet(),
+ is(propsExpected));
+ propsExpected = new HashSet<String>(2);
+ propsExpected.add("Port");
+ propsExpected.add("CoreSize");
+ assertThat(
+ toAttr.getCapitalizedPropertiesToTypesMap().keySet(),
+ is(propsExpected));
+ propsExpected = new HashSet<String>(2);
+ propsExpected.add("port");
+ propsExpected.add("coreSize");
+ assertThat(toAttr.getJmxPropertiesToTypesMap().keySet(),
+ is(propsExpected));
+
+ JavaAttribute timestampAttr = (JavaAttribute) attributeMap
+ .get("timestamp");
+ assertNotNull(timestampAttr);
+
+ JavaAttribute stateAttr = (JavaAttribute) attributeMap
+ .get("state");
+ assertNotNull(stateAttr);
+
+ ListAttribute innerStream = (ListAttribute) attributeMap
+ .get("inner-stream-list");
+ assertNotNull(innerStream);
+ assertThat(innerStream.getAttributeYangName(),
+ is("inner-stream-list"));
+ assertThat(innerStream.getLowerCaseCammelCase(),
+ is("innerStreamList"));
+ assertThat(innerStream.getUpperCaseCammelCase(),
+ is("InnerStreamList"));
+ assertThat(innerStream.getOpenType(), is(ArrayType.class));
+
+ }
+
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import java.net.URI;
+import java.util.Map;
+
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.model.api.Module;
+
+import com.google.common.collect.Maps;
+
+public class PackageTranslatorTest {
+ public static final String EXPECTED_PACKAGE_PREFIX = "org.opendaylight.controller.config";
+
+ @Test
+ public void test() throws Exception {
+ Map<String, String> map = Maps.newHashMap();
+ map.put(ConfigConstants.CONFIG_NAMESPACE, EXPECTED_PACKAGE_PREFIX);
+ PackageTranslator tested = new PackageTranslator(map);
+ Module module = mock(Module.class);
+ doReturn(new URI(ConfigConstants.CONFIG_NAMESPACE + ":threads:api"))
+ .when(module).getNamespace();
+ assertEquals(EXPECTED_PACKAGE_PREFIX + ".threads.api",
+ tested.getPackageName(module));
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.doReturn;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+import javax.management.openmbean.SimpleType;
+
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.opendaylight.controller.config.yangjmxgenerator.attribute.JavaAttribute;
+import org.opendaylight.yangtools.sal.binding.yang.types.TypeProviderImpl;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
+
+public class RuntimeBeanEntryTest extends AbstractYangTest {
+
+ public static final String PACKAGE_NAME = "packages.sis";
+ public static final String THREADFACTORY_NAMING_MXB_NAME = "threadfactory-naming";
+ public static final String THREAD_RUNTIME_BEAN_JAVA_NAME = "ThreadRuntimeMXBean";
+ public static final String THREAD_RUNTIME_BEAN_JAVA_PREFIX = "Thread";
+ public static final String THREAD_RUNTIME_BEAN_YANG_NAME = "thread";
+ public static final String SLEEP_RPC_NAME = "sleep";
+ public static final String SLEEP_RPC_OUTPUT = "ThreadState";
+ public static final String SLEEP_RPC_INPUT_NAME = "millis";
+ public static final String SLEEP_RPC_INPUT_TYPE = "Long";
+
+ @Test
+ public void createRuntimeBean() {
+ ChoiceCaseNode caseNode = Mockito.mock(ChoiceCaseNode.class);
+ doReturn(new HashSet<LeafSchemaNode>()).when(caseNode).getChildNodes();
+ doReturn(new ArrayList<UnknownSchemaNode>()).when(caseNode)
+ .getUnknownSchemaNodes();
+ Map<String, RuntimeBeanEntry> runtimeBeans = RuntimeBeanEntry
+ .extractClassNameToRuntimeBeanMap(PACKAGE_NAME, caseNode, "test-name", new TypeProviderWrapper(new
+ TypeProviderImpl(context)), "test", jmxImplModule);
+ assertThat(runtimeBeans.size(), is(1));
+ RuntimeBeanEntry runtimeMXBean = runtimeBeans.get("testRuntimeMXBean");
+ assertThat(runtimeMXBean.isRoot(), is(true));
+ assertThat(runtimeMXBean.getYangName(), is("test-name"));
+ }
+
+ @Test
+ public void runtimeBeanRPCTest() {
+ // create service interfaces
+ Map<QName, ServiceInterfaceEntry> modulesToSIEs = ServiceInterfaceEntry
+ .create(threadsModule, "packages.sis");
+ assertNotNull(modulesToSIEs);
+
+ // create MXBeans map
+ Map<String, ModuleMXBeanEntry> namesToMBEs = ModuleMXBeanEntry.create(
+ threadsJavaModule, modulesToSIEs, context,
+ new TypeProviderWrapper(new TypeProviderImpl(context)),
+ PACKAGE_NAME);
+ assertThat(namesToMBEs.isEmpty(), is(false));
+
+ // get threadfactory-naming bean
+ ModuleMXBeanEntry threadfactoryNamingMXBean = namesToMBEs
+ .get(THREADFACTORY_NAMING_MXB_NAME);
+ assertNotNull(threadfactoryNamingMXBean);
+
+ // get runtime beans
+ Collection<RuntimeBeanEntry> runtimeBeanEntries = threadfactoryNamingMXBean
+ .getRuntimeBeans();
+ assertThat(runtimeBeanEntries.isEmpty(), is(false));
+
+ // get root runtime bean
+ RuntimeBeanEntry threadfactoryRuntimeBeanEntry = getRuntimeBeanEntryByJavaName(
+ runtimeBeanEntries, "NamingThreadFactoryRuntimeMXBean");
+ assertNotNull(threadfactoryRuntimeBeanEntry);
+ assertThat(threadfactoryRuntimeBeanEntry.isRoot(), is(true));
+
+ // get thread runtime bean
+ RuntimeBeanEntry runtimeBeanEntry = getRuntimeBeanEntryByJavaName(
+ runtimeBeanEntries, THREAD_RUNTIME_BEAN_JAVA_NAME);
+ assertNotNull(runtimeBeanEntry);
+
+ // test thread runtime bean properties
+ assertThat(runtimeBeanEntry.getJavaNamePrefix(),
+ is(THREAD_RUNTIME_BEAN_JAVA_PREFIX));
+ assertThat(runtimeBeanEntry.getPackageName(), is(PACKAGE_NAME));
+ assertThat(runtimeBeanEntry.getFullyQualifiedName(runtimeBeanEntry
+ .getJavaNameOfRuntimeMXBean()), is(PACKAGE_NAME + "."
+ + THREAD_RUNTIME_BEAN_JAVA_NAME));
+ assertThat(runtimeBeanEntry.getYangName(),
+ is(THREAD_RUNTIME_BEAN_YANG_NAME));
+
+ // get thread runtime bean rpcs
+ List<RuntimeBeanEntry.Rpc> rpcs = new ArrayList<RuntimeBeanEntry.Rpc>(
+ runtimeBeanEntry.getRpcs());
+ assertThat(rpcs.size(), is(2));
+
+ // get sleep rpc and test it
+ RuntimeBeanEntry.Rpc rpc = getRpcByName(rpcs, SLEEP_RPC_NAME);
+ assertNotNull(rpc);
+ assertThat(rpc.getYangName(), is(SLEEP_RPC_NAME));
+ assertThat(rpc.getReturnType().endsWith(SLEEP_RPC_OUTPUT), is(true));
+
+ // get sleep rpc input attribute and test it
+ List<JavaAttribute> attributes = rpc.getParameters();
+ assertThat(attributes.size(), is(1));
+ JavaAttribute attribute = attributes.get(0);
+ assertThat(attribute.getAttributeYangName(), is(SLEEP_RPC_INPUT_NAME));
+ assertThat(attribute.getType().getName(), is(SLEEP_RPC_INPUT_TYPE));
+ assertThat(attribute.getLowerCaseCammelCase(), is(SLEEP_RPC_INPUT_NAME));
+ assertThat(attribute.getUpperCaseCammelCase(), is("Millis"));
+ assertNull(attribute.getNullableDefault());
+ assertNull(attribute.getNullableDescription());
+ assertThat(attribute.getOpenType(), is(SimpleType.class));
+ }
+
+ private RuntimeBeanEntry getRuntimeBeanEntryByJavaName(
+ final Collection<RuntimeBeanEntry> runtimeBeanEntries,
+ String javaName) {
+ if (runtimeBeanEntries != null && !runtimeBeanEntries.isEmpty()) {
+ for (RuntimeBeanEntry runtimeBeanEntry : runtimeBeanEntries) {
+ if (runtimeBeanEntry.getJavaNameOfRuntimeMXBean().equals(
+ javaName)) {
+ return runtimeBeanEntry;
+ }
+ }
+ }
+ return null;
+ }
+
+ private RuntimeBeanEntry.Rpc getRpcByName(
+ final List<RuntimeBeanEntry.Rpc> rpcs, String name) {
+ if (rpcs != null && !rpcs.isEmpty()) {
+ for (RuntimeBeanEntry.Rpc rpc : rpcs) {
+ if (rpc.getName().equals(name)) {
+ return rpc;
+ }
+ }
+ }
+ return null;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator;
+
+import static org.apache.commons.lang3.StringUtils.capitalize;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import java.net.URI;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.Test;
+import org.opendaylight.controller.config.yangjmxgenerator.RuntimeBeanEntry.Rpc;
+import org.opendaylight.controller.config.yangjmxgenerator.attribute.AttributeIfc;
+import org.opendaylight.yangtools.sal.binding.model.api.Type;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+
+import com.google.common.base.Optional;
+
+public class RuntimeRegistratorTest {
+ // TODO add more tests
+ protected RuntimeBeanEntry prepareRootRB(List<RuntimeBeanEntry> children) {
+
+ DataSchemaNode dataSchemaNodeForReporting = mock(DataSchemaNode.class);
+ doReturn("DataSchemaNode").when(dataSchemaNodeForReporting).toString();
+ return new RuntimeBeanEntry("pa.cka.ge", dataSchemaNodeForReporting,
+ "module-name", "ModuleName", true, Optional.<String> absent(),
+ Collections.<AttributeIfc> emptyList(), children,
+ Collections.<Rpc> emptySet());
+ }
+
+ protected RuntimeBeanEntry prepareChildRB(List<RuntimeBeanEntry> children,
+ String prefix) {
+ DataSchemaNode dataSchemaNodeForReporting = mock(DataSchemaNode.class);
+ doReturn("DataSchemaNode").when(dataSchemaNodeForReporting).toString();
+ return new RuntimeBeanEntry("pa.cka.ge", dataSchemaNodeForReporting,
+ prefix + "child-name", capitalize(prefix) + "ChildName", false,
+ Optional.<String> absent(),
+ Collections.<AttributeIfc> emptyList(), children,
+ Collections.<Rpc> emptySet());
+ }
+
+ @Test
+ public void testHierarchy() {
+ LeafSchemaNode leaf = mock(LeafSchemaNode.class);
+ doReturn(new QName(URI.create("urn:x"), "leaf-local-name")).when(leaf)
+ .getQName();
+ doReturn(Collections.emptyList()).when(leaf).getUnknownSchemaNodes();
+ doReturn(null).when(leaf).getDefault();
+ doReturn(null).when(leaf).getDescription();
+
+ TypeProviderWrapper typeProviderWrapper = mock(TypeProviderWrapper.class);
+ Type mockedType = mock(Type.class);
+ doReturn(mockedType).when(typeProviderWrapper).getType(leaf);
+ doReturn("java.lang.String").when(mockedType).getFullyQualifiedName();
+
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.opendaylight.controller.config.yangjmxgenerator.ConfigConstants.MODULE_TYPE_Q_NAME;
+import static org.opendaylight.controller.config.yangjmxgenerator.ConfigConstants.SERVICE_TYPE_Q_NAME;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
+
+public class SchemaContextTest extends AbstractYangTest {
+
+ IdentitySchemaNode findIdentityByQName(Module module, QName qName) {
+ Map<QName, IdentitySchemaNode> mapIdentitiesByQNames = mapIdentitiesByQNames(module);
+ IdentitySchemaNode found = mapIdentitiesByQNames.get(qName);
+ assertNotNull(found);
+ return found;
+ }
+
+ @Test
+ public void testReadingIdentities_threadsModule() {
+
+ IdentitySchemaNode serviceType = findIdentityByQName(configModule,
+ SERVICE_TYPE_Q_NAME);
+
+ Map<String /* identity name */, Optional<QName>> expectedIdentitiesToBases = ImmutableMap
+ .of("eventbus", Optional.<QName>absent(), "threadfactory", Optional.<QName>absent(), "threadpool",
+ Optional.<QName>absent(), "scheduled-threadpool", Optional.<QName>absent());
+
+ assertThat(threadsModule.getIdentities().size(),
+ is(expectedIdentitiesToBases.size()));
+ assertAllIdentitiesAreExpected(threadsModule, expectedIdentitiesToBases);
+
+ IdentitySchemaNode eventBusSchemaNode = null;
+ for (IdentitySchemaNode id : threadsModule.getIdentities()) {
+ String localName = id.getQName().getLocalName();
+
+ if (localName.equals("eventbus")) {
+ eventBusSchemaNode = id;
+ }
+ // all except scheduled-threadpool should have base set to
+ // serviceType
+ if (localName.equals("scheduled-threadpool") == false) {
+ assertEquals(serviceType, id.getBaseIdentity());
+ }
+ }
+ assertNotNull(eventBusSchemaNode);
+ // check unknown schma nodes
+ List<UnknownSchemaNode> unknownSchemaNodes = eventBusSchemaNode
+ .getUnknownSchemaNodes();
+ assertEquals(1, unknownSchemaNodes.size());
+ UnknownSchemaNode usn = unknownSchemaNodes.get(0);
+ assertEquals("com.google.common.eventbus.EventBus", usn.getQName()
+ .getLocalName());
+ assertEquals(ConfigConstants.JAVA_CLASS_EXTENSION_QNAME,
+ usn.getNodeType());
+ }
+
+ private void assertAllIdentitiesAreExpected(
+ Module module,
+ Map<String /* identity name */, Optional<QName>> expectedIdentitiesToBases) {
+ Map<String /* identity name */, Optional<QName>> copyOfExpectedNames = new HashMap<>(
+ expectedIdentitiesToBases);
+ for (IdentitySchemaNode id : module.getIdentities()) {
+ String localName = id.getQName().getLocalName();
+ assertTrue("Unexpected identity " + localName,
+ copyOfExpectedNames.containsKey(localName));
+ Optional<QName> maybeExpectedBaseQName = copyOfExpectedNames
+ .remove(localName);
+ if (maybeExpectedBaseQName.isPresent()) {
+ assertEquals("Unexpected base identity of " + localName,
+ maybeExpectedBaseQName.get(), id.getBaseIdentity()
+ .getQName());
+ }
+ }
+ assertEquals("Expected identities not found " + copyOfExpectedNames,
+ Collections.EMPTY_MAP, copyOfExpectedNames);
+ }
+
+ @Test
+ public void testReadingIdentities_threadsJavaModule() {
+ Map<String /* identity name */, Optional<QName>> expectedIdentitiesToBases = ImmutableMap
+ .of("eventbus", Optional.of(MODULE_TYPE_Q_NAME), "async-eventbus", Optional.of(MODULE_TYPE_Q_NAME),
+ "threadfactory-naming", Optional.of(MODULE_TYPE_Q_NAME), "threadpool-dynamic",
+ Optional.of(MODULE_TYPE_Q_NAME), "thread-rpc-context", Optional.<QName>absent());
+ assertAllIdentitiesAreExpected(threadsJavaModule,
+ expectedIdentitiesToBases);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.common.QName;
+
+import com.google.common.collect.Sets;
+
+public class ServiceInterfaceEntryTest extends AbstractYangTest {
+ public static final String PACKAGE_NAME = "packages.sis";
+ public static final List<String> expectedSIEFileNames = toFileNames("[EventBusServiceInterface"
+ + ".java, "
+ + "ScheduledThreadPoolServiceInterface"
+ + ".java, ThreadFactoryServiceInterface.java, ThreadPoolServiceInterface.java]");
+
+ private static final URI THREADS_NAMESPACE;
+ private static final Date THREADS_REVISION_DATE;
+ static {
+ try {
+ THREADS_NAMESPACE = new URI(ConfigConstants.CONFIG_NAMESPACE
+ + ":threads");
+ } catch (URISyntaxException e) {
+ throw new Error(e);
+ }
+ SimpleDateFormat revisionFormat = new SimpleDateFormat("yyyy-MM-dd");
+ try {
+ THREADS_REVISION_DATE = revisionFormat.parse("2013-04-09");
+ } catch (ParseException e) {
+ throw new Error(e);
+ }
+ }
+
+ public static final QName EVENTBUS_QNAME = new QName(THREADS_NAMESPACE,
+ THREADS_REVISION_DATE, "eventbus");
+ public static final QName THREADFACTORY_QNAME = new QName(
+ THREADS_NAMESPACE, THREADS_REVISION_DATE, "threadfactory");
+ public static final QName THREADPOOL_QNAME = new QName(THREADS_NAMESPACE,
+ THREADS_REVISION_DATE, "threadpool");
+ public static final QName SCHEDULED_THREADPOOL_QNAME = new QName(
+ THREADS_NAMESPACE, THREADS_REVISION_DATE, "scheduled-threadpool");
+ public static final QName SCHEDULED_EXECUTOR_SERVICE_QNAME = new QName(
+ THREADS_NAMESPACE, THREADS_REVISION_DATE,
+ "scheduled-executor-service");
+ public static final String SCHEDULED_THREADPOOL_INTERFACE_NAME = "ScheduledThreadPoolServiceInterface";
+
+ public static List<String> toFileNames(String fileNameString) {
+ assertThat(fileNameString.startsWith("["), CoreMatchers.is(true));
+ assertThat(fileNameString.endsWith("]"), CoreMatchers.is(true));
+ fileNameString = fileNameString.substring(1,
+ fileNameString.length() - 1);
+ return Arrays.asList(fileNameString.split(", "));
+ }
+
+ @Test
+ public void testCreateFromIdentities() {
+ // each identity has to have a base that leads to service-type
+ Map<QName, ServiceInterfaceEntry> namesToSIEntries = ServiceInterfaceEntry
+ .create(threadsModule, PACKAGE_NAME);
+ // expected eventbus, threadfactory, threadpool,
+ // scheduled-threadpool,thread-rpc-context
+ assertThat(namesToSIEntries.size(), is(expectedSIEFileNames.size()));
+
+ Set<QName> withNoBaseType = Sets.newHashSet(EVENTBUS_QNAME,
+ THREADFACTORY_QNAME, THREADPOOL_QNAME,
+ SCHEDULED_EXECUTOR_SERVICE_QNAME);
+ HashSet<QName> withBaseType = new HashSet<>();
+ for (Entry<QName, ServiceInterfaceEntry> entry : namesToSIEntries
+ .entrySet()) {
+ QName qName = entry.getKey();
+ if (withNoBaseType.contains(qName)) {
+ ServiceInterfaceEntry sie = namesToSIEntries.get(qName);
+ assertNotNull(qName + " not found", sie);
+ assertThat(qName + " should have empty base type", sie
+ .getBase().isPresent(), is(false));
+ assertThat(sie.getQName(), is(qName));
+ } else {
+ withBaseType.add(qName);
+ }
+ }
+ // scheduled-threadpool has super type threadpool
+ assertThat(withBaseType,
+ is(Sets.newHashSet(SCHEDULED_THREADPOOL_QNAME)));
+ assertThat(withBaseType.contains(SCHEDULED_THREADPOOL_QNAME), is(true));
+ ServiceInterfaceEntry scheduled = namesToSIEntries
+ .get(SCHEDULED_THREADPOOL_QNAME);
+ assertNotNull(scheduled);
+ assertThat(scheduled.getQName(), is(SCHEDULED_THREADPOOL_QNAME));
+ ServiceInterfaceEntry threadPool = namesToSIEntries
+ .get(THREADPOOL_QNAME);
+ assertNotNull(threadPool);
+ assertThat("scheduled-threadpool should extend threadpool", scheduled
+ .getBase().get(), is(threadPool));
+
+ assertThat(scheduled.getExportedOsgiClassName(),
+ is(PackageTranslatorTest.EXPECTED_PACKAGE_PREFIX
+ + ".threadpool.ScheduledThreadPool"));
+ assertThat(threadPool.getExportedOsgiClassName(),
+ is(PackageTranslatorTest.EXPECTED_PACKAGE_PREFIX
+ + ".threadpool.ThreadPool"));
+
+ String expectedDescription = "An extension of the simple pool of threads able to schedule "
+ + "work to be executed at some point in time.";
+ assertThat(trimInnerSpacesOrNull(scheduled.getNullableDescription()),
+ is(expectedDescription));
+ assertThat(scheduled.getPackageName(), is(PACKAGE_NAME));
+ assertThat(scheduled.getTypeName(),
+ is(SCHEDULED_THREADPOOL_INTERFACE_NAME));
+ assertThat(scheduled.getFullyQualifiedName(), is(PACKAGE_NAME + "."
+ + SCHEDULED_THREADPOOL_INTERFACE_NAME));
+ }
+
+ static String trimInnerSpacesOrNull(String input) {
+ if (input == null)
+ return null;
+ return input.replaceAll("\\s{2,}", " ");
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.plugin.util;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.opendaylight.yangtools.yang.model.api.Module;
+
+import com.google.common.base.Preconditions;
+
+public class YangModelSearchUtils {
+
+ public static Map<String, Module> mapModulesByNames(
+ Collection<Module> modules) {
+ Map<String, Module> result = new HashMap<>();
+ for (Module m : modules) {
+ String moduleName = m.getName();
+ Preconditions.checkArgument(
+ result.containsKey(moduleName) == false,
+ "Two modules have same name " + moduleName);
+ result.put(moduleName, m);
+ }
+ return result;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yangjmxgenerator.unknownextension;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.InputStream;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.Test;
+import org.opendaylight.controller.config.yangjmxgenerator.ConfigConstants;
+import org.opendaylight.controller.config.yangjmxgenerator.ServiceInterfaceEntryTest;
+import org.opendaylight.controller.config.yangjmxgenerator.plugin.util.YangModelSearchUtils;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl;
+
+import com.google.common.collect.Lists;
+
+public class UnknownExtensionTest extends ServiceInterfaceEntryTest {
+
+ @Test
+ public void testStopOnUnknownLanguageExtension() throws Exception {
+ List<InputStream> yangISs = Lists.newArrayList(getClass()
+ .getResourceAsStream("test-ifcWithUnknownExtension.yang"));
+ yangISs.addAll(getConfigApiYangInputStreams());
+ try {
+ YangParserImpl parser = new YangParserImpl();
+ Set<Module> modulesToBuild = parser
+ .parseYangModelsFromStreams(yangISs);
+ context = parser.resolveSchemaContext(modulesToBuild);
+ namesToModules = YangModelSearchUtils.mapModulesByNames(context
+ .getModules());
+ configModule = namesToModules.get(ConfigConstants.CONFIG_MODULE);
+ threadsModule = namesToModules
+ .get(ConfigConstants.CONFIG_THREADS_MODULE);
+ try {
+ super.testCreateFromIdentities();
+ fail();
+ } catch (IllegalStateException e) {
+ assertTrue(
+ e.getMessage(),
+ e.getMessage().startsWith(
+ "Unexpected unknown schema node."));
+ }
+ } finally {
+ for (InputStream is : yangISs) {
+ is.close();
+ }
+ }
+ }
+
+}
--- /dev/null
+module config-bgp-listener-impl {
+ yang-version 1;
+ namespace "urn:opendaylight:params:xml:ns:yang:controller:config:bgp:listener:impl";
+ prefix "bgpl-impl";
+
+ import ietf-inet-types { prefix inet; }
+ import config { prefix config; revision-date 2013-04-05; }
+
+ description
+ "This module contains the base YANG definitions for NS-OS
+ BGP listener implementation.";
+
+ revision "2013-04-09" {
+ description
+ "Initial revision";
+ reference "NS-OS System Design, version 1.2.";
+ }
+
+ identity bgp-listener-impl {
+ base config:module-type;
+ }
+
+ augment "/config:modules/config:module/config:state" {
+ case bgp-listener-impl {
+ when "/config:modules/config:module/config:type = 'bgp-listener-impl'";
+ list peers {
+ config:inner-state-bean;
+ leaf port {
+ type inet:port-number;
+ default 179;
+ }
+ leaf core-size {
+ type uint32;
+ }
+ }
+
+ leaf as-number {
+ mandatory true;
+ type inet:as-number;
+ }
+ }
+ }
+}
--- /dev/null
+module config-jmx-it-impl {
+ yang-version 1;
+ namespace "urn:opendaylight:params:xml:ns:yang:controller:config:jmx:generator:it:impl";
+ prefix "it-impl";
+
+ import config-jmx-it { prefix jmxIt; revision-date 2013-06-13;}
+ import config { prefix config; revision-date 2013-04-05; }
+ import ietf-inet-types { prefix inet; revision-date 2010-09-24;}
+ import config-threads { prefix th; revision-date 2013-04-09; }
+
+
+
+ description
+ "Testing IMPL";
+
+ revision "2013-04-03" {
+ description
+ "Initial revision";
+ }
+
+ identity impl {
+ base config:module-type;
+ config:provided-service jmxIt:testing;
+ config:java-name-prefix TestImpl;
+ }
+
+ identity impl-netconf {
+ base config:module-type;
+ config:provided-service jmxIt:testing;
+ config:java-name-prefix NetconfTestImpl;
+ }
+
+ augment "/config:modules/config:module/config:configuration" {
+ case impl {
+ when "/config:modules/config:module/config:type = 'impl'";
+
+ container dto-a1 {
+ leaf simple-arg {
+ type uint32;
+ }
+
+ leaf port {
+ type inet:port-number;
+ }
+
+ }
+
+ leaf as-number {
+ mandatory true;
+ type inet:as-number;
+ }
+
+
+ leaf simpleInt {
+ type uint32;
+ default 99L;
+ }
+
+ container dto_b {
+ leaf simple-int1 {
+ type uint32;
+ }
+
+ leaf simple-int2 {
+ type uint32;
+ }
+ }
+
+ }
+ }
+
+ augment "/config:modules/config:module/config:state" {
+ case impl {
+ when "/config:modules/config:module/config:type = 'impl'";
+ // root runtime bean
+ leaf created-sessions {
+ type uint32;
+ }
+ }
+ }
+
+ augment "/config:modules/config:module/config:configuration" {
+ case impl-netconf {
+ when "/config:modules/config:module/config:type = 'impl-netconf'";
+
+ container dto-a {
+ leaf simple-arg {
+ type uint32;
+ }
+
+ container dto-a-inner {
+ leaf simple-arg {
+ type uint32;
+ }
+
+ container dto-a-inner-inner {
+ leaf simple-arg {
+ type uint32;
+ }
+ }
+ }
+ }
+
+ leaf simpleInt {
+ type uint32;
+ }
+
+ leaf simpleBoolean {
+ type boolean;
+ default false;
+ }
+
+ leaf simple-long {
+ type int64 ;
+ }
+
+ leaf simple-long-2 {
+ type uint32;
+ }
+
+ leaf simple-BigInteger {
+ type uint64;
+ }
+
+ leaf simple-byte {
+ type int8;
+ }
+
+ leaf simple-short {
+ type uint8;
+ }
+
+ leaf simple-test {
+ type uint16;
+ default 99;
+ }
+
+ leaf-list simple-list {
+ type uint16;
+ }
+
+ container dto_c {
+ leaf simple-int1 {
+ type uint32;
+ }
+
+ leaf simple-int2 {
+ type uint32;
+ }
+
+ leaf simple-int3 {
+ type uint16;
+ }
+
+ leaf-list simple-list {
+ type uint16;
+ }
+
+ list complex-dto-bInner {
+ leaf-list simple-list {
+ type uint16;
+ }
+ leaf simple-int3 {
+ type uint16;
+ }
+
+ container deep {
+ leaf simple-int3 {
+ type uint16;
+ }
+ }
+ }
+ }
+
+ list complex-list {
+ list simple-list {
+ leaf simple-int3 {
+ type uint16;
+ }
+ }
+ }
+
+ list peers {
+ config:java-name-prefix Peer;
+ leaf port {
+ type string;
+ }
+ leaf core-size {
+ type uint32;
+ }
+ leaf simple-int3 {
+ type uint16;
+ }
+ }
+
+ container thread-factory {
+ uses config:service-ref {
+ refine type {
+ mandatory false;
+ config:required-identity th:threadfactory;
+ }
+ }
+ }
+ }
+ }
+
+ augment "/config:modules/config:module/config:state" {
+ case impl-netconf {
+ when "/config:modules/config:module/config:type = 'impl-netconf'";
+ // root runtime bean
+ leaf created-sessions {
+ type uint32;
+ }
+
+ }
+ }
+}
--- /dev/null
+// vi: set smarttab et sw=4 tabstop=4:
+module config-jmx-it {
+ yang-version 1;
+ namespace "urn:opendaylight:params:xml:ns:yang:controller:config:jmx:generator:it";
+ prefix "jmxIt";
+
+ import config { prefix config; revision-date 2013-04-05; }
+
+
+
+ description
+ "Testing API";
+
+ revision "2013-06-13" {
+ description
+ "Initial revision";
+ }
+
+ identity testing {
+ description
+ "Test api";
+
+ base "config:service-type";
+ config:java-class "java.lang.AutoCloseable";
+ }
+}
--- /dev/null
+// vi: set smarttab et sw=4 tabstop=4:
+module config-test-duplicate-attribute-in-list {
+ yang-version 1;
+ namespace "urn:opendaylight:params:xml:ns:yang:controller:jmx:generator:it:duplicate";
+ prefix "it-duplicate";
+
+ import config { prefix config; revision-date 2013-04-05; }
+ import ietf-inet-types { prefix inet; revision-date 2010-09-24;}
+
+ description
+ "Testing IMPL";
+
+ revision "2013-04-03" {
+ description
+ "Initial revision";
+ }
+
+ identity implementation {
+ base config:module-type;
+ config:java-name-prefix TestImpl;
+ }
+
+ identity netconf {
+ base config:module-type;
+ config:java-name-prefix NetconfTestImpl;
+ }
+
+ augment "/config:modules/config:module/config:configuration" {
+ case implementation {
+ when "/config:modules/config:module/config:type = 'implementation'";
+
+ container dto-a {
+ leaf simple-arg {
+ type uint32;
+ }
+
+ leaf port {
+ type inet:port-number;
+ }
+
+ }
+ }
+ }
+
+ augment "/config:modules/config:module/config:state" {
+ case implementation {
+ when "/config:modules/config:module/config:type = 'implementation'";
+ // root runtime bean
+ leaf created-sessions {
+ type uint32;
+ }
+ }
+ }
+
+ augment "/config:modules/config:module/config:configuration" {
+ case netconf {
+ when "/config:modules/config:module/config:type = 'netconf'";
+
+ list dtos {
+ config:java-name-prefix dto-a;
+ leaf port {
+ type string;
+ }
+ leaf core-size {
+ type uint32;
+ }
+ leaf simple-int3 {
+ type uint16;
+ }
+ }
+ }
+ }
+
+ augment "/config:modules/config:module/config:state" {
+ case netconf {
+ when "/config:modules/config:module/config:type = 'netconf'";
+ // root runtime bean
+ leaf created-sessions {
+ type uint32;
+ }
+
+ }
+ }
+}
--- /dev/null
+// vi: set smarttab et sw=4 tabstop=4:
+module config-test-duplicate-attribute-runtime-bean {
+ yang-version 1;
+ namespace "urn:opendaylight:params:xml:ns:yang:controller:jmx:duplicate:runtime";
+ prefix "th-java";
+
+ import config { prefix config; revision-date 2013-04-05; }
+ import rpc-context { prefix rpcx; revision-date 2013-06-17; }
+ import ietf-inet-types { prefix inet; revision-date 2010-09-24;}
+
+
+ description
+ "This module contains the base YANG definitions for NS-OS
+ thread services pure Java implementation.";
+
+ revision "2013-04-05" {
+ description
+ "Updated to work with new anchors.";
+ }
+
+ revision "2013-04-03" {
+ description
+ "Initial revision.";
+ }
+
+ identity async-eventbus {
+ base config:module-type;
+ config:java-name-prefix AsyncEventBus;
+ }
+
+ augment "/config:modules/config:module/config:configuration" {
+ case async-eventbus {
+ when "/config:modules/config:module/config:type = 'async-eventbus'";
+ list dtos {
+ config:java-name-prefix dto-a;
+ leaf port {
+ type string;
+ }
+ leaf core-size {
+ type uint32;
+ }
+ leaf simple-int3 {
+ type uint16;
+ }
+ }
+ }
+ }
+
+ augment "/config:modules/config:module/config:state" {
+ case async-eventbus {
+ when "/config:modules/config:module/config:type = 'async-eventbus'";
+ container dto-a {
+ leaf simple-arg {
+ type uint32;
+ }
+
+ leaf port {
+ type inet:port-number;
+ }
+ }
+ }
+ }
+}
--- /dev/null
+// vi: set smarttab et sw=4 tabstop=4:
+module config-test-duplicate-attribute {
+ yang-version 1;
+ namespace "urn:opendaylight:params:xml:ns:yang:controller:jmx:generator:it:duplicate";
+ prefix "it-duplicate";
+
+ import config { prefix config; revision-date 2013-04-05; }
+ import ietf-inet-types { prefix inet; revision-date 2010-09-24;}
+
+ description
+ "Testing IMPL";
+
+ revision "2013-04-03" {
+ description
+ "Initial revision";
+ }
+
+ identity implementation {
+ base config:module-type;
+ config:java-name-prefix TestImpl;
+ }
+
+ identity netconf {
+ base config:module-type;
+ config:java-name-prefix NetconfTestImpl;
+ }
+
+ augment "/config:modules/config:module/config:configuration" {
+ case implementation {
+ when "/config:modules/config:module/config:type = 'implementation'";
+
+ container dto-a {
+ leaf simple-arg {
+ type uint32;
+ }
+
+ leaf port {
+ type inet:port-number;
+ }
+
+ }
+ }
+ }
+
+ augment "/config:modules/config:module/config:state" {
+ case implementation {
+ when "/config:modules/config:module/config:type = 'implementation'";
+ // root runtime bean
+ leaf created-sessions {
+ type uint32;
+ }
+ }
+ }
+
+ augment "/config:modules/config:module/config:configuration" {
+ case netconf {
+ when "/config:modules/config:module/config:type = 'netconf'";
+
+ container dto-a {
+ leaf simple-arg {
+ type uint32;
+ }
+
+ leaf port {
+ type inet:port-number;
+ }
+ }
+ }
+ }
+
+ augment "/config:modules/config:module/config:state" {
+ case netconf {
+ when "/config:modules/config:module/config:type = 'netconf'";
+ // root runtime bean
+ leaf created-sessions {
+ type uint32;
+ }
+
+ }
+ }
+}
--- /dev/null
+// vi: set smarttab et sw=4 tabstop=4:
+module config-test-generated-attributes-name-conflict {
+ yang-version 1;
+ namespace "urn:opendaylight:params:xml:ns:yang:controller:jmx:duplicate:runtime:bean";
+ prefix "th-java";
+
+ import config { prefix config; revision-date 2013-04-05; }
+ import ietf-inet-types { prefix inet; revision-date 2010-09-24;}
+
+ description
+ "This module contains the base YANG definitions for NS-OS
+ thread services pure Java implementation.";
+
+ revision "2013-04-05" {
+ description
+ "Updated to work with new anchors.";
+ }
+
+ revision "2013-04-03" {
+ description
+ "Initial revision";
+ }
+
+ identity async-eventbus {
+ base config:module-type;
+ config:java-name-prefix AsyncEventBus;
+ }
+
+ augment "/config:modules/config:module/config:configuration" {
+ case async-eventbus {
+ when "/config:modules/config:module/config:type = 'async-eventbus'";
+ leaf port {
+ type string;
+ }
+
+ list state-b {
+ leaf port {
+ type string;
+ }
+ }
+
+ container stateB {
+ leaf port {
+ type string;
+ }
+ }
+ }
+ }
+
+ augment "/config:modules/config:module/config:state" {
+ case async-eventbus {
+ when "/config:modules/config:module/config:type = 'async-eventbus'";
+
+ list state-a {
+ config:inner-state-bean;
+
+ leaf port {
+ type string;
+ }
+
+ list state-b {
+ leaf port {
+ type string;
+ }
+ }
+ }
+ }
+ }
+}
--- /dev/null
+// vi: set smarttab et sw=4 tabstop=4:
+module config-test-runtime-bean-list-name-conflict {
+ yang-version 1;
+ namespace "urn:opendaylight:params:xml:ns:yang:controller:jmx:duplicate:runtime:bean";
+ prefix "th-java";
+
+ import config { prefix config; revision-date 2013-04-05; }
+ import ietf-inet-types { prefix inet; revision-date 2010-09-24;}
+
+ description
+ "This module contains the base YANG definitions for NS-OS
+ thread services pure Java implementation.";
+
+ revision "2013-04-05" {
+ description
+ "Updated to work with new anchors.";
+ }
+
+ revision "2013-04-03" {
+ description
+ "Initial revision";
+ }
+
+ identity async-eventbus {
+ base config:module-type;
+ config:java-name-prefix AsyncEventBus;
+ }
+
+ augment "/config:modules/config:module/config:configuration" {
+ case async-eventbus {
+ when "/config:modules/config:module/config:type = 'async-eventbus'";
+ leaf port {
+ type string;
+ }
+ }
+ }
+
+ augment "/config:modules/config:module/config:state" {
+ case async-eventbus {
+ when "/config:modules/config:module/config:type = 'async-eventbus'";
+
+ list state-a-runtime-mX-bean {
+ leaf port {
+ type string;
+ }
+ }
+
+ list state-a {
+ config:inner-state-bean;
+
+ leaf port {
+ type string;
+ }
+ }
+ }
+ }
+}
--- /dev/null
+// vi: set smarttab et sw=4 tabstop=4:
+module config-test-runtime-bean-list-name-conflict2 {
+ yang-version 1;
+ namespace "urn:opendaylight:params:xml:ns:yang:controller:jmx:duplicate:runtime:bean";
+ prefix "th-java";
+
+ import config { prefix config; revision-date 2013-04-05; }
+ import ietf-inet-types { prefix inet; revision-date 2010-09-24;}
+
+ description
+ "This module contains the base YANG definitions of
+ thread services pure Java implementation.";
+
+ revision "2013-04-05" {
+ description
+ "Updated to work with new anchors.";
+ }
+
+ revision "2013-04-03" {
+ description
+ "Initial revision";
+ }
+
+ identity async-eventbus {
+ base config:module-type;
+ config:java-name-prefix AsyncEventBus;
+ }
+
+ augment "/config:modules/config:module/config:configuration" {
+ case async-eventbus {
+ when "/config:modules/config:module/config:type = 'async-eventbus'";
+ leaf port {
+ type string;
+ }
+
+ list state-a-runtime-mX-bean {
+ leaf port {
+ type string;
+ }
+ }
+ }
+ }
+
+ augment "/config:modules/config:module/config:state" {
+ case async-eventbus {
+ when "/config:modules/config:module/config:type = 'async-eventbus'";
+
+ list state-a {
+ config:inner-state-bean;
+
+ leaf port {
+ type string;
+ }
+ }
+ }
+ }
+}
--- /dev/null
+// vi: set smarttab et sw=4 tabstop=4:
+module config-test-runtime-bean-name-conflict {
+ yang-version 1;
+ namespace "urn:opendaylight:params:xml:ns:yang:controller:jmx:duplicate:runtime:bean";
+ prefix "th-java";
+
+ import config { prefix config; revision-date 2013-04-05; }
+ import ietf-inet-types { prefix inet; revision-date 2010-09-24;}
+
+ description
+ "This module contains the base YANG definitions for
+ thread services pure Java implementation.";
+
+ revision "2013-04-05" {
+ description
+ "Updated to work with new anchors.";
+ }
+
+ revision "2013-04-03" {
+ description
+ "Initial revision";
+ }
+
+ identity async-eventbus {
+ base config:module-type;
+ config:java-name-prefix AsyncEventBus;
+ }
+
+ augment "/config:modules/config:module/config:configuration" {
+ case async-eventbus {
+ when "/config:modules/config:module/config:type = 'async-eventbus'";
+ leaf port {
+ type string;
+ }
+ }
+ }
+
+ augment "/config:modules/config:module/config:state" {
+ case async-eventbus {
+ when "/config:modules/config:module/config:type = 'async-eventbus'";
+
+ list state-a {
+ config:inner-state-bean;
+
+ leaf port {
+ type string;
+ }
+
+ list state-a {
+ config:inner-state-bean;
+
+ leaf port {
+ type string;
+ }
+ }
+ }
+ }
+ }
+}
--- /dev/null
+// vi: set smarttab et sw=4 tabstop=4:
+module config-test-runtime-bean-name-conflict2 {
+ yang-version 1;
+ namespace "urn:opendaylight:params:xml:ns:yang:controller:jmx:duplicate:runtime:bean";
+ prefix "th-java";
+
+ import config { prefix config; revision-date 2013-04-05; }
+ import ietf-inet-types { prefix inet; revision-date 2010-09-24;}
+
+
+ description
+ "This module contains the base YANG definitions for
+ thread services pure Java implementation.";
+
+ revision "2013-04-05" {
+ description
+ "Initial";
+ }
+
+ identity async-eventbus {
+ base config:module-type;
+ config:java-name-prefix AsyncEventBus;
+ }
+
+ augment "/config:modules/config:module/config:configuration" {
+ case async-eventbus {
+ when "/config:modules/config:module/config:type = 'async-eventbus'";
+ leaf port {
+ type string;
+ }
+ }
+ }
+
+ augment "/config:modules/config:module/config:state" {
+ case async-eventbus {
+ when "/config:modules/config:module/config:type = 'async-eventbus'";
+
+ list state-a {
+ config:inner-state-bean;
+
+ leaf port {
+ type string;
+ }
+ }
+ }
+ }
+
+
+ identity async-eventbus-b {
+ base config:module-type;
+ config:java-name-prefix AsyncEventBusB;
+ }
+
+ augment "/config:modules/config:module/config:configuration" {
+ case async-eventbus-b {
+ when "/config:modules/config:module/config:type = 'async-eventbus-b'";
+ leaf port {
+ type string;
+ }
+ }
+ }
+
+ augment "/config:modules/config:module/config:state" {
+ case async-eventbus-b {
+ when "/config:modules/config:module/config:type = 'async-eventbus-b'";
+
+ list state-a {
+ config:inner-state-bean;
+
+ leaf port {
+ type string;
+ }
+
+ }
+ }
+ }
+}
--- /dev/null
+module ietf-inet-types {
+
+ namespace "urn:ietf:params:xml:ns:yang:ietf-inet-types";
+ prefix "inet";
+
+ organization
+ "IETF NETMOD (NETCONF Data Modeling Language) Working Group";
+
+ contact
+ "WG Web: <http://tools.ietf.org/wg/netmod/>
+ WG List: <mailto:netmod@ietf.org>
+
+ WG Chair: David Partain
+ <mailto:david.partain@ericsson.com>
+
+ WG Chair: David Kessens
+ <mailto:david.kessens@nsn.com>
+
+ Editor: Juergen Schoenwaelder
+ <mailto:j.schoenwaelder@jacobs-university.de>";
+
+ description
+ "This module contains a collection of generally useful derived
+ YANG data types for Internet addresses and related things.
+
+ Copyright (c) 2010 IETF Trust and the persons identified as
+ authors of the code. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, is permitted pursuant to, and subject to the license
+ terms contained in, the Simplified BSD License set forth in Section
+ 4.c of the IETF Trust's Legal Provisions Relating to IETF Documents
+ (http://trustee.ietf.org/license-info).
+
+ This version of this YANG module is part of RFC 6021; see
+ the RFC itself for full legal notices.";
+
+ revision 2010-09-24 {
+ description
+ "Initial revision.";
+ reference
+ "RFC 6021: Common YANG Data Types";
+ }
+
+ /*** collection of protocol field related types ***/
+
+ typedef ip-version {
+ type enumeration {
+ enum unknown {
+ value "0";
+ description
+ "An unknown or unspecified version of the Internet protocol.";
+ }
+ enum ipv4 {
+ value "1";
+ description
+ "The IPv4 protocol as defined in RFC 791.";
+ }
+ enum ipv6 {
+ value "2";
+ description
+ "The IPv6 protocol as defined in RFC 2460.";
+ }
+ }
+ description
+ "This value represents the version of the IP protocol.
+
+ In the value set and its semantics, this type is equivalent
+ to the InetVersion textual convention of the SMIv2.";
+ reference
+ "RFC 791: Internet Protocol
+ RFC 2460: Internet Protocol, Version 6 (IPv6) Specification
+ RFC 4001: Textual Conventions for Internet Network Addresses";
+ }
+
+ typedef dscp {
+ type uint8 {
+ range "0..63";
+ }
+ description
+ "The dscp type represents a Differentiated Services Code-Point
+ that may be used for marking packets in a traffic stream.
+
+ In the value set and its semantics, this type is equivalent
+ to the Dscp textual convention of the SMIv2.";
+ reference
+ "RFC 3289: Management Information Base for the Differentiated
+ Services Architecture
+ RFC 2474: Definition of the Differentiated Services Field
+ (DS Field) in the IPv4 and IPv6 Headers
+ RFC 2780: IANA Allocation Guidelines For Values In
+ the Internet Protocol and Related Headers";
+ }
+
+ typedef ipv6-flow-label {
+ type uint32 {
+ range "0..1048575";
+ }
+ description
+ "The flow-label type represents flow identifier or Flow Label
+ in an IPv6 packet header that may be used to discriminate
+ traffic flows.
+
+ In the value set and its semantics, this type is equivalent
+ to the IPv6FlowLabel textual convention of the SMIv2.";
+ reference
+ "RFC 3595: Textual Conventions for IPv6 Flow Label
+ RFC 2460: Internet Protocol, Version 6 (IPv6) Specification";
+ }
+
+ typedef port-number {
+ type uint16 {
+ range "0..65535";
+ }
+ description
+ "The port-number type represents a 16-bit port number of an
+ Internet transport layer protocol such as UDP, TCP, DCCP, or
+ SCTP. Port numbers are assigned by IANA. A current list of
+ all assignments is available from <http://www.iana.org/>.
+
+ Note that the port number value zero is reserved by IANA. In
+ situations where the value zero does not make sense, it can
+ be excluded by subtyping the port-number type.
+
+ In the value set and its semantics, this type is equivalent
+ to the InetPortNumber textual convention of the SMIv2.";
+ reference
+ "RFC 768: User Datagram Protocol
+ RFC 793: Transmission Control Protocol
+ RFC 4960: Stream Control Transmission Protocol
+ RFC 4340: Datagram Congestion Control Protocol (DCCP)
+ RFC 4001: Textual Conventions for Internet Network Addresses";
+ }
+
+ /*** collection of autonomous system related types ***/
+
+ typedef as-number {
+ type uint32;
+ description
+ "The as-number type represents autonomous system numbers
+ which identify an Autonomous System (AS). An AS is a set
+ of routers under a single technical administration, using
+ an interior gateway protocol and common metrics to route
+ packets within the AS, and using an exterior gateway
+ protocol to route packets to other ASs'. IANA maintains
+ the AS number space and has delegated large parts to the
+ regional registries.
+
+ Autonomous system numbers were originally limited to 16
+ bits. BGP extensions have enlarged the autonomous system
+ number space to 32 bits. This type therefore uses an uint32
+ base type without a range restriction in order to support
+ a larger autonomous system number space.
+
+ In the value set and its semantics, this type is equivalent
+ to the InetAutonomousSystemNumber textual convention of
+ the SMIv2.";
+ reference
+ "RFC 1930: Guidelines for creation, selection, and registration
+ of an Autonomous System (AS)
+ RFC 4271: A Border Gateway Protocol 4 (BGP-4)
+ RFC 4893: BGP Support for Four-octet AS Number Space
+ RFC 4001: Textual Conventions for Internet Network Addresses";
+ }
+
+ /*** collection of IP address and hostname related types ***/
+
+ typedef ip-address {
+ type union {
+ type inet:ipv4-address;
+ type inet:ipv6-address;
+ }
+ description
+ "The ip-address type represents an IP address and is IP
+ version neutral. The format of the textual representations
+ implies the IP version.";
+ }
+
+ typedef ipv4-address {
+ type string {
+ pattern
+ '(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}'
+ + '([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'
+ + '(%[\p{N}\p{L}]+)?';
+ }
+ description
+ "The ipv4-address type represents an IPv4 address in
+ dotted-quad notation. The IPv4 address may include a zone
+ index, separated by a % sign.
+
+ The zone index is used to disambiguate identical address
+ values. For link-local addresses, the zone index will
+ typically be the interface index number or the name of an
+ interface. If the zone index is not present, the default
+ zone of the device will be used.
+
+ The canonical format for the zone index is the numerical
+ format";
+ }
+
+ typedef ipv6-address {
+ type string {
+ pattern '((:|[0-9a-fA-F]{0,4}):)([0-9a-fA-F]{0,4}:){0,5}'
+ + '((([0-9a-fA-F]{0,4}:)?(:|[0-9a-fA-F]{0,4}))|'
+ + '(((25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\.){3}'
+ + '(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])))'
+ + '(%[\p{N}\p{L}]+)?';
+ pattern '(([^:]+:){6}(([^:]+:[^:]+)|(.*\..*)))|'
+ + '((([^:]+:)*[^:]+)?::(([^:]+:)*[^:]+)?)'
+ + '(%.+)?';
+ }
+ description
+ "The ipv6-address type represents an IPv6 address in full,
+ mixed, shortened, and shortened-mixed notation. The IPv6
+ address may include a zone index, separated by a % sign.
+
+ The zone index is used to disambiguate identical address
+ values. For link-local addresses, the zone index will
+ typically be the interface index number or the name of an
+ interface. If the zone index is not present, the default
+ zone of the device will be used.
+
+ The canonical format of IPv6 addresses uses the compressed
+ format described in RFC 4291, Section 2.2, item 2 with the
+ following additional rules: the :: substitution must be
+ applied to the longest sequence of all-zero 16-bit chunks
+ in an IPv6 address. If there is a tie, the first sequence
+ of all-zero 16-bit chunks is replaced by ::. Single
+ all-zero 16-bit chunks are not compressed. The canonical
+ format uses lowercase characters and leading zeros are
+ not allowed. The canonical format for the zone index is
+ the numerical format as described in RFC 4007, Section
+ 11.2.";
+ reference
+ "RFC 4291: IP Version 6 Addressing Architecture
+ RFC 4007: IPv6 Scoped Address Architecture
+ RFC 5952: A Recommendation for IPv6 Address Text Representation";
+ }
+
+ typedef ip-prefix {
+ type union {
+ type inet:ipv4-prefix;
+ type inet:ipv6-prefix;
+ }
+ description
+ "The ip-prefix type represents an IP prefix and is IP
+ version neutral. The format of the textual representations
+ implies the IP version.";
+ }
+
+ typedef ipv4-prefix {
+ type string {
+ pattern
+ '(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}'
+ + '([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'
+ + '/(([0-9])|([1-2][0-9])|(3[0-2]))';
+ }
+ description
+ "The ipv4-prefix type represents an IPv4 address prefix.
+ The prefix length is given by the number following the
+ slash character and must be less than or equal to 32.
+
+ A prefix length value of n corresponds to an IP address
+ mask that has n contiguous 1-bits from the most
+ significant bit (MSB) and all other bits set to 0.
+
+ The canonical format of an IPv4 prefix has all bits of
+ the IPv4 address set to zero that are not part of the
+ IPv4 prefix.";
+ }
+
+ typedef ipv6-prefix {
+ type string {
+ pattern '((:|[0-9a-fA-F]{0,4}):)([0-9a-fA-F]{0,4}:){0,5}'
+ + '((([0-9a-fA-F]{0,4}:)?(:|[0-9a-fA-F]{0,4}))|'
+ + '(((25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\.){3}'
+ + '(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])))'
+ + '(/(([0-9])|([0-9]{2})|(1[0-1][0-9])|(12[0-8])))';
+ pattern '(([^:]+:){6}(([^:]+:[^:]+)|(.*\..*)))|'
+ + '((([^:]+:)*[^:]+)?::(([^:]+:)*[^:]+)?)'
+ + '(/.+)';
+ }
+ description
+ "The ipv6-prefix type represents an IPv6 address prefix.
+ The prefix length is given by the number following the
+ slash character and must be less than or equal 128.
+
+ A prefix length value of n corresponds to an IP address
+ mask that has n contiguous 1-bits from the most
+ significant bit (MSB) and all other bits set to 0.
+
+ The IPv6 address should have all bits that do not belong
+ to the prefix set to zero.
+
+ The canonical format of an IPv6 prefix has all bits of
+ the IPv6 address set to zero that are not part of the
+ IPv6 prefix. Furthermore, IPv6 address is represented
+ in the compressed format described in RFC 4291, Section
+ 2.2, item 2 with the following additional rules: the ::
+ substitution must be applied to the longest sequence of
+ all-zero 16-bit chunks in an IPv6 address. If there is
+ a tie, the first sequence of all-zero 16-bit chunks is
+ replaced by ::. Single all-zero 16-bit chunks are not
+ compressed. The canonical format uses lowercase
+ characters and leading zeros are not allowed.";
+ reference
+ "RFC 4291: IP Version 6 Addressing Architecture";
+ }
+
+ /*** collection of domain name and URI types ***/
+
+ typedef domain-name {
+ type string {
+ pattern '((([a-zA-Z0-9_]([a-zA-Z0-9\-_]){0,61})?[a-zA-Z0-9]\.)*'
+ + '([a-zA-Z0-9_]([a-zA-Z0-9\-_]){0,61})?[a-zA-Z0-9]\.?)'
+ + '|\.';
+ length "1..253";
+ }
+ description
+ "The domain-name type represents a DNS domain name. The
+ name SHOULD be fully qualified whenever possible.
+
+ Internet domain names are only loosely specified. Section
+ 3.5 of RFC 1034 recommends a syntax (modified in Section
+ 2.1 of RFC 1123). The pattern above is intended to allow
+ for current practice in domain name use, and some possible
+ future expansion. It is designed to hold various types of
+ domain names, including names used for A or AAAA records
+ (host names) and other records, such as SRV records. Note
+ that Internet host names have a stricter syntax (described
+ in RFC 952) than the DNS recommendations in RFCs 1034 and
+ 1123, and that systems that want to store host names in
+ schema nodes using the domain-name type are recommended to
+ adhere to this stricter standard to ensure interoperability.
+
+ The encoding of DNS names in the DNS protocol is limited
+ to 255 characters. Since the encoding consists of labels
+ prefixed by a length bytes and there is a trailing NULL
+ byte, only 253 characters can appear in the textual dotted
+ notation.
+
+ The description clause of schema nodes using the domain-name
+ type MUST describe when and how these names are resolved to
+ IP addresses. Note that the resolution of a domain-name value
+ may require to query multiple DNS records (e.g., A for IPv4
+ and AAAA for IPv6). The order of the resolution process and
+ which DNS record takes precedence can either be defined
+ explicitely or it may depend on the configuration of the
+ resolver.
+
+ Domain-name values use the US-ASCII encoding. Their canonical
+ format uses lowercase US-ASCII characters. Internationalized
+ domain names MUST be encoded in punycode as described in RFC
+ 3492";
+ reference
+ "RFC 952: DoD Internet Host Table Specification
+ RFC 1034: Domain Names - Concepts and Facilities
+ RFC 1123: Requirements for Internet Hosts -- Application
+ and Support
+ RFC 2782: A DNS RR for specifying the location of services
+ (DNS SRV)
+ RFC 3492: Punycode: A Bootstring encoding of Unicode for
+ Internationalized Domain Names in Applications
+ (IDNA)
+ RFC 5891: Internationalizing Domain Names in Applications
+ (IDNA): Protocol";
+ }
+
+ typedef host {
+ type union {
+ type inet:ip-address;
+ type inet:domain-name;
+ }
+ description
+ "The host type represents either an IP address or a DNS
+ domain name.";
+ }
+
+ typedef uri {
+ type string;
+ description
+ "The uri type represents a Uniform Resource Identifier
+ (URI) as defined by STD 66.
+
+ Objects using the uri type MUST be in US-ASCII encoding,
+ and MUST be normalized as described by RFC 3986 Sections
+ 6.2.1, 6.2.2.1, and 6.2.2.2. All unnecessary
+ percent-encoding is removed, and all case-insensitive
+ characters are set to lowercase except for hexadecimal
+ digits, which are normalized to uppercase as described in
+ Section 6.2.2.1.
+
+ The purpose of this normalization is to help provide
+ unique URIs. Note that this normalization is not
+ sufficient to provide uniqueness. Two URIs that are
+ textually distinct after this normalization may still be
+ equivalent.
+
+ Objects using the uri type may restrict the schemes that
+ they permit. For example, 'data:' and 'urn:' schemes
+ might not be appropriate.
+
+ A zero-length URI is not a valid URI. This can be used to
+ express 'URI absent' where required.
+
+ In the value set and its semantics, this type is equivalent
+ to the Uri SMIv2 textual convention defined in RFC 5017.";
+ reference
+ "RFC 3986: Uniform Resource Identifier (URI): Generic Syntax
+ RFC 3305: Report from the Joint W3C/IETF URI Planning Interest
+ Group: Uniform Resource Identifiers (URIs), URLs,
+ and Uniform Resource Names (URNs): Clarifications
+ and Recommendations
+ RFC 5017: MIB Textual Conventions for Uniform Resource
+ Identifiers (URIs)";
+ }
+
+}
--- /dev/null
+// vi: set smarttab et sw=4 tabstop=4:
+module config-threads {
+ yang-version 1;
+ namespace "urn:opendaylight:params:xml:ns:yang:controller:threads";
+ prefix "th";
+
+ import config { prefix config; revision-date 2013-04-05; }
+
+ revision "2013-05-02" {
+ description
+ "Add test";
+ }
+
+ extension java-class2 {
+ description
+ "YANG language extension carrying the fully-qualified name of
+ a Java class. Code generation tools use the provided reference
+ to tie a specific construct to its Java representation.";
+
+ argument "name";
+ }
+
+ identity eventbus {
+ description
+ "Service representing an event bus. The service acts as message
+ router between event producers and event consumers";
+
+ base "config:service-type";
+ config:java-class2 "com.google.common.eventbus.EventBus";
+ }
+
+}
--- /dev/null
+module test-config-files {
+ yang-version 1;
+ namespace "urn:opendaylight:params:xml:ns:yang:controller:config:test:files";
+ prefix "it-duplicate";
+
+ import config { prefix config; revision-date 2013-04-05; }
+ import ietf-inet-types { prefix inet; revision-date 2010-09-24;}
+
+
+ description
+ "Testing IMPL";
+
+ revision "2013-04-03" {
+ description
+ "Initial revision";
+ }
+
+ identity implementation {
+ base config:module-type;
+ config:java-name-prefix TestFileImpl;
+ }
+
+ identity netconf {
+ base config:module-type;
+ config:java-name-prefix NetconfTestFileImpl;
+ }
+
+ augment "/config:modules/config:module/config:configuration" {
+ case implementation {
+ when "/config:modules/config:module/config:type = 'implementation'";
+
+ container dto-a {
+ leaf simple-arg {
+ type uint32;
+ }
+
+ leaf port {
+ type inet:port-number;
+ }
+
+ }
+ }
+ }
+
+ augment "/config:modules/config:module/config:state" {
+ case implementation {
+ when "/config:modules/config:module/config:type = 'implementation'";
+ // root runtime bean
+ leaf created-sessions {
+ type uint32;
+ }
+ }
+ }
+
+ augment "/config:modules/config:module/config:configuration" {
+ case netconf {
+ when "/config:modules/config:module/config:type = 'netconf'";
+ }
+ }
+
+ augment "/config:modules/config:module/config:state" {
+ case netconf {
+ when "/config:modules/config:module/config:type = 'netconf'";
+ // root runtime bean
+ leaf created-sessions {
+ type uint32;
+ }
+
+ }
+ }
+}
--- /dev/null
+module test-config-files1 {
+ yang-version 1;
+ namespace "urn:opendaylight:params:xml:ns:yang:controller:config:test:files1";
+ prefix "it-duplicate";
+
+ import config { prefix config; revision-date 2013-04-05; }
+ import ietf-inet-types { prefix inet; revision-date 2010-09-24;}
+
+
+ description
+ "Testing IMPL";
+
+ revision "2013-04-03" {
+ description
+ "Initial revision";
+ }
+
+ identity implementation1 {
+ base config:module-type;
+ config:java-name-prefix TestFiles1Impl;
+ }
+
+ identity netconf1 {
+ base config:module-type;
+ config:java-name-prefix NetconfTestFiles1Impl;
+ }
+
+ augment "/config:modules/config:module/config:configuration" {
+ case implementation1 {
+ when "/config:modules/config:module/config:type = 'implementation1'";
+
+ container dto-a {
+ leaf simple-arg {
+ type uint32;
+ }
+
+ leaf port {
+ type inet:port-number;
+ }
+
+ }
+ }
+ }
+
+ augment "/config:modules/config:module/config:state" {
+ case implementation1 {
+ when "/config:modules/config:module/config:type = 'implementation1'";
+ // root runtime bean
+ leaf created-sessions {
+ type uint32;
+ }
+ }
+ }
+
+ augment "/config:modules/config:module/config:configuration" {
+ case netconf1 {
+ when "/config:modules/config:module/config:type = 'netconf1'";
+ }
+ }
+
+ augment "/config:modules/config:module/config:state" {
+ case netconf1 {
+ when "/config:modules/config:module/config:type = 'netconf1'";
+ // root runtime bean
+ leaf created-sessions {
+ type uint32;
+ }
+
+ }
+ }
+}
--- /dev/null
+module config-threads-java {
+ yang-version 1;
+ namespace "urn:opendaylight:params:xml:ns:yang:controller:config:threads:java";
+ prefix "th-java";
+
+ import config-threads { prefix th2; revision-date 2013-04-09; }
+ import config { prefix config; revision-date 2013-04-05; }
+ import rpc-context { prefix rpcx; revision-date 2013-06-17; }
+
+ description
+ "This module contains the base YANG definitions for NS-OS
+ thread services pure Java implementation.";
+
+ revision "2013-04-05" {
+ description
+ "Updated to work with new anchors.";
+ }
+
+ revision "2013-04-03" {
+ description
+ "Initial revision";
+ }
+
+ identity thread-rpc-context;
+
+ identity eventbus {
+ base config:module-type;
+ config:provided-service "th2:eventbus";
+ config:java-name-prefix EventBus;
+ }
+
+ identity async-eventbus {
+ base config:module-type;
+ config:provided-service "th2:eventbus";
+ config:java-name-prefix AsyncEventBus;
+ }
+
+ identity threadfactory-naming {
+ base config:module-type;
+ config:provided-service "th2:threadfactory";
+ config:java-name-prefix NamingThreadFactory;
+ }
+
+ identity threadpool-dynamic {
+ base config:module-type;
+ description "threadpool-dynamic description";
+ config:provided-service "th2:threadpool";
+ config:provided-service "th2:scheduled-threadpool";
+ config:java-name-prefix DynamicThreadPool;
+ }
+
+ augment "/config:modules/config:module/config:configuration" {
+ case eventbus {
+ when "/config:modules/config:module/config:type = 'eventbus'";
+ // No real configuration
+ }
+ }
+
+ augment "/config:modules/config:module/config:configuration" {
+ case async-eventbus {
+ when "/config:modules/config:module/config:type = 'async-eventbus'";
+ container threadpool {
+ uses config:service-ref {
+ refine type {
+ config:required-identity th2:threadpool;
+ }
+ }
+ }
+ }
+ }
+ augment "/config:modules/config:module/config:state" {
+ case async-eventbus {
+ when "/config:modules/config:module/config:type = 'async-eventbus'";
+ // simulate not having root runtime bean
+ list event {
+ config:inner-state-bean;
+ key "name";
+ leaf name {
+ type string;
+ }
+ }
+ }
+ }
+
+ typedef thread-state {
+ type enumeration {
+ enum "STARTED";
+ enum "STOPPED";
+ }
+ description "Enum type holding state in which a thread can be.";
+ }
+
+ augment "/config:modules/config:module/config:configuration" {
+ case threadfactory-naming {
+ when "/config:modules/config:module/config:type = 'threadfactory-naming'";
+ leaf name-prefix {
+ description "String that will be prefixed to each created thread. Suffix will be constructed from
+ underscore (_) and auto-incremented index number.";
+ type string;
+ }
+ }
+ }
+
+ augment "/config:modules/config:module/config:state" {
+ case threadfactory-naming {
+ when "/config:modules/config:module/config:type = 'threadfactory-naming'";
+ list thread {
+ config:inner-state-bean;
+ key "name";
+ leaf name {
+ type string;
+ }
+ // add rpc context
+ rpcx:rpc-context-instance "thread-rpc-context";
+
+ list stream {
+ config:inner-state-bean;
+ config:java-name-prefix ThreadStream;
+ // no key – key will be generated by incrementing a counter
+ leaf timestamp {
+ type string;
+ }
+ leaf state {
+ type thread-state;
+ }
+
+ container peer {
+ leaf port {
+ type uint32;
+ default 179;
+ }
+ leaf core-size {
+ type uint32;
+ }
+ }
+
+ list inner-stream-list {
+ leaf timestamp {
+ type string;
+ }
+ }
+
+ }
+ }
+
+ list stream {
+ config:inner-state-bean;
+
+ leaf timestamp {
+ type string;
+ }
+ }
+
+ // root runtime bean
+ leaf created-sessions {
+ type uint32;
+ }
+ }
+ }
+
+
+ rpc dump-stack {
+ config:java-name-prefix dumpStack;
+ input {
+ uses rpcx:rpc-context-ref {
+ refine context-instance {
+ rpcx:rpc-context-instance thread-rpc-context;
+ }
+ }
+ }
+ }
+
+ rpc sleep {
+ input {
+ uses rpcx:rpc-context-ref {
+ refine context-instance {
+ rpcx:rpc-context-instance thread-rpc-context;
+ }
+ }
+ leaf millis {
+ type uint32;
+ }
+ }
+ output {
+ leaf result {
+ type thread-state;
+ }
+ }
+ }
+
+
+ augment "/config:modules/config:module/config:configuration" {
+ case threadpool-dynamic {
+ when "/config:modules/config:module/config:type = 'threadpool-dynamic'";
+ leaf core-size {
+ type uint32;
+ }
+
+ leaf keep-alive {
+ type uint32;
+ units seconds;
+ default 10;
+ }
+
+ leaf maximum-size {
+ type uint32;
+ description "maximum-size description";
+ }
+
+ leaf binary {
+ type binary;
+ }
+
+ container threadfactory {
+ description "threadfactory description";
+ uses config:service-ref {
+ refine type {
+ mandatory true;
+ config:required-identity th2:threadfactory;
+ }
+ }
+ }
+
+ leaf-list users {
+ type string;
+ }
+
+ leaf-list users-numbers {
+ type uint32;
+ description "numbers of users description";
+ }
+ }
+ }
+
+ augment "/config:modules/config:module/config:state" {
+ case threadpool-dynamic {
+ when "/config:modules/config:module/config:type = 'threadpool-dynamic'";
+ // root runtime bean
+ leaf created-sessions {
+ type uint32;
+ }
+ }
+ }
+}
--- /dev/null
+module config-threads {
+ yang-version 1;
+ namespace "urn:opendaylight:params:xml:ns:yang:controller:config:threads";
+ prefix "th";
+
+ import config { prefix config; revision-date 2013-04-05; }
+
+ description
+ "This module contains the base YANG definitions for NS-OS
+ thread-related services.";
+
+ revision "2013-04-09" {
+ description
+ "Added eventbus service.";
+ }
+
+ revision "2013-04-05" {
+ description
+ "Updated with YANG extension for Java class specification.";
+ }
+
+ revision "2013-04-03" {
+ description
+ "Initial revision";
+ }
+
+ identity eventbus {
+ description
+ "Service representing an event bus. The service acts as message
+ router between event producers and event consumers";
+
+ base "config:service-type";
+ config:java-class "com.google.common.eventbus.EventBus";
+ }
+
+ identity threadfactory {
+ description
+ "Service representing a ThreadFactory instance. It is directly
+ useful in Java world, where various library pieces need to create
+ threads and you may want to inject a customized thread
+ implementation.";
+
+ base "config:service-type";
+ config:java-class "java.util.concurrent.ThreadFactory";
+ }
+
+ identity threadpool {
+ description
+ "A simple pool of threads able to execute work.";
+
+ base "config:service-type";
+ config:java-class "org.opendaylight.controller.config.threadpool.ThreadPool";
+ }
+
+ identity scheduled-threadpool {
+ description
+ "An extension of the simple pool of threads able to schedule
+ work to be executed at some point in time.";
+
+ base "threadpool";
+ config:java-class "org.opendaylight.controller.config.threadpool.ScheduledThreadPool";
+ }
+}
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <artifactId>config-subsystem</artifactId>
+ <groupId>org.opendaylight</groupId>
+ <version>0.2.1-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+ <artifactId>yang-store-api</artifactId>
+ <name>${project.artifactId}</name>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight</groupId>
+ <artifactId>yang-jmx-generator</artifactId>
+ <version>0.2.1-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+
+ <Private-Package>
+ </Private-Package>
+
+ <Import-Package>
+ org.opendaylight.controller.config.yangjmxgenerator,
+ org.opendaylight.yangtools.yang.model.api
+ </Import-Package>
+ <Export-Package>
+ org.opendaylight.controller.config.yang.store.api
+ </Export-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ <!-- test jar -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yang.store.api;
+
+public class YangStoreException extends Exception {
+
+ private static final long serialVersionUID = 2841238836278528836L;
+
+ public YangStoreException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yang.store.api;
+
+/**
+ * Yang store OSGi service
+ */
+public interface YangStoreService {
+
+ /**
+ * Module entry objects mapped to module names and namespaces.
+ *
+ * @return actual view of what is available in OSGi service registry.
+ */
+ YangStoreSnapshot getYangStoreSnapshot() throws YangStoreException;
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yang.store.api;
+
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntry;
+import org.opendaylight.yangtools.yang.model.api.Module;
+
+public interface YangStoreSnapshot extends AutoCloseable {
+
+ Map<String, Map<String, ModuleMXBeanEntry>> getModuleMXBeanEntryMap();
+
+ Map<String, Entry<Module, String>> getModuleMap();
+
+ int countModuleMXBeanEntries();
+
+ @Override
+ void close();
+}
--- /dev/null
+target
+.classpath
+.settings
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <artifactId>config-subsystem</artifactId>
+ <groupId>org.opendaylight</groupId>
+ <version>0.2.1-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+ <artifactId>yang-store-impl</artifactId>
+ <name>${project.artifactId}</name>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>yang-store-api</artifactId>
+ <version>0.2.1-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>yang-jmx-generator</artifactId>
+ <version>0.2.1-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>binding-generator-impl</artifactId>
+ <version>${opendaylight.binding.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.bgpcep</groupId>
+ <artifactId>mockito-configuration</artifactId>
+ <version>0.2.0-SNAPSHOT</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Bundle-Activator>org.opendaylight.controller.config.yang.store.impl.YangStoreActivator
+ </Bundle-Activator>
+ <Private-Package>
+ org.opendaylight.controller.config.yang.store.impl,
+ <!-- OPENDAYLIGHT -->
+ org.opendaylight.yangtools.sal.binding.yang.types,
+ org.opendaylight.yangtools.yang.parser.impl,
+ org.opendaylight.yangtools.antlrv4.code.gen,
+ org.opendaylight.yangtools.yang.model.parser.api,
+
+ org.opendaylight.yangtools.yang.model.util,
+
+ org.opendaylight.yangtools.yang.parser.builder.api,
+ org.opendaylight.yangtools.yang.parser.builder.impl,
+
+ org.opendaylight.yangtools.yang.parser.impl,
+ org.opendaylight.yangtools.yang.parser.util,
+
+ org.opendaylight.yangtools.yang.validator,
+ <!-- ANTLR -->
+
+ org.antlr.v4.runtime,
+ org.antlr.v4.runtime.tree,
+ org.antlr.v4.runtime.atn,
+ org.antlr.v4.runtime.dfa,
+ org.antlr.v4.runtime.misc,
+ org.antlr.v4.runtime.tree.gui,
+ org.abego.treelayout,
+ org.abego.treelayout.util,
+ org.abego.treelayout.internal.util,
+ org.abego.treelayout.internal.util.java.lang,
+ org.abego.treelayout.internal.util.java.lang.string,
+ org.abego.treelayout.internal.util.java.util,
+
+ <!-- xtext -->
+ org.eclipse.xtext.xbase.lib.*,
+ org.apache.commons.lang.*,
+ </Private-Package>
+
+ <Import-Package>
+ org.opendaylight.controller.config.yang.store.api,
+ org.opendaylight.controller.config.yangjmxgenerator,
+ com.google.common.base,
+ com.google.common.collect,
+ com.google.common.primitives,
+ org.apache.commons.io,
+ org.osgi.framework,
+ org.osgi.util.tracker,
+ org.slf4j,
+ javax.*,
+ <!-- OPENDAYLIGHT PROVIDED BY YANG-JMX-GENERATOR -->
+ org.opendaylight.yangtools.yang.common,
+ org.opendaylight.yangtools.yang.model.api,
+ org.opendaylight.yangtools.yang.model.api.type,
+
+ org.opendaylight.yangtools.yang.binding,
+
+ org.opendaylight.yangtools.binding.generator.util,
+ org.opendaylight.yangtools.sal.binding.generator.spi,
+ org.opendaylight.yangtools.sal.binding.model.api,
+
+ org.opendaylight.yangtools.binding.generator.util.generated.type.builder,
+ org.opendaylight.yangtools.sal.binding.model.api.type.builder,
+ </Import-Package>
+ <Export-Package>
+ </Export-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ <!-- test jar -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yang.store.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Set;
+
+import org.opendaylight.controller.config.yang.store.api.YangStoreException;
+import org.opendaylight.controller.config.yang.store.api.YangStoreService;
+import org.opendaylight.controller.config.yang.store.api.YangStoreSnapshot;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.util.tracker.BundleTracker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+
+public class ExtenderYangTracker extends BundleTracker<Object> implements
+ YangStoreService {
+
+ private static final Logger logger = LoggerFactory
+ .getLogger(ExtenderYangTracker.class);
+
+ private final Multimap<Bundle, URL> bundlesToYangURLs = HashMultimap
+ .create();
+ private final YangStoreCache cache = new YangStoreCache();
+ private final MbeParser mbeParser;
+
+ public ExtenderYangTracker(BundleContext context) {
+ this(context, new MbeParser());
+
+ }
+
+ @VisibleForTesting
+ ExtenderYangTracker(BundleContext context, MbeParser mbeParser) {
+ super(context, Bundle.ACTIVE, null);
+ this.mbeParser = mbeParser;
+ logger.trace("Registered as extender with context {}", context);
+ }
+
+ @Override
+ public Object addingBundle(Bundle bundle, BundleEvent event) {
+
+ // Ignore system bundle
+ //
+ // system bundle has config-api on classpath &&
+ // config-api contains yang files =>
+ // system bundle contains yang files from that bundle
+ if (bundle.getBundleId() == 0)
+ return bundle;
+
+ Enumeration<URL> yangURLs = bundle.findEntries("META-INF/yang",
+ "*.yang", false);
+
+ if (yangURLs == null)
+ return bundle;
+
+ synchronized (this) {
+ while (yangURLs.hasMoreElements()) {
+ URL yang = yangURLs.nextElement();
+ logger.debug("Bundle {} found yang file {}", bundle, yang);
+ bundlesToYangURLs.put(bundle, yang);
+ }
+ }
+
+ return bundle;
+ }
+
+ @Override
+ public void removedBundle(Bundle bundle, BundleEvent event, Object object) {
+ synchronized (this) {
+ Collection<URL> urls = bundlesToYangURLs.removeAll(bundle);
+ logger.debug(
+ "Removed following yang URLs {} because of removed bundle {}",
+ urls, bundle);
+ }
+ }
+
+ @Override
+ public synchronized YangStoreSnapshot getYangStoreSnapshot()
+ throws YangStoreException {
+ Optional<YangStoreSnapshot> yangStoreOpt = cache
+ .getCachedYangStore(bundlesToYangURLs);
+ if (yangStoreOpt.isPresent()) {
+ logger.debug("Returning cached yang store {}", yangStoreOpt.get());
+ return yangStoreOpt.get();
+ }
+
+ try {
+ YangStoreSnapshot yangStoreSnapshot = mbeParser
+ .parseYangFiles(fromUrlsToInputStreams());
+ logger.debug(
+ "{} module entries parsed successfully from {} yang files",
+ yangStoreSnapshot.countModuleMXBeanEntries(),
+ bundlesToYangURLs.values().size());
+ cache.cacheYangStore(bundlesToYangURLs, yangStoreSnapshot);
+
+ return yangStoreSnapshot;
+ } catch (RuntimeException e) {
+ logger.warn(
+ "Unable to parse yang files, yang files that were picked up so far: {}",
+ bundlesToYangURLs, e);
+ throw new YangStoreException("Unable to parse yang files", e);
+ }
+ }
+
+ private Collection<InputStream> fromUrlsToInputStreams() {
+ return Collections2.transform(bundlesToYangURLs.values(),
+ new Function<URL, InputStream>() {
+
+ @Override
+ public InputStream apply(URL url) {
+ try {
+ return url.openStream();
+ } catch (IOException e) {
+ logger.warn("Unable to open stream from {}", url);
+ throw new IllegalStateException(
+ "Unable to open stream from " + url, e);
+ }
+ }
+ });
+ }
+
+ private static final class YangStoreCache {
+
+ Set<URL> cachedUrls;
+ YangStoreSnapshot cachedYangStoreSnapshot;
+
+ Optional<YangStoreSnapshot> getCachedYangStore(
+ Multimap<Bundle, URL> bundlesToYangURLs) {
+ Set<URL> urls = setFromMultimapValues(bundlesToYangURLs);
+ if (cachedUrls != null && cachedUrls.equals(urls)) {
+ Preconditions.checkState(cachedYangStoreSnapshot != null);
+ return Optional.of(cachedYangStoreSnapshot);
+ }
+ return Optional.absent();
+ }
+
+ private static Set<URL> setFromMultimapValues(
+ Multimap<Bundle, URL> bundlesToYangURLs) {
+ Set<URL> urls = Sets.newHashSet(bundlesToYangURLs.values());
+ Preconditions.checkState(bundlesToYangURLs.size() == urls.size());
+ return urls;
+ }
+
+ void cacheYangStore(Multimap<Bundle, URL> urls,
+ YangStoreSnapshot yangStoreSnapshot) {
+ this.cachedUrls = setFromMultimapValues(urls);
+ this.cachedYangStoreSnapshot = yangStoreSnapshot;
+ }
+
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yang.store.impl;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.commons.io.IOUtils;
+import org.opendaylight.controller.config.yang.store.api.YangStoreException;
+import org.opendaylight.controller.config.yang.store.api.YangStoreSnapshot;
+import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntry;
+import org.opendaylight.controller.config.yangjmxgenerator.PackageTranslator;
+import org.opendaylight.controller.config.yangjmxgenerator.ServiceInterfaceEntry;
+import org.opendaylight.controller.config.yangjmxgenerator.TypeProviderWrapper;
+import org.opendaylight.yangtools.sal.binding.yang.types.TypeProviderImpl;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+public class MbeParser {
+
+ public YangStoreSnapshot parseYangFiles(
+ Collection<? extends InputStream> allInput)
+ throws YangStoreException {
+ YangParserImpl parser = new YangParserImpl();
+
+ List<InputStream> bufferedInputStreams = new ArrayList<>();
+ for (InputStream is : allInput) {
+ String content;
+ try {
+ content = IOUtils.toString(is);
+ } catch (IOException e) {
+ throw new YangStoreException("Can not get yang as String from "
+ + is, e);
+ }
+ InputStream buf = new ByteArrayInputStream(content.getBytes());
+ bufferedInputStreams.add(buf);
+ }
+
+ Map<InputStream, Module> allYangModules = parser
+ .parseYangModelsFromStreamsMapped(bufferedInputStreams);
+
+ SchemaContext resolveSchemaContext = parser.resolveSchemaContext(Sets
+ .newHashSet(allYangModules.values()));
+
+ // JMX generator
+
+ Map<String, String> namespaceToPackageMapping = Maps.newHashMap();
+ PackageTranslator packageTranslator = new PackageTranslator(
+ namespaceToPackageMapping);
+
+ Map<QName, ServiceInterfaceEntry> qNamesToSIEs = new HashMap<>();
+
+ // create SIE structure qNamesToSIEs
+ for (Module module : resolveSchemaContext.getModules()) {
+ String packageName = packageTranslator.getPackageName(module);
+ Map<QName, ServiceInterfaceEntry> namesToSIEntries = ServiceInterfaceEntry
+ .create(module, packageName);
+
+ for (Entry<QName, ServiceInterfaceEntry> sieEntry : namesToSIEntries
+ .entrySet()) {
+
+ // merge value into qNamesToSIEs
+ if (qNamesToSIEs.containsKey(sieEntry.getKey()) == false) {
+ qNamesToSIEs.put(sieEntry.getKey(), sieEntry.getValue());
+ } else {
+ throw new IllegalStateException(
+ "Cannot add two SIE with same qname "
+ + sieEntry.getValue());
+ }
+ }
+ }
+
+ Map<String, Map<String, ModuleMXBeanEntry>> retVal = Maps.newHashMap();
+ Map<String, Entry<Module, String>> modulesMap = new HashMap<>();
+
+ for (Entry<InputStream, Module> moduleEntry : allYangModules.entrySet()) {
+ String packageName = packageTranslator.getPackageName(moduleEntry
+ .getValue());
+ TypeProviderWrapper typeProviderWrapper = new TypeProviderWrapper(
+ new TypeProviderImpl(resolveSchemaContext));
+ String yangAsString;
+ try {
+ moduleEntry.getKey().reset();
+ yangAsString = IOUtils.toString(moduleEntry.getKey());
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ modulesMap.put(moduleEntry.getValue().getName(),
+ Maps.immutableEntry(moduleEntry.getValue(), yangAsString));
+ Map<String /* MB identity local name */, ModuleMXBeanEntry> namesToMBEs = ModuleMXBeanEntry
+ .create(moduleEntry.getValue(), qNamesToSIEs, resolveSchemaContext, typeProviderWrapper,
+ packageName);
+ retVal.put(moduleEntry.getValue().getNamespace().toString(),
+ namesToMBEs);
+ }
+
+ return new YangStoreSnapshotImpl(retVal, modulesMap);
+ }
+
+ public Map<Module, String> parseYangFilesToString(
+ Collection<? extends InputStream> allYangs) {
+ YangParserImpl parser = new YangParserImpl();
+
+ Map<InputStream, Module> allYangModules = parser
+ .parseYangModelsFromStreamsMapped(Lists.newArrayList(allYangs));
+ Map<Module, String> retVal = new HashMap<>();
+
+ for (Entry<InputStream, Module> entry : allYangModules.entrySet()) {
+ try {
+ retVal.put(entry.getValue(), IOUtils.toString(entry.getKey()));
+ } catch (IOException e) {
+ throw new IllegalStateException(
+ "Can not create string from yang file.");
+ }
+ }
+ return retVal;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yang.store.impl;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.opendaylight.controller.config.yang.store.api.YangStoreService;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class YangStoreActivator implements BundleActivator {
+
+ private ExtenderYangTracker extender;
+ private ServiceRegistration<YangStoreService> registration;
+ private static final Logger logger = LoggerFactory
+ .getLogger(YangStoreActivator.class);
+
+ @Override
+ public void start(BundleContext context) throws Exception {
+ extender = new ExtenderYangTracker(context);
+ extender.open();
+
+ Dictionary<String, ?> properties = new Hashtable<>();
+ registration = context.registerService(YangStoreService.class,
+ extender, properties);
+ }
+
+ @Override
+ public void stop(BundleContext context) throws Exception {
+ try {
+ extender.close();
+ } catch (Exception e) {
+ logger.warn("Exception while closing extender", e);
+ }
+
+ if (registration != null)
+ try {
+ registration.unregister();
+ } catch (Exception e) {
+ logger.warn("Exception while unregistring yang store service",
+ e);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yang.store.impl;
+
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.opendaylight.controller.config.yang.store.api.YangStoreSnapshot;
+import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntry;
+import org.opendaylight.yangtools.yang.model.api.Module;
+
+public class YangStoreSnapshotImpl implements YangStoreSnapshot {
+
+ private final Map<String /* Namespace from yang file */, Map<String /*
+ * Name
+ * of
+ * module
+ * entry
+ * from
+ * yang
+ * file
+ */, ModuleMXBeanEntry>> moduleMXBeanEntryMap;
+
+ private final Map<String, Entry<Module, String>> moduleMap;
+
+ public YangStoreSnapshotImpl(
+ Map<String, Map<String, ModuleMXBeanEntry>> moduleMXBeanEntryMap,
+ Map<String, Entry<Module, String>> moduleMap) {
+ this.moduleMXBeanEntryMap = moduleMXBeanEntryMap;
+ this.moduleMap = moduleMap;
+ }
+
+ @Override
+ public Map<String, Map<String, ModuleMXBeanEntry>> getModuleMXBeanEntryMap() {
+ return moduleMXBeanEntryMap;
+ }
+
+ @Override
+ public Map<String, Entry<Module, String>> getModuleMap() {
+ return moduleMap;
+ }
+
+ @Override
+ public int countModuleMXBeanEntries() {
+ int i = 0;
+ for (Map<String, ModuleMXBeanEntry> value : moduleMXBeanEntryMap
+ .values()) {
+ i += value.keySet().size();
+ }
+ return i;
+ }
+
+ @Override
+ public void close() {
+ // TODO: reference counting
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yang.store.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.anyCollectionOf;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.opendaylight.controller.config.yang.store.api.YangStoreException;
+import org.opendaylight.controller.config.yang.store.api.YangStoreSnapshot;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+
+import com.google.common.collect.Lists;
+
+public class ExtenderYangTrackerTest {
+
+ @Mock
+ private BundleContext context;
+ private ExtenderYangTracker tested;
+ @Mock
+ private MbeParser parser;
+ @Mock
+ private YangStoreSnapshot yangStoreSnapshot;
+
+ @Before
+ public void setUp() throws YangStoreException {
+ MockitoAnnotations.initMocks(this);
+ doReturn("context").when(context).toString();
+ tested = new ExtenderYangTracker(context, parser);
+ doReturn(yangStoreSnapshot).when(parser).parseYangFiles(
+ anyCollectionOf(InputStream.class));
+ doReturn(22).when(yangStoreSnapshot).countModuleMXBeanEntries();
+ doReturn("mock yang store").when(yangStoreSnapshot).toString();
+ }
+
+ @Test
+ public void testCache() throws MalformedURLException, YangStoreException,
+ InterruptedException {
+ Bundle bundle = getMockedBundle(5, false);
+ tested.addingBundle(bundle, null);
+ bundle = getMockedBundle(2, false);
+ tested.addingBundle(bundle, null);
+ bundle = getMockedBundle(10, false);
+ tested.addingBundle(bundle, null);
+ YangStoreSnapshot returnedStore;
+
+ returnedStore = tested.getYangStoreSnapshot();
+
+ assertEquals(yangStoreSnapshot, returnedStore);
+
+ tested.removedBundle(bundle, null, null);
+ tested.getYangStoreSnapshot();
+
+ bundle = getMockedBundle(10, false);
+ tested.addingBundle(bundle, null);
+
+ tested.getYangStoreSnapshot();
+
+ verify(parser, times(3)).parseYangFiles(
+ anyCollectionOf(InputStream.class));
+
+ returnedStore = tested.getYangStoreSnapshot();
+
+ verifyNoMoreInteractions(parser);
+ assertEquals(yangStoreSnapshot, returnedStore);
+ }
+
+ int bundleCounter = 1;
+
+ private Bundle getMockedBundle(int sizeOfUrls, boolean system)
+ throws MalformedURLException {
+ Bundle mock = mock(Bundle.class);
+
+ List<URL> urls = Lists.newArrayList();
+ for (int i = 0; i < sizeOfUrls; i++) {
+ urls.add(new URL("http://127.0." + bundleCounter++ + "." + i));
+ }
+ Enumeration<URL> abc = new TestEnumeration(urls);
+
+ doReturn(abc).when(mock).findEntries("META-INF/yang", "*.yang", false);
+ if (system)
+ doReturn(0L).when(mock).getBundleId();
+ else
+ doReturn(1L).when(mock).getBundleId();
+
+ doReturn("mockedBundle").when(mock).toString();
+
+ return mock;
+ }
+
+ private static final class TestEnumeration implements Enumeration<URL> {
+
+ private final List<URL> urls;
+ int currentPos = 0;
+
+ public TestEnumeration(List<URL> urls) {
+ this.urls = urls;
+ }
+
+ @Override
+ public boolean hasMoreElements() {
+ try {
+ urls.get(currentPos);
+ } catch (IndexOutOfBoundsException e) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public URL nextElement() {
+ URL url = urls.get(currentPos++);
+ return url;
+ }
+
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 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.controller.config.yang.store.impl;
+
+import static org.junit.Assert.assertNotNull;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.apache.commons.io.IOUtils;
+import org.opendaylight.controller.config.yang.store.api.YangStoreException;
+import org.opendaylight.controller.config.yang.store.api.YangStoreService;
+import org.opendaylight.controller.config.yang.store.api.YangStoreSnapshot;
+
+public class HardcodedYangStoreService implements YangStoreService {
+
+ private final Collection<ByteArrayInputStream> byteArrayInputStreams;
+
+ public HardcodedYangStoreService(
+ Collection<? extends InputStream> inputStreams)
+ throws YangStoreException, IOException {
+ byteArrayInputStreams = new ArrayList<>();
+ for (InputStream inputStream : inputStreams) {
+ assertNotNull(inputStream);
+ byte[] content = IOUtils.toByteArray(inputStream);
+ ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(
+ content);
+ byteArrayInputStreams.add(byteArrayInputStream);
+ }
+ }
+
+ @Override
+ public YangStoreSnapshot getYangStoreSnapshot() throws YangStoreException {
+ for (InputStream inputStream : byteArrayInputStreams) {
+ try {
+ inputStream.reset();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return new MbeParser().parseYangFiles(byteArrayInputStreams);
+ }
+}
--- /dev/null
+Test code generator, namely generating abstract classes to target/generated-sources/config folder.
+Currently files generated to src are modified - getInstance() method must be implemented, so the body is replaced
+manually with following snippet:
+ return new AutoCloseable() {
+ @Override
+ public void close() throws Exception {
+ }
+ };
+TODO: clean src/main/java directory and replace generated body during build.
--- /dev/null
+<project
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+ xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight</groupId>
+ <artifactId>config-subsystem</artifactId>
+ <version>0.2.1-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>yang-test</artifactId>
+
+ <description>Artifact that contains only generated code from yang files. Suitable for testing.
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>config-api</artifactId>
+ <version>0.2.1-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ </dependencies>
+
+ <name>${project.artifactId}</name>
+ <prerequisites>
+ <maven>3.0.4</maven>
+ </prerequisites>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-maven-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+</project>
--- /dev/null
+/**
+ * Generated file
+
+ * Generated from: yang module name: config-test-impl yang module local name: impl-dep
+ * Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator
+ * Generated at: Fri Sep 27 13:02:28 CEST 2013
+ *
+ * Do not modify this file unless it is present under src/main directory
+ */
+package org.opendaylight.controller.config.yang.test.impl;
+
+/**
+*
+*/
+public final class DepTestImplModule
+ extends
+ org.opendaylight.controller.config.yang.test.impl.AbstractDepTestImplModule {
+
+ public DepTestImplModule(
+ org.opendaylight.controller.config.api.ModuleIdentifier name,
+ org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) {
+ super(name, dependencyResolver);
+ }
+
+ public DepTestImplModule(
+ org.opendaylight.controller.config.api.ModuleIdentifier name,
+ org.opendaylight.controller.config.api.DependencyResolver dependencyResolver,
+ DepTestImplModule oldModule, java.lang.AutoCloseable oldInstance) {
+ super(name, dependencyResolver, oldModule, oldInstance);
+ }
+
+ @Override
+ public void validate() {
+ super.validate();
+ // Add custom validation for module attributes here.
+ }
+
+ @Override
+ public java.lang.AutoCloseable createInstance() {
+ return new AutoCloseable() {
+ @Override
+ public void close() throws Exception {
+ }
+ };
+ }
+}
--- /dev/null
+/**
+ * Generated file
+
+ * Generated from: yang module name: config-test-impl yang module local name: impl-dep
+ * Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator
+ * Generated at: Fri Sep 27 13:02:28 CEST 2013
+ *
+ * Do not modify this file unless it is present under src/main directory
+ */
+package org.opendaylight.controller.config.yang.test.impl;
+
+/**
+*
+*/
+public class DepTestImplModuleFactory
+ extends
+ org.opendaylight.controller.config.yang.test.impl.AbstractDepTestImplModuleFactory {
+
+}
--- /dev/null
+/**
+ * Generated file
+
+ * Generated from: yang module name: config-test-impl yang module local name: impl-netconf
+ * Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator
+ * Generated at: Fri Sep 27 13:02:28 CEST 2013
+ *
+ * Do not modify this file unless it is present under src/main directory
+ */
+package org.opendaylight.controller.config.yang.test.impl;
+
+/**
+*
+*/
+public final class NetconfTestImplModule
+ extends
+ org.opendaylight.controller.config.yang.test.impl.AbstractNetconfTestImplModule {
+
+ public NetconfTestImplModule(
+ org.opendaylight.controller.config.api.ModuleIdentifier name,
+ org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) {
+ super(name, dependencyResolver);
+ }
+
+ public NetconfTestImplModule(
+ org.opendaylight.controller.config.api.ModuleIdentifier name,
+ org.opendaylight.controller.config.api.DependencyResolver dependencyResolver,
+ NetconfTestImplModule oldModule, java.lang.AutoCloseable oldInstance) {
+ super(name, dependencyResolver, oldModule, oldInstance);
+ }
+
+ @Override
+ public void validate() {
+ super.validate();
+ // Add custom validation for module attributes here.
+ }
+
+ @Override
+ public java.lang.AutoCloseable createInstance() {
+ return new AutoCloseable() {
+ @Override
+ public void close() throws Exception {
+ }
+ };
+ }
+}
--- /dev/null
+/**
+ * Generated file
+
+ * Generated from: yang module name: config-test-impl yang module local name: impl-netconf
+ * Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator
+ * Generated at: Fri Sep 27 13:02:28 CEST 2013
+ *
+ * Do not modify this file unless it is present under src/main directory
+ */
+package org.opendaylight.controller.config.yang.test.impl;
+
+/**
+*
+*/
+public class NetconfTestImplModuleFactory
+ extends
+ org.opendaylight.controller.config.yang.test.impl.AbstractNetconfTestImplModuleFactory {
+
+}
--- /dev/null
+/**
+ * Generated file
+
+ * Generated from: yang module name: config-test-impl yang module local name: impl
+ * Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator
+ * Generated at: Fri Sep 27 13:02:28 CEST 2013
+ *
+ * Do not modify this file unless it is present under src/main directory
+ */
+package org.opendaylight.controller.config.yang.test.impl;
+
+/**
+*
+*/
+public final class TestImplModule
+ extends
+ org.opendaylight.controller.config.yang.test.impl.AbstractTestImplModule {
+
+ public TestImplModule(
+ org.opendaylight.controller.config.api.ModuleIdentifier name,
+ org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) {
+ super(name, dependencyResolver);
+ }
+
+ public TestImplModule(
+ org.opendaylight.controller.config.api.ModuleIdentifier name,
+ org.opendaylight.controller.config.api.DependencyResolver dependencyResolver,
+ TestImplModule oldModule, java.lang.AutoCloseable oldInstance) {
+ super(name, dependencyResolver, oldModule, oldInstance);
+ }
+
+ @Override
+ public void validate() {
+ super.validate();
+ // Add custom validation for module attributes here.
+ }
+
+ @Override
+ public java.lang.AutoCloseable createInstance() {
+ return new AutoCloseable() {
+ @Override
+ public void close() throws Exception {
+ }
+ };
+ }
+}
--- /dev/null
+/**
+ * Generated file
+
+ * Generated from: yang module name: config-test-impl yang module local name: impl
+ * Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator
+ * Generated at: Fri Sep 27 13:02:28 CEST 2013
+ *
+ * Do not modify this file unless it is present under src/main directory
+ */
+package org.opendaylight.controller.config.yang.test.impl;
+
+/**
+*
+*/
+public class TestImplModuleFactory
+ extends
+ org.opendaylight.controller.config.yang.test.impl.AbstractTestImplModuleFactory {
+
+}
--- /dev/null
+module config-test-impl {
+ yang-version 1;
+ namespace "urn:opendaylight:params:xml:ns:yang:controller:test:impl";
+ prefix "it-impl";
+
+ import config-test { prefix test; revision-date 2013-06-13;}
+ import config { prefix config; revision-date 2013-04-05; }
+ import ietf-inet-types { prefix inet; revision-date 2010-09-24;}
+ import rpc-context { prefix rpcx; revision-date 2013-06-17; }
+
+
+ description
+ "Testing IMPL";
+
+ revision "2013-04-03" {
+ description
+ "Initial revision";
+ }
+
+ identity impl {
+ base config:module-type;
+ config:provided-service test:testing;
+ config:java-name-prefix TestImpl;
+ }
+
+ identity impl-dep {
+ base config:module-type;
+ config:provided-service test:testing;
+ config:java-name-prefix DepTestImpl;
+ }
+
+ identity impl-netconf {
+ base config:module-type;
+ config:provided-service test:testing;
+ config:java-name-prefix NetconfTestImpl;
+ }
+
+
+ augment "/config:modules/config:module/config:configuration" {
+ case impl {
+ when "/config:modules/config:module/config:type = 'impl'";
+
+ leaf-list allow-user {
+ type string;
+ description "A list of user name patterns to allow";
+ }
+
+ container dto-a {
+ leaf simple-arg {
+ type uint32;
+ }
+
+ leaf port {
+ type inet:port-number;
+ }
+
+ }
+
+ leaf as-number {
+ mandatory true;
+ type inet:as-number;
+ }
+
+
+ leaf simpleInt {
+ type uint32;
+ default 99L;
+ }
+
+ container dto_b {
+ leaf simple-int1 {
+ type uint32;
+ }
+
+ leaf simple-int2 {
+ type uint32;
+ }
+ }
+
+ }
+ }
+
+ augment "/config:modules/config:module/config:state" {
+ case impl {
+ when "/config:modules/config:module/config:type = 'impl'";
+ // root runtime bean
+ leaf created-sessions {
+ type uint32;
+ }
+ }
+ }
+
+ augment "/config:modules/config:module/config:configuration" {
+ case impl-dep {
+ when "/config:modules/config:module/config:type = 'impl-dep'";
+ }
+ }
+
+ augment "/config:modules/config:module/config:configuration" {
+ case impl-netconf {
+ when "/config:modules/config:module/config:type = 'impl-netconf'";
+ leaf binaryLeaf {
+ type binary;
+ }
+
+ leaf type {
+ type string;
+ }
+
+ container dto-c {
+ leaf simple-arg {
+ type uint32;
+ }
+
+ container dto-a-inner {
+ leaf simple-arg {
+ type uint32;
+ }
+
+ container dto-a-inner-inner {
+ leaf simple-arg {
+ type uint32;
+ }
+ }
+ }
+ }
+
+ leaf simpleInt {
+ type uint32;
+ }
+
+ leaf simpleBoolean {
+ type boolean;
+ default false;
+ }
+
+ leaf simple-long {
+ type int64 ;
+ }
+
+ leaf simple-long-2 {
+ type uint32;
+ }
+
+ leaf simple-BigInteger {
+ type uint64;
+ }
+
+ leaf simple-byte {
+ type int8;
+ }
+
+ leaf simple-short {
+ type uint8;
+ }
+
+ leaf simple-test {
+ type uint16;
+ default 99;
+ }
+
+ leaf-list simple-list {
+ type uint16;
+ }
+
+ container dto_d {
+ leaf simple-int1 {
+ type uint32;
+ }
+
+ leaf simple-int2 {
+ type uint32;
+ }
+
+ leaf simple-int3 {
+ type uint16;
+ }
+
+ leaf-list simple-list {
+ type uint16;
+ }
+
+ list complex-dto-bInner {
+ leaf-list simple-list {
+ type uint16;
+ }
+ leaf simple-int3 {
+ type uint16;
+ }
+
+ container deep {
+ leaf simple-int3 {
+ type uint16;
+ }
+ }
+ }
+ }
+
+ list complex-list {
+ list simple-list {
+ leaf simple-int3 {
+ type uint16;
+ }
+ }
+ }
+
+ list peers {
+ leaf port {
+ type string;
+ }
+ leaf core-size {
+ type uint32;
+ }
+ leaf simple-int3 {
+ type uint16;
+ }
+ }
+
+ container testing-dep {
+ uses config:service-ref {
+ refine type {
+ mandatory true;
+ config:required-identity test:testing;
+ }
+ }
+ }
+ }
+ }
+
+ augment "/config:modules/config:module/config:state" {
+ case impl-netconf {
+ when "/config:modules/config:module/config:type = 'impl-netconf'";
+ // rpc
+ rpcx:rpc-context-instance "test-rpc";
+
+ // root runtime bean
+ leaf created-sessions {
+ type uint32;
+ }
+
+ container asdf {
+ leaf simpleInt {
+ type uint16;
+ }
+
+ leaf simpleString {
+ type string;
+ }
+ }
+
+
+ list inner-running-data-additional {
+ config:inner-state-bean;
+
+ // rpc
+ rpcx:rpc-context-instance "inner-test-rpc";
+
+ key "simpleString";
+
+ leaf simple-int3 {
+ type uint16;
+ }
+
+ leaf simpleString {
+ type string;
+ }
+
+ container deep4 {
+ leaf boool {
+ type boolean;
+ }
+ }
+ }
+
+ list inner-running-data {
+ config:inner-state-bean;
+
+ key "simple-int3";
+
+ leaf simple-int3 {
+ type uint16;
+ }
+
+ container deep2 {
+ leaf boool {
+ type boolean;
+ }
+ }
+
+ list inner-inner-running-data {
+ config:inner-state-bean;
+
+ rpcx:rpc-context-instance "inner-inner-test-rpc";
+
+ key "simple-int3";
+
+ leaf simple-int3 {
+ type uint16;
+ }
+
+ list not-state-bean {
+ leaf element {
+ type string;
+ }
+
+ list not-state-bean-internal {
+ // This should be ignored
+ config:inner-state-bean;
+
+ leaf element2 {
+ type string;
+ }
+ }
+ }
+
+ container deep3 {
+ leaf boool {
+ type boolean;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ identity test-rpc;
+ identity inner-test-rpc;
+ identity inner-inner-test-rpc;
+
+ rpc no-arg {
+ input {
+ uses rpcx:rpc-context-ref {
+ refine context-instance {
+ rpcx:rpc-context-instance test-rpc;
+ }
+ }
+ leaf arg1 {
+ type string;
+ }
+ }
+
+ output {
+ leaf result {
+ type string;
+ }
+ }
+ }
+
+ rpc noArgInner {
+ input {
+ uses rpcx:rpc-context-ref {
+ refine context-instance {
+ rpcx:rpc-context-instance inner-test-rpc;
+ }
+ }
+ }
+ }
+
+ rpc noArgInnerInner {
+ input {
+ uses rpcx:rpc-context-ref {
+ refine context-instance {
+ rpcx:rpc-context-instance inner-inner-test-rpc;
+ }
+ }
+
+ leaf arg1 {
+ type uint16;
+ }
+
+ leaf arg2 {
+ type boolean;
+ }
+ }
+ output {
+ leaf result {
+ type boolean;
+ }
+ }
+ }
+}
--- /dev/null
+module config-test {
+ yang-version 1;
+ namespace "urn:opendaylight:params:xml:ns:yang:controller:test";
+ prefix "test";
+
+ import config { prefix config; revision-date 2013-04-05; }
+
+ description
+ "Testing API";
+
+ revision "2013-06-13" {
+ description
+ "Initial revision";
+ }
+
+ identity testing {
+ description
+ "Test api";
+
+ base "config:service-type";
+ config:java-class "java.lang.AutoCloseable";
+ }
+}
<modules>
<!-- MD-SAL bundles -->
<module>../../md-sal</module>
+ <module>../../config</module>
</modules>
</profile>
</profiles>