From: Tomas Olvecky Date: Fri, 27 Sep 2013 13:38:10 +0000 (+0200) Subject: Initial code drop of yang model driven configuration system X-Git-Tag: jenkins-controller-bulk-release-prepare-only-2-1~684 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=commitdiff_plain;h=9fb64948564e252018f9b1e13e7cea2c92f991aa Initial code drop of yang model driven configuration system 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 --- diff --git a/opendaylight/commons/opendaylight/pom.xml b/opendaylight/commons/opendaylight/pom.xml index f78fd4723d..d8c4ccebb1 100644 --- a/opendaylight/commons/opendaylight/pom.xml +++ b/opendaylight/commons/opendaylight/pom.xml @@ -61,6 +61,7 @@ 1.3.1 2.3.7 4.8.1 + 0.2.0-SNAPSHOT @@ -159,6 +160,16 @@ javassist ${javassist.version} + + org.opendaylight.bgpcep + concepts + ${bgpcep.version} + + + org.opendaylight.bgpcep + util + ${bgpcep.version} + ${project.version} @@ -664,7 +675,17 @@ commons-io commons-io - 2.3 + 2.4 + + + commons-codec + commons-codec + 1.7 + + + org.apache.commons + commons-lang3 + ${commons.lang.version} commons-fileupload diff --git a/opendaylight/config/config-api/.gitignore b/opendaylight/config/config-api/.gitignore new file mode 100644 index 0000000000..fc1d35eb24 --- /dev/null +++ b/opendaylight/config/config-api/.gitignore @@ -0,0 +1,3 @@ +target +.classpath +.settings diff --git a/opendaylight/config/config-api/pom.xml b/opendaylight/config/config-api/pom.xml new file mode 100644 index 0000000000..5c36f20e02 --- /dev/null +++ b/opendaylight/config/config-api/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + config-subsystem + org.opendaylight + 0.2.1-SNAPSHOT + + + config-api + ${project.artifactId} + bundle + + + + com.google.code.findbugs + jsr305 + + + org.opendaylight.bgpcep + concepts + 0.2.0-SNAPSHOT + + + + + + + org.apache.felix + maven-bundle-plugin + + + + javax.management, + org.opendaylight.protocol.concepts + + + 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, + + + + + + + diff --git a/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/ConfigRegistry.java b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/ConfigRegistry.java new file mode 100644 index 0000000000..d50d6ba475 --- /dev/null +++ b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/ConfigRegistry.java @@ -0,0 +1,86 @@ +/* + * 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 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 getAvailableModuleNames(); + + /** + * Find all runtime beans + * + * @return objectNames + */ + Set lookupRuntimeBeans(); + + /** + * Find all runtime of specified module + * + * @param moduleName + * of bean + * @param instanceName + * of bean + * @return objectNames + */ + Set lookupRuntimeBeans(String moduleName, String instanceName); + +} diff --git a/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/ConfigTransactionController.java b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/ConfigTransactionController.java new file mode 100644 index 0000000000..7e8ee64daa --- /dev/null +++ b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/ConfigTransactionController.java @@ -0,0 +1,69 @@ +/* + * 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 getAvailableModuleNames(); + +} diff --git a/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/ConflictingVersionException.java b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/ConflictingVersionException.java new file mode 100644 index 0000000000..9c25242c2d --- /dev/null +++ b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/ConflictingVersionException.java @@ -0,0 +1,36 @@ +/* + * 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); + } + +} diff --git a/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/DependencyResolver.java b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/DependencyResolver.java new file mode 100644 index 0000000000..a2b171aec0 --- /dev/null +++ b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/DependencyResolver.java @@ -0,0 +1,60 @@ +/* + * 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 expectedServiceInterface, + ObjectName objectName, JmxAttribute jmxAttribute); + + @Deprecated + // TODO remove once all config code is generated + void validateDependency( + Class 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 resolveInstance(Class expectedType, ObjectName objectName, + JmxAttribute jmxAttribute); + + @Deprecated + T resolveInstance(Class expectedType, ObjectName objectName); + +} diff --git a/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/DynamicMBeanWithInstance.java b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/DynamicMBeanWithInstance.java new file mode 100644 index 0000000000..cf0cb6a245 --- /dev/null +++ b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/DynamicMBeanWithInstance.java @@ -0,0 +1,32 @@ +/* + * 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(); + +} diff --git a/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/JmxAttribute.java b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/JmxAttribute.java new file mode 100644 index 0000000000..96deb051ef --- /dev/null +++ b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/JmxAttribute.java @@ -0,0 +1,52 @@ +/* + * 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 + "'}"; + } +} diff --git a/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/JmxAttributeValidationException.java b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/JmxAttributeValidationException.java new file mode 100644 index 0000000000..3380a10afe --- /dev/null +++ b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/JmxAttributeValidationException.java @@ -0,0 +1,93 @@ +/* + * 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 attributeNames; + + public JmxAttributeValidationException(JmxAttribute jmxAttribute) { + this(Arrays.asList(jmxAttribute)); + } + + public JmxAttributeValidationException(List jmxAttribute) { + this.attributeNames = jmxAttribute; + } + + public JmxAttributeValidationException(String message, + JmxAttribute jmxAttribute) { + this(message, Arrays.asList(jmxAttribute)); + } + + public JmxAttributeValidationException(String message, + List 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 jmxAttributes) { + super(message, cause); + this.attributeNames = jmxAttributes; + } + + public List getAttributeNames() { + return attributeNames; + } + + public static T checkNotNull(T param, JmxAttribute jmxAttribute) { + String message = "is null"; + return checkNotNull(param, message, jmxAttribute); + } + + public static 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); + } + } +} diff --git a/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/LookupRegistry.java b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/LookupRegistry.java new file mode 100644 index 0000000000..7a3c4bf82d --- /dev/null +++ b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/LookupRegistry.java @@ -0,0 +1,61 @@ +/* + * 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 lookupConfigBeans(); + + /** + * Find modules with given module name. + * + * @param moduleName + * @return objectNames + */ + Set 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 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; + +} diff --git a/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/ModuleIdentifier.java b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/ModuleIdentifier.java new file mode 100644 index 0000000000..d162ca07d9 --- /dev/null +++ b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/ModuleIdentifier.java @@ -0,0 +1,64 @@ +/* + * 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 + '\'' + '}'; + } +} diff --git a/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/RuntimeBeanRegistratorAwareModule.java b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/RuntimeBeanRegistratorAwareModule.java new file mode 100644 index 0000000000..a23d47b862 --- /dev/null +++ b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/RuntimeBeanRegistratorAwareModule.java @@ -0,0 +1,31 @@ +/* + * 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); + +} diff --git a/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/ValidationException.java b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/ValidationException.java new file mode 100644 index 0000000000..90b8bb6d85 --- /dev/null +++ b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/ValidationException.java @@ -0,0 +1,148 @@ +/* + * 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> failedValidations; + + public ValidationException( + Map> failedValidations) { + super(failedValidations.toString()); + this.failedValidations = Collections.unmodifiableMap(failedValidations); + } + + public static ValidationException createFromCollectedValidationExceptions( + List collectedExceptions) { + Map> failedValidations = new HashMap<>(); + for (ValidationException ve : collectedExceptions) { + for (Entry> outerEntry : ve + .getFailedValidations().entrySet()) { + for (Entry innerEntry : outerEntry + .getValue().entrySet()) { + String moduleName = outerEntry.getKey(); + String instanceName = innerEntry.getKey(); + ExceptionMessageWithStackTrace ex = innerEntry.getValue(); + Map 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> failedValidations = new HashMap<>(); + Map innerMap = new HashMap<>(); + + failedValidations.put(moduleIdentifier.getFactoryName(), innerMap); + innerMap.put(moduleIdentifier.getInstanceName(), + new ExceptionMessageWithStackTrace(e)); + return new ValidationException(failedValidations); + } + + public Map> 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 + "]"; + } + + } +} diff --git a/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/annotations/AbstractServiceInterface.java b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/annotations/AbstractServiceInterface.java new file mode 100644 index 0000000000..261c5f5841 --- /dev/null +++ b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/annotations/AbstractServiceInterface.java @@ -0,0 +1,17 @@ +/* + * 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 { +} diff --git a/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/annotations/Description.java b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/annotations/Description.java new file mode 100644 index 0000000000..77362a890e --- /dev/null +++ b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/annotations/Description.java @@ -0,0 +1,30 @@ +/* + * 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(); +} diff --git a/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/annotations/RequireInterface.java b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/annotations/RequireInterface.java new file mode 100644 index 0000000000..3584776bb3 --- /dev/null +++ b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/annotations/RequireInterface.java @@ -0,0 +1,42 @@ +/* + * 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. + * + *

+ * Example:
+ * + * + * + * @RequireInterface(value = ThreadPoolServiceInterface.class, optional = + * false)
void setThreadPool(ObjectName on); + *
+ */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface RequireInterface { + + /** + * Declares dependency on service interface. + */ + Class value(); + +} diff --git a/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/annotations/ServiceInterfaceAnnotation.java b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/annotations/ServiceInterfaceAnnotation.java new file mode 100644 index 0000000000..a81d992d81 --- /dev/null +++ b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/annotations/ServiceInterfaceAnnotation.java @@ -0,0 +1,37 @@ +/* + * 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(); +} diff --git a/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/jmx/CommitStatus.java b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/jmx/CommitStatus.java new file mode 100644 index 0000000000..bd53bd4e58 --- /dev/null +++ b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/jmx/CommitStatus.java @@ -0,0 +1,115 @@ +/* + * 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 newInstances, reusedInstances, + recreatedInstances; + + /** + * + * @param newInstances + * newly created instances + * @param reusedInstances + * reused instances + * @param recreatedInstances + * recreated instances + */ + @ConstructorProperties({ "newInstances", "reusedInstances", + "recreatedInstances" }) + public CommitStatus(List newInstances, + List reusedInstances, + List 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 getNewInstances() { + return newInstances; + } + + /** + * + * @return list of objectNames representing reused instances + */ + public List getReusedInstances() { + return reusedInstances; + } + + /** + * + * @return list of objectNames representing recreated instances + */ + public List 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 + "]"; + } + +} diff --git a/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/jmx/ConfigRegistryMXBean.java b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/jmx/ConfigRegistryMXBean.java new file mode 100644 index 0000000000..af02f14a4b --- /dev/null +++ b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/jmx/ConfigRegistryMXBean.java @@ -0,0 +1,22 @@ +/* + * 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.
+ * 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 { + +} diff --git a/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/jmx/ConfigTransactionControllerMXBean.java b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/jmx/ConfigTransactionControllerMXBean.java new file mode 100644 index 0000000000..e5123e2bee --- /dev/null +++ b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/jmx/ConfigTransactionControllerMXBean.java @@ -0,0 +1,25 @@ +/* + * 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.
+ * 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 { + +} diff --git a/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/jmx/ObjectNameUtil.java b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/jmx/ObjectNameUtil.java new file mode 100644 index 0000000000..8111690c72 --- /dev/null +++ b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/jmx/ObjectNameUtil.java @@ -0,0 +1,259 @@ +/* + * 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 attribs) { + Hashtable table = new Hashtable<>(attribs); + try { + return new ObjectName(name, table); + } catch (Exception e) { + throw new RuntimeException(e); + } + + } + + public static ObjectName createTransactionControllerON( + String transactionName) { + Map 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 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 onParams = createModuleON(moduleName, instanceName); + return createON(ON_DOMAIN, onParams); + } + + private static Map createModuleON(String moduleName, + String instanceName) { + Map 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 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 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 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 blacklist = new HashSet<>(Arrays.asList( + MODULE_FACTORY_NAME_KEY, INSTANCE_NAME_KEY, TYPE_KEY)); + + public static Map getAdditionalPropertiesOfRuntimeBeanName( + ObjectName on) { + checkType(on, TYPE_RUNTIME_BEAN); + Map allProperties = getAdditionalProperties(on); + Map result = new HashMap<>(); + for (Entry entry : allProperties.entrySet()) { + if (blacklist.contains(entry.getKey()) == false) { + result.put(entry.getKey(), entry.getValue()); + } + } + return result; + } + + public static Map getAdditionalProperties(ObjectName on) { + Hashtable keyPropertyList = on.getKeyPropertyList(); + Map result = new HashMap<>(); + for (Entry 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); + } + +} diff --git a/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/jmx/constants/ConfigRegistryConstants.java b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/jmx/constants/ConfigRegistryConstants.java new file mode 100644 index 0000000000..81a29bf7b3 --- /dev/null +++ b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/jmx/constants/ConfigRegistryConstants.java @@ -0,0 +1,36 @@ +/* + * 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); + } + + } +} diff --git a/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/runtime/HierarchicalRuntimeBeanRegistration.java b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/runtime/HierarchicalRuntimeBeanRegistration.java new file mode 100644 index 0000000000..185c13c9f9 --- /dev/null +++ b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/runtime/HierarchicalRuntimeBeanRegistration.java @@ -0,0 +1,27 @@ +/* + * 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(); + +} diff --git a/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/runtime/RootRuntimeBeanRegistrator.java b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/runtime/RootRuntimeBeanRegistrator.java new file mode 100644 index 0000000000..76949bfc6d --- /dev/null +++ b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/runtime/RootRuntimeBeanRegistrator.java @@ -0,0 +1,28 @@ +/* + * 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(); + +} diff --git a/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/runtime/RuntimeBean.java b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/runtime/RuntimeBean.java new file mode 100644 index 0000000000..b7ac7733c9 --- /dev/null +++ b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/api/runtime/RuntimeBean.java @@ -0,0 +1,15 @@ +/* + * 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 { +} diff --git a/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/spi/Module.java b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/spi/Module.java new file mode 100644 index 0000000000..b11d5f8beb --- /dev/null +++ b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/spi/Module.java @@ -0,0 +1,58 @@ +/* + * 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. + *

+ * In order to guide dependency resolution, the setter method should be + * annotated with {@link RequireInterface}. + *

+ *

+ * Thread safety note: implementations of this interface are not required to be + * thread safe as thread safety is enforced by configuration manager. + *

+ */ +@NotThreadSafe +public interface Module extends NamedObject { + /** + * 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(); + +} diff --git a/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/spi/ModuleFactory.java b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/spi/ModuleFactory.java new file mode 100644 index 0000000000..00db2c2cd4 --- /dev/null +++ b/opendaylight/config/config-api/src/main/java/org/opendaylight/controller/config/spi/ModuleFactory.java @@ -0,0 +1,91 @@ +/* + * 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 serviceInterface); + +} diff --git a/opendaylight/config/config-api/src/main/resources/META-INF/yang/config.yang b/opendaylight/config/config-api/src/main/resources/META-INF/yang/config.yang new file mode 100644 index 0000000000..5d6c11fbee --- /dev/null +++ b/opendaylight/config/config-api/src/main/resources/META-INF/yang/config.yang @@ -0,0 +1,192 @@ +// 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"; + } + } + } + } + } + + +} diff --git a/opendaylight/config/config-api/src/main/resources/META-INF/yang/rpc-context.yang b/opendaylight/config/config-api/src/main/resources/META-INF/yang/rpc-context.yang new file mode 100644 index 0000000000..5c8b113412 --- /dev/null +++ b/opendaylight/config/config-api/src/main/resources/META-INF/yang/rpc-context.yang @@ -0,0 +1,32 @@ +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"; + } +} diff --git a/opendaylight/config/config-manager/.gitignore b/opendaylight/config/config-manager/.gitignore new file mode 100644 index 0000000000..fc1d35eb24 --- /dev/null +++ b/opendaylight/config/config-manager/.gitignore @@ -0,0 +1,3 @@ +target +.classpath +.settings diff --git a/opendaylight/config/config-manager/pom.xml b/opendaylight/config/config-manager/pom.xml new file mode 100644 index 0000000000..eb3dcc1aa4 --- /dev/null +++ b/opendaylight/config/config-manager/pom.xml @@ -0,0 +1,115 @@ + + 4.0.0 + + config-subsystem + org.opendaylight + 0.2.1-SNAPSHOT + .. + + config-manager + ${project.artifactId} + bundle + + + + + ${project.groupId} + config-api + 0.2.1-SNAPSHOT + + + org.slf4j + slf4j-api + + + org.osgi + org.osgi.core + + + com.google.code.findbugs + jsr305 + + + com.googlecode.json-simple + json-simple + 1.1 + + + commons-io + commons-io + + + + + org.opendaylight.bgpcep + mockito-configuration + 0.2.0-SNAPSHOT + test + + + ${project.groupId} + config-util + 0.2.1-SNAPSHOT + test + + + ${project.groupId} + config-util + 0.2.1-SNAPSHOT + test + test-jar + + + com.google.guava + guava + test + + + + + + + org.apache.felix + maven-bundle-plugin + + + org.opendaylight.controller.config.manager.impl.osgi.ConfigManagerActivator + + + org.opendaylight.controller.config.manager.*, + javax.annotation.*, + + + 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, + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + 1 + false + false + 1 + + + + + + diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/CommitInfo.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/CommitInfo.java new file mode 100644 index 0000000000..9d086295e3 --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/CommitInfo.java @@ -0,0 +1,47 @@ +/* + * 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 destroyedFromPreviousTransactions; + private final Map commitMap; + + public CommitInfo(List destroyedFromPreviousTransactions, + Map 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 getDestroyedFromPreviousTransactions() { + return destroyedFromPreviousTransactions; + } + + public Map getCommitted() { + return commitMap; + } +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ConfigRegistryImpl.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ConfigRegistryImpl.java new file mode 100644 index 0000000000..97d57a4591 --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ConfigRegistryImpl.java @@ -0,0 +1,600 @@ +/* + * 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 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 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 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 orderedModuleIdentifiers = configTransactionController + .secondPhaseCommit(); + + // copy configuration to read only mode + List newInstances = new LinkedList<>(); + List reusedInstances = new LinkedList<>(); + List recreatedInstances = new LinkedList<>(); + + Map 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 getOpenConfigs() { + Map transactions = transactionsHolder + .getCurrentTransactions(); + List 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 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 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 getAvailableModuleNames() { + return new HierarchicalConfigMBeanFactoriesHolder( + resolver.getAllFactories()).getModuleNames(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isHealthy() { + return isHealthy; + } + + // filtering methods + + /** + * {@inheritDoc} + */ + @Override + public Set lookupConfigBeans() { + return lookupConfigBeans("*", "*"); + } + + /** + * {@inheritDoc} + */ + @Override + public Set 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 lookupConfigBeans(String moduleName, + String instanceName) { + ObjectName namePattern = ObjectNameUtil.createModulePattern(moduleName, + instanceName); + return baseJMXRegistrator.queryNames(namePattern, null); + } + + /** + * {@inheritDoc} + */ + @Override + public Set lookupRuntimeBeans() { + return lookupRuntimeBeans("*", "*"); + } + + /** + * {@inheritDoc} + */ + @Override + public Set 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 currentConfig = new HashMap<>(); + + /** + * Add all modules to the internal map. Also add service instance to OSGi + * Service Registry. + */ + public void addAll(Collection 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 getEntries() { + return currentConfig.values(); + } + + public List getModulesToBeDestroyed() { + List result = new ArrayList<>(); + for (ModuleInternalInfo moduleInternalInfo : getEntries()) { + result.add(moduleInternalInfo.toDestroyedModule()); + } + Collections.sort(result); + return result; + } +} + +/** + * Holds Map 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 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 getCurrentTransactions() { + // first, remove closed transaction + for (Iterator> it = transactions + .entrySet().iterator(); it.hasNext();) { + Entry entry = it + .next(); + if (entry.getValue().isClosed()) { + it.remove(); + } + } + return Collections.unmodifiableMap(transactions); + } +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ConfigRegistryImplMXBean.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ConfigRegistryImplMXBean.java new file mode 100644 index 0000000000..bc8b6a5288 --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ConfigRegistryImplMXBean.java @@ -0,0 +1,24 @@ +/* + * 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(); + +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ConfigTransactionControllerImpl.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ConfigTransactionControllerImpl.java new file mode 100644 index 0000000000..4fe4b319a5 --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ConfigTransactionControllerImpl.java @@ -0,0 +1,401 @@ +/* + * 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 { + 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 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 collectedExceptions = new ArrayList<>(); + for (Entry 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 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 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 lookupConfigBeans() { + return lookupConfigBeans("*", "*"); + } + + /** + * {@inheritDoc} + */ + @Override + public Set 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 lookupConfigBeans(String moduleName, + String instanceName) { + ObjectName namePattern = ObjectNameUtil.createModulePattern(moduleName, + instanceName, transactionIdentifier.getName()); + return txModuleJMXRegistrator.queryNames(namePattern, null); + } + + @Override + public Set 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; + } +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ConfigTransactionControllerImplMXBean.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ConfigTransactionControllerImplMXBean.java new file mode 100644 index 0000000000..b35c1dc7d0 --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ConfigTransactionControllerImplMXBean.java @@ -0,0 +1,28 @@ +/* + * 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(); + +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ConfigTransactionControllerInternal.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ConfigTransactionControllerInternal.java new file mode 100644 index 0000000000..58d3bc1e4b --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ConfigTransactionControllerInternal.java @@ -0,0 +1,65 @@ +/* + * 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 secondPhaseCommit(); + + /** + * @return ObjectName of this transaction controller + */ + ObjectName getControllerObjectName(); + + /** + * @return true iif transaction was committed or aborted. + */ + boolean isClosed(); + +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/DestroyedModule.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/DestroyedModule.java new file mode 100644 index 0000000000..47f34e25f8 --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/DestroyedModule.java @@ -0,0 +1,73 @@ +/* + * 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, + AutoCloseable, Comparable { + 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); + } +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ModuleInternalInfo.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ModuleInternalInfo.java new file mode 100644 index 0000000000..fefc88651c --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ModuleInternalInfo.java @@ -0,0 +1,115 @@ +/* + * 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, + Comparable { + + 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()); + } +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ModuleInternalTransactionalInfo.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ModuleInternalTransactionalInfo.java new file mode 100644 index 0000000000..c71c3bb470 --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ModuleInternalTransactionalInfo.java @@ -0,0 +1,80 @@ +/* + * 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 { + 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; + } +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/TransactionIdentifier.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/TransactionIdentifier.java new file mode 100644 index 0000000000..3e2b8926a6 --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/TransactionIdentifier.java @@ -0,0 +1,47 @@ +/* + * 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; + } +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/TransactionStatus.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/TransactionStatus.java new file mode 100644 index 0000000000..a25062074b --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/TransactionStatus.java @@ -0,0 +1,78 @@ +/* + * 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"); + } + } +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dependencyresolver/DependencyResolverImpl.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dependencyresolver/DependencyResolverImpl.java new file mode 100644 index 0000000000..87b2e1f752 --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dependencyresolver/DependencyResolverImpl.java @@ -0,0 +1,219 @@ +/* + * 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, Comparable { + private final ModulesHolder modulesHolder; + private final ModuleIdentifier name; + private final TransactionStatus transactionStatus; + @GuardedBy("this") + private final Set 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 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 expectedServiceInterface, + ObjectName objectName, String attributeNameForErrorReporting) { + validateDependency(expectedServiceInterface, objectName, + new JmxAttribute(attributeNameForErrorReporting)); + } + + /** + * {@inheritDoc} + */ + @Override + public T resolveInstance(Class 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 resolveInstance(Class 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()); + } + } + + private static int getMaxDepth(DependencyResolverImpl impl, + DependencyResolverManager manager, + LinkedHashSet chainForDetectingCycles) { + int maxDepth = 0; + LinkedHashSet 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; + } +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dependencyresolver/DependencyResolverManager.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dependencyresolver/DependencyResolverManager.java new file mode 100644 index 0000000000..34b3093f5d --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dependencyresolver/DependencyResolverManager.java @@ -0,0 +1,131 @@ +/* + * 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 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 getAllSorted() { + transactionStatus.checkCommitted(); + List sorted = new ArrayList<>( + moduleIdentifiersToDependencyResolverMap.values()); + for (DependencyResolverImpl dri : sorted) { + dri.countMaxDependencyDepth(this); + } + Collections.sort(sorted); + return sorted; + } + + public List getSortedModuleIdentifiers() { + List 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 getAllModules() { + return modulesHolder.getAllModules(); + } + + @Override + public void assertNotExists(ModuleIdentifier moduleIdentifier) + throws InstanceAlreadyExistsException { + modulesHolder.assertNotExists(moduleIdentifier); + } +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dependencyresolver/ModulesHolder.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dependencyresolver/ModulesHolder.java new file mode 100644 index 0000000000..7747e55693 --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dependencyresolver/ModulesHolder.java @@ -0,0 +1,123 @@ +/* + * 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 commitMap = new HashMap<>(); + + @GuardedBy("this") + private final Set unorderedDestroyedFromPreviousTransactions = new HashSet<>(); + + ModulesHolder(String transactionName) { + this.transactionName = transactionName; + } + + @Override + public CommitInfo toCommitInfo() { + List 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 getAllModules() { + Map 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); + } + } +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dependencyresolver/TransactionHolder.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dependencyresolver/TransactionHolder.java new file mode 100644 index 0000000000..f81e747f62 --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dependencyresolver/TransactionHolder.java @@ -0,0 +1,40 @@ +/* + * 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 getAllModules(); + + void put(ModuleInternalTransactionalInfo moduleInternalTransactionalInfo); + + ModuleInternalTransactionalInfo destroyModule( + ModuleIdentifier moduleIdentifier); + + void assertNotExists(ModuleIdentifier moduleIdentifier) + throws InstanceAlreadyExistsException; + +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/AbstractDynamicWrapper.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/AbstractDynamicWrapper.java new file mode 100644 index 0000000000..b7c15706c5 --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/AbstractDynamicWrapper.java @@ -0,0 +1,324 @@ +/* + * 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 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> 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 attributeHolderMap, + MBeanOperationInfo[] dOperations, Set> jmxInterfaces) { + + String dDescription = findDescription(module.getClass(), jmxInterfaces); + MBeanConstructorInfo[] dConstructors = new MBeanConstructorInfo[0]; + List 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> jmxInterfaces) { + List 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 buildMBeanInfo(Module module, + boolean writable, ModuleIdentifier moduleIdentifier, + Set> jmxInterfaces, MBeanServer internalServer, + ObjectName internalObjectName) { + + // internal variables for describing MBean elements + Set 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 attributeMap = new HashMap<>(); + for (MBeanAttributeInfo a : internalInfo.getAttributes()) { + attributeMap.put(a.getName(), a); + } + Map 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); + } + +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/AnnotationsHelper.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/AnnotationsHelper.java new file mode 100644 index 0000000000..64664f7980 --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/AnnotationsHelper.java @@ -0,0 +1,113 @@ +/* + * 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 + * generic type of annotation + * @return list of found annotations + */ + static List findMethodAnnotationInSuperClassesAndIfcs( + final Method setter, Class annotationType, + Set> inspectedInterfaces) { + List result = new ArrayList(); + 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 List findClassAnnotationInSuperClassesAndIfcs( + Class clazz, Class annotationType, Set> interfaces) { + List result = new ArrayList(); + 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 descriptions) { + StringBuilder builder = new StringBuilder(); + for (Description d : descriptions) { + if (builder.length() != 0) { + builder.append("\n"); + } + builder.append(d.value()); + + } + return builder.toString(); + } +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/AttributeHolder.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/AttributeHolder.java new file mode 100644 index 0000000000..109ab10ac2 --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/AttributeHolder.java @@ -0,0 +1,143 @@ +/* + * 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> jmxInterfaces) { + List 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> 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 foundRequireInterfaces = AnnotationsHelper + .findMethodAnnotationInSuperClassesAndIfcs(setter, RequireInterface.class, inspectedInterfaces); + // make sure the list if not empty contains always annotation with same + // value + Set> foundValues = new HashSet>(); + 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); + } + } +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/DynamicMBeanModuleWrapper.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/DynamicMBeanModuleWrapper.java new file mode 100644 index 0000000000..d095839d1e --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/DynamicMBeanModuleWrapper.java @@ -0,0 +1,18 @@ +/* + * 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 { + +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/DynamicReadableWrapper.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/DynamicReadableWrapper.java new file mode 100644 index 0000000000..3a24940a4c --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/DynamicReadableWrapper.java @@ -0,0 +1,90 @@ +/* + * 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); + } +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/DynamicWritableWrapper.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/DynamicWritableWrapper.java new file mode 100644 index 0000000000..9495ca6f11 --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/DynamicWritableWrapper.java @@ -0,0 +1,153 @@ +/* + * 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. + *

+ * 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. + *

+ */ +@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(); + } +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/ReadOnlyAtomicBoolean.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/ReadOnlyAtomicBoolean.java new file mode 100644 index 0000000000..deccde0f24 --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/ReadOnlyAtomicBoolean.java @@ -0,0 +1,30 @@ +/* + * 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(); + } + + } +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/factoriesresolver/HierarchicalConfigMBeanFactoriesHolder.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/factoriesresolver/HierarchicalConfigMBeanFactoriesHolder.java new file mode 100644 index 0000000000..8f1c69eee3 --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/factoriesresolver/HierarchicalConfigMBeanFactoriesHolder.java @@ -0,0 +1,94 @@ +/* + * 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 moduleNamesToConfigBeanFactories; + private final Set moduleNames; + + /** + * Create instance. + * + * @throws IllegalArgumentException + * if unique constraint on module names is violated + */ + public HierarchicalConfigMBeanFactoriesHolder( + List list) { + Map 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 getModuleNames() { + return moduleNames; + } + +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/factoriesresolver/ModuleFactoriesResolver.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/factoriesresolver/ModuleFactoriesResolver.java new file mode 100644 index 0000000000..9678d70615 --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/factoriesresolver/ModuleFactoriesResolver.java @@ -0,0 +1,25 @@ +/* + * 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 getAllFactories(); + +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/jmx/BaseJMXRegistrator.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/jmx/BaseJMXRegistrator.java new file mode 100644 index 0000000000..be64238aaf --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/jmx/BaseJMXRegistrator.java @@ -0,0 +1,58 @@ +/* + * 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 queryNames(ObjectName name, QueryExp query) { + return internalJMXRegistrator.queryNames(name, query); + } + + public Set getRegisteredObjectNames() { + return internalJMXRegistrator.getRegisteredObjectNames(); + } + + @Override + public void close() { + internalJMXRegistrator.close(); + } +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/jmx/ConfigRegistryJMXRegistrator.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/jmx/ConfigRegistryJMXRegistrator.java new file mode 100644 index 0000000000..ec7fc9ce92 --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/jmx/ConfigRegistryJMXRegistrator.java @@ -0,0 +1,37 @@ +/* + * 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(); + } +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/jmx/HierarchicalRuntimeBeanRegistrationImpl.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/jmx/HierarchicalRuntimeBeanRegistrationImpl.java new file mode 100644 index 0000000000..49fad61a89 --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/jmx/HierarchicalRuntimeBeanRegistrationImpl.java @@ -0,0 +1,66 @@ +/* + * 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 properties; + + public HierarchicalRuntimeBeanRegistrationImpl( + ModuleIdentifier moduleIdentifier, + InternalJMXRegistrator internalJMXRegistrator, + Map 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 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(); + } +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/jmx/InternalJMXRegistrator.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/jmx/InternalJMXRegistrator.java new file mode 100644 index 0000000000..5d771560a5 --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/jmx/InternalJMXRegistrator.java @@ -0,0 +1,150 @@ +/* + * 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 registeredObjectNames = new HashSet<>(); + private final List 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 newMBeanProxy(ObjectName objectName, Class interfaceClass) { + return JMX.newMBeanProxy(configMBeanServer, objectName, interfaceClass); + } + + public T newMBeanProxy(ObjectName objectName, Class interfaceClass, + boolean notificationBroadcaster) { + return JMX.newMBeanProxy(configMBeanServer, objectName, interfaceClass, + notificationBroadcaster); + } + + public T newMXBeanProxy(ObjectName objectName, Class interfaceClass) { + return JMX + .newMXBeanProxy(configMBeanServer, objectName, interfaceClass); + } + + public T newMXBeanProxy(ObjectName objectName, Class interfaceClass, + boolean notificationBroadcaster) { + return JMX.newMXBeanProxy(configMBeanServer, objectName, + interfaceClass, notificationBroadcaster); + } + + public Set getRegisteredObjectNames() { + return Collections.unmodifiableSet(registeredObjectNames); + } + + public Set queryNames(ObjectName name, QueryExp query) { + Set result = configMBeanServer.queryNames(name, query); + // keep only those that were registered using this instance + return getSameNames(result); + } + + private Set getSameNames(Set superSet) { + Set result = new HashSet<>(superSet); + result.retainAll(registeredObjectNames); + for (InternalJMXRegistrator child : children) { + result.addAll(child.getSameNames(superSet)); + } + return result; + } + +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/jmx/ModuleJMXRegistrator.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/jmx/ModuleJMXRegistrator.java new file mode 100644 index 0000000000..34c0436daf --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/jmx/ModuleJMXRegistrator.java @@ -0,0 +1,61 @@ +/* + * 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(); + } + +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/jmx/RootRuntimeBeanRegistratorImpl.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/jmx/RootRuntimeBeanRegistratorImpl.java new file mode 100644 index 0000000000..55bfbf56fc --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/jmx/RootRuntimeBeanRegistratorImpl.java @@ -0,0 +1,60 @@ +/* + * 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. 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. 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); + + } +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/jmx/TransactionJMXRegistrator.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/jmx/TransactionJMXRegistrator.java new file mode 100644 index 0000000000..523cbc5b01 --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/jmx/TransactionJMXRegistrator.java @@ -0,0 +1,71 @@ +/* + * 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 queryNames(ObjectName name, QueryExp query) { + return childJMXRegistrator.queryNames(name, query); + } + + public TransactionModuleJMXRegistrator createTransactionModuleJMXRegistrator() { + return new TransactionModuleJMXRegistrator(childJMXRegistrator, + transactionName); + } + + @Override + public void close() { + childJMXRegistrator.close(); + } +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/jmx/TransactionModuleJMXRegistrator.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/jmx/TransactionModuleJMXRegistrator.java new file mode 100644 index 0000000000..546adb0d89 --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/jmx/TransactionModuleJMXRegistrator.java @@ -0,0 +1,64 @@ +/* + * 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 queryNames(ObjectName name, QueryExp query) { + return childJMXRegistrator.queryNames(name, query); + } + + @Override + public void close() { + childJMXRegistrator.close(); + } +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/osgi/BeanToOsgiServiceManager.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/osgi/BeanToOsgiServiceManager.java new file mode 100644 index 0000000000..42f8b8790b --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/osgi/BeanToOsgiServiceManager.java @@ -0,0 +1,121 @@ +/* + * 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 configBeanClass, AutoCloseable instance, + ModuleIdentifier moduleIdentifier) { + try { + final Set> configuresInterfaces = InterfacesHelper + .getOsgiRegistrationTypes(configBeanClass); + checkInstanceImplementing(instance, configuresInterfaces); + + // bundleContext.registerService blows up with empty 'clazzes' + if (configuresInterfaces.isEmpty() == false) { + final Dictionary 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> 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> configures) { + Set> 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 getPropertiesForOsgi( + ModuleIdentifier moduleIdentifier) { + Hashtable 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(); + } + } + } + +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/osgi/BundleContextBackedModuleFactoriesResolver.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/osgi/BundleContextBackedModuleFactoriesResolver.java new file mode 100644 index 0000000000..77bfc495b8 --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/osgi/BundleContextBackedModuleFactoriesResolver.java @@ -0,0 +1,54 @@ +/* + * 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 getAllFactories() { + Collection> serviceReferences; + try { + serviceReferences = bundleContext.getServiceReferences( + ModuleFactory.class, null); + } catch (InvalidSyntaxException e) { + throw new IllegalStateException(e); + } + List result = new ArrayList<>(serviceReferences.size()); + for (ServiceReference 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; + } +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/osgi/ConfigManagerActivator.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/osgi/ConfigManagerActivator.java new file mode 100644 index 0000000000..81b9ea9606 --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/osgi/ConfigManagerActivator.java @@ -0,0 +1,68 @@ +/* + * 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); + } + } +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/osgi/ExtenderBundleTracker.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/osgi/ExtenderBundleTracker.java new file mode 100644 index 0000000000..22a1216959 --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/osgi/ExtenderBundleTracker.java @@ -0,0 +1,121 @@ +/* + * 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 { + + 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 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); + } +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/util/InterfacesHelper.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/util/InterfacesHelper.java new file mode 100644 index 0000000000..6cd855450d --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/util/InterfacesHelper.java @@ -0,0 +1,100 @@ +/* + * 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> getAllInterfaces(Class clazz) { + if (clazz.isInterface()) { + throw new IllegalArgumentException(clazz + + " should not be an interface"); + } + // getInterfaces gets interfaces implemented directly by this class + Set> 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> inspected = new HashSet<>(); + while (toBeInspected.size() > 0) { + Iterator> 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> getMXInterfaces( + Class configBeanClass) { + Set> allInterfaces = getAllInterfaces(configBeanClass); + Set> 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> getServiceInterfaces( + Class configBeanClass) { + Set> allInterfaces = getAllInterfaces(configBeanClass); + Set> 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> getOsgiRegistrationTypes( + Class configBeanClass) { + Set> serviceInterfaces = getServiceInterfaces(configBeanClass); + Set> result = new HashSet<>(); + for (Class clazz : serviceInterfaces) { + ServiceInterfaceAnnotation annotation = clazz + .getAnnotation(ServiceInterfaceAnnotation.class); + result.add(annotation.osgiRegistrationType()); + } + return result; + } + +} diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/util/LookupBeansUtil.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/util/LookupBeansUtil.java new file mode 100644 index 0000000000..fdde0b23ef --- /dev/null +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/util/LookupBeansUtil.java @@ -0,0 +1,32 @@ +/* + * 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 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(); + } + +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/ConfigRegistryImplTest.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/ConfigRegistryImplTest.java new file mode 100644 index 0000000000..928f2c1879 --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/ConfigRegistryImplTest.java @@ -0,0 +1,63 @@ +/* + * 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); + } + } + } + +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/AbstractConfigTest.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/AbstractConfigTest.java new file mode 100644 index 0000000000..3eaa9b1c7d --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/AbstractConfigTest.java @@ -0,0 +1,162 @@ +/* + * 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 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 configBeanClass, String implementationName) { + return new ClassBasedModuleFactory(implementationName, configBeanClass); + } +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/AbstractConfigWithJolokiaTest.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/AbstractConfigWithJolokiaTest.java new file mode 100644 index 0000000000..de26220848 --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/AbstractConfigWithJolokiaTest.java @@ -0,0 +1,38 @@ +/* + * 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(); + } +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/AbstractLockedPlatformMBeanServerTest.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/AbstractLockedPlatformMBeanServerTest.java new file mode 100644 index 0000000000..2e97c49f26 --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/AbstractLockedPlatformMBeanServerTest.java @@ -0,0 +1,44 @@ +/* + * 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 { + + } + +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/AbstractMockedModule.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/AbstractMockedModule.java new file mode 100644 index 0000000000..89ac9a3828 --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/AbstractMockedModule.java @@ -0,0 +1,42 @@ +/* + * 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"); + } + +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/ClassBasedModuleFactory.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/ClassBasedModuleFactory.java new file mode 100644 index 0000000000..afc79a5c16 --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/ClassBasedModuleFactory.java @@ -0,0 +1,95 @@ +/* + * 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 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 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 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 serviceInterface) { + Class[] classes = configBeanClass.getInterfaces(); + List> 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; + } +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/ConfigRegistryImplLookupTest.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/ConfigRegistryImplLookupTest.java new file mode 100644 index 0000000000..6dddc62f67 --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/ConfigRegistryImplLookupTest.java @@ -0,0 +1,157 @@ +/* + * 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.emptyMap()); + private static final ObjectName name6 = ObjectNameUtil + .createRuntimeBeanName(moduleNameA, instanceNameB, Collections.emptyMap()); + private static final ObjectName name8 = ObjectNameUtil + .createRuntimeBeanName(moduleNameB, instanceNameA, Collections.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 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 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 bean = configRegistryImpl + .lookupConfigBeans(moduleNameA); + assertEquals(Sets.newHashSet(name1, name2, name3), bean); + } + + @Test + public void testLookupConfigBeanWithModuleNameAndInstanceName() + throws Exception { + Set bean = configRegistryImpl.lookupConfigBeans( + moduleNameA, instanceNameA); + assertEquals(Sets.newHashSet(name1), bean); + } + + @Test + public void testLookupRuntimeBeans() throws Exception { + Set 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 beans = configRegistryImpl.lookupRuntimeBeans( + moduleNameA, instanceNameA); + assertEquals(Sets.newHashSet(name5), beans); + } + +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/ConfigTransactionControllerImplTest.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/ConfigTransactionControllerImplTest.java new file mode 100644 index 0000000000..593f99f062 --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/ConfigTransactionControllerImplTest.java @@ -0,0 +1,117 @@ +/* + * 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 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 beans = testedTxController.lookupConfigBeans(); + assertEquals(Sets.newHashSet(name1, name2, name3), beans); + } + + @Test + public void testLookupConfigBeansWithModuleName() { + Set beans = testedTxController + .lookupConfigBeans(moduleName124); + assertEquals(Sets.newHashSet(name1, name2), beans); + } + + @Test + public void lookupConfigBeansWithModuleNameAndImplName() throws Exception { + Set beans = testedTxController.lookupConfigBeans( + moduleName124, instanceName134); + assertEquals(Sets.newHashSet(name1), beans); + } + +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/ConfigTransactionManagerImplTest.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/ConfigTransactionManagerImplTest.java new file mode 100644 index 0000000000..eaaee5eb5b --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/ConfigTransactionManagerImplTest.java @@ -0,0 +1,77 @@ +/* + * 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(); + + } +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/dependencyresolver/DependencyResolverManagerTest.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/dependencyresolver/DependencyResolverManagerTest.java new file mode 100644 index 0000000000..a79cb83a38 --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/dependencyresolver/DependencyResolverManagerTest.java @@ -0,0 +1,100 @@ +/* + * 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 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; + } + +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/AbstractDynamicWrapperTest.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/AbstractDynamicWrapperTest.java new file mode 100644 index 0000000000..e411778e55 --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/AbstractDynamicWrapperTest.java @@ -0,0 +1,111 @@ +/* + * 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()); + } + +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/AnnotationsTest.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/AnnotationsTest.java new file mode 100644 index 0000000000..cf6ed18c6c --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/AnnotationsTest.java @@ -0,0 +1,280 @@ +/* + * 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> inspectedInterfaces, + Class 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> 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> 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> 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> 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> exportedInterfaces = Sets + .> newHashSet(HasSomeMethodWithAnnotations.class); + assertRequireInterfaceAnnotationHasCorrectValue( + HasSomeMethodWithAnnotationsImpl.class, setSomethingString, + exportedInterfaces, SubSI2.class); + + assertDescription(HasSomeMethodWithAnnotationsImpl.class, + setSomethingString, exportedInterfaces, "descr"); + + assertDescriptionOnClass( + HasSomeMethodWithAnnotationsImpl.class, + new HashSet>(Arrays + .asList(HasSomeMethodWithAnnotations.class)), "class"); + } + +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/DynamicReadableWrapperTest.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/DynamicReadableWrapperTest.java new file mode 100644 index 0000000000..3564489c4a --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/DynamicReadableWrapperTest.java @@ -0,0 +1,24 @@ +/* + * 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()); + } + +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/DynamicWritableWrapperTest.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/DynamicWritableWrapperTest.java new file mode 100644 index 0000000000..4c3e72f91e --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/DynamicWritableWrapperTest.java @@ -0,0 +1,133 @@ +/* + * 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); + } + + } + +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/factoriesresolver/HardcodedModuleFactoriesResolver.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/factoriesresolver/HardcodedModuleFactoriesResolver.java new file mode 100644 index 0000000000..e489a2256b --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/factoriesresolver/HardcodedModuleFactoriesResolver.java @@ -0,0 +1,28 @@ +/* + * 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 list; + + public HardcodedModuleFactoriesResolver(ModuleFactory... list) { + this.list = Arrays.asList(list); + } + + @Override + public List getAllFactories() { + return list; + } + +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/factoriesresolver/HierarchicalConfigMBeanFactoriesHolderTest.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/factoriesresolver/HierarchicalConfigMBeanFactoriesHolderTest.java new file mode 100644 index 0000000000..7611b92d7a --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/factoriesresolver/HierarchicalConfigMBeanFactoriesHolderTest.java @@ -0,0 +1,11 @@ +/* + * 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 { +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/runtimembean/RuntimeBeanRegistratorImplTest.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/runtimembean/RuntimeBeanRegistratorImplTest.java new file mode 100644 index 0000000000..ce3648d160 --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/runtimembean/RuntimeBeanRegistratorImplTest.java @@ -0,0 +1,160 @@ +/* + * 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 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. 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(); + } + +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/runtimembean/TestingRuntimeBean.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/runtimembean/TestingRuntimeBean.java new file mode 100644 index 0000000000..96960d3378 --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/runtimembean/TestingRuntimeBean.java @@ -0,0 +1,22 @@ +/* + * 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() { + + } + +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/runtimembean/TestingRuntimeBeanMXBean.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/runtimembean/TestingRuntimeBeanMXBean.java new file mode 100644 index 0000000000..3d4634f45d --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/runtimembean/TestingRuntimeBeanMXBean.java @@ -0,0 +1,16 @@ +/* + * 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(); +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/util/InterfacesHelperTest.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/util/InterfacesHelperTest.java new file mode 100644 index 0000000000..6736ba1111 --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/util/InterfacesHelperTest.java @@ -0,0 +1,62 @@ +/* + * 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> expected = Sets.> 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> expected = Sets.> newHashSet(SuperBMXBean.class, SubA.class); + assertEquals(expected, InterfacesHelper.getMXInterfaces(SubClass.class)); + } + +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/util/ObjectNameUtilTest.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/util/ObjectNameUtilTest.java new file mode 100644 index 0000000000..03380392e8 --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/util/ObjectNameUtilTest.java @@ -0,0 +1,43 @@ +/* + * 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 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); + } + } +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/parallelapsp/TestingAPSP.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/parallelapsp/TestingAPSP.java new file mode 100644 index 0000000000..a4c1a6f0bf --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/parallelapsp/TestingAPSP.java @@ -0,0 +1,14 @@ +/* + * 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(); + +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/parallelapsp/TestingParallelAPSPConfigMXBean.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/parallelapsp/TestingParallelAPSPConfigMXBean.java new file mode 100644 index 0000000000..4fd2f5f1b5 --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/parallelapsp/TestingParallelAPSPConfigMXBean.java @@ -0,0 +1,30 @@ +/* + * 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(); + +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/parallelapsp/TestingParallelAPSPImpl.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/parallelapsp/TestingParallelAPSPImpl.java new file mode 100644 index 0000000000..0c29386007 --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/parallelapsp/TestingParallelAPSPImpl.java @@ -0,0 +1,62 @@ +/* + * 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; + } + +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/parallelapsp/TestingParallelAPSPModule.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/parallelapsp/TestingParallelAPSPModule.java new file mode 100644 index 0000000000..de283fec03 --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/parallelapsp/TestingParallelAPSPModule.java @@ -0,0 +1,141 @@ +/* + * 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; + } +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/parallelapsp/TestingParallelAPSPModuleFactory.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/parallelapsp/TestingParallelAPSPModuleFactory.java new file mode 100644 index 0000000000..0a53d75c40 --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/parallelapsp/TestingParallelAPSPModuleFactory.java @@ -0,0 +1,62 @@ +/* + * 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 serviceInterface) { + return false; + } +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/parallelapsp/package-info.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/parallelapsp/package-info.java new file mode 100644 index 0000000000..f37aa5a2ff --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/parallelapsp/package-info.java @@ -0,0 +1,12 @@ +/* + * 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; diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/parallelapsp/test/AbstractParallelAPSPTest.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/parallelapsp/test/AbstractParallelAPSPTest.java new file mode 100644 index 0000000000..7deb1efb55 --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/parallelapsp/test/AbstractParallelAPSPTest.java @@ -0,0 +1,48 @@ +/* + * 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; + } +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/parallelapsp/test/DependentWiringTest.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/parallelapsp/test/DependentWiringTest.java new file mode 100644 index 0000000000..5b1a6f6e81 --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/parallelapsp/test/DependentWiringTest.java @@ -0,0 +1,178 @@ +/* + * 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> exception : e + .getFailedValidations().entrySet()) { + for (Map.Entry 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> exception : e + .getFailedValidations().entrySet()) { + for (Map.Entry 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)); + + } +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/parallelapsp/test/MockedDependenciesTest.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/parallelapsp/test/MockedDependenciesTest.java new file mode 100644 index 0000000000..c27e01f19b --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/parallelapsp/test/MockedDependenciesTest.java @@ -0,0 +1,130 @@ +/* + * 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(); + } + +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/TestingScheduledThreadPoolConfigBeanMXBean.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/TestingScheduledThreadPoolConfigBeanMXBean.java new file mode 100644 index 0000000000..df86232906 --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/TestingScheduledThreadPoolConfigBeanMXBean.java @@ -0,0 +1,18 @@ +/* + * 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); +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/TestingScheduledThreadPoolIfc.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/TestingScheduledThreadPoolIfc.java new file mode 100644 index 0000000000..3437eca29a --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/TestingScheduledThreadPoolIfc.java @@ -0,0 +1,16 @@ +/* + * 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(); + +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/TestingScheduledThreadPoolImpl.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/TestingScheduledThreadPoolImpl.java new file mode 100644 index 0000000000..fb97b64faf --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/TestingScheduledThreadPoolImpl.java @@ -0,0 +1,78 @@ +/* + * 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 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; + } + +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/TestingScheduledThreadPoolModule.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/TestingScheduledThreadPoolModule.java new file mode 100644 index 0000000000..4716d589f1 --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/TestingScheduledThreadPoolModule.java @@ -0,0 +1,111 @@ +/* + * 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; + } + +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/TestingScheduledThreadPoolModuleFactory.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/TestingScheduledThreadPoolModuleFactory.java new file mode 100644 index 0000000000..8244bc153d --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/TestingScheduledThreadPoolModuleFactory.java @@ -0,0 +1,66 @@ +/* + * 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> ifc = Arrays + .asList(TestingScheduledThreadPoolServiceInterface.class, TestingThreadPoolServiceInterface.class); + + @Override + public boolean isModuleImplementingServiceInterface( + Class 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; + } + +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/package-info.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/package-info.java new file mode 100644 index 0000000000..998627ede2 --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/package-info.java @@ -0,0 +1,12 @@ +/* + * 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; diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/runtimebeans/TestingScheduledRuntimeBean.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/runtimebeans/TestingScheduledRuntimeBean.java new file mode 100644 index 0000000000..c16e95d0dd --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/runtimebeans/TestingScheduledRuntimeBean.java @@ -0,0 +1,21 @@ +/* + * 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; + } + +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/runtimebeans/TestingScheduledRuntimeMXBean.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/runtimebeans/TestingScheduledRuntimeMXBean.java new file mode 100644 index 0000000000..31902dfebd --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/runtimebeans/TestingScheduledRuntimeMXBean.java @@ -0,0 +1,16 @@ +/* + * 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(); + +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/test/AbstractScheduledTest.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/test/AbstractScheduledTest.java new file mode 100644 index 0000000000..28fea454fe --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/test/AbstractScheduledTest.java @@ -0,0 +1,42 @@ +/* + * 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()); + } +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/test/RuntimeBeanTest.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/test/RuntimeBeanTest.java new file mode 100644 index 0000000000..d2a0507c2e --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/test/RuntimeBeanTest.java @@ -0,0 +1,161 @@ +/* + * 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:
+ * {@link org.opendaylight.controller.config.manager.testingservices.scheduledthreadpool.TestingScheduledThreadPoolModuleFactory#NAME} + * ,
+ * {@link org.opendaylight.controller.config.manager.testingservices.threadpool.TestingFixedThreadPoolModuleFactory#NAME} + *
+ *
+ * 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. newHashMap()); + // additional runtime bean + ObjectName ifc1runtimeON2 = ObjectNameUtil.createRuntimeBeanName( + TestingScheduledThreadPoolModuleFactory.NAME, scheduled1, + ImmutableMap.of("a", "b")); + + List 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 newInstances = Collections. emptyList(); + List reusedInstances = Lists + .newArrayList(readableConfigBean); + List recreatedInstaces = Collections + . 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 newInstances = Collections. emptyList(); + List reusedInstances = Collections. emptyList(); + List 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); + } + +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/test/TwoInterfacesExportTest.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/test/TwoInterfacesExportTest.java new file mode 100644 index 0000000000..ca038f1f1c --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/scheduledthreadpool/test/TwoInterfacesExportTest.java @@ -0,0 +1,205 @@ +/* + * 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(); + + } + +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/seviceinterface/ModifiableThreadPoolServiceInterface.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/seviceinterface/ModifiableThreadPoolServiceInterface.java new file mode 100644 index 0000000000..ab1b6bd218 --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/seviceinterface/ModifiableThreadPoolServiceInterface.java @@ -0,0 +1,16 @@ +/* + * 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 { +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/seviceinterface/TestingScheduledThreadPoolServiceInterface.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/seviceinterface/TestingScheduledThreadPoolServiceInterface.java new file mode 100644 index 0000000000..e4e388c610 --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/seviceinterface/TestingScheduledThreadPoolServiceInterface.java @@ -0,0 +1,16 @@ +/* + * 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 { +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/seviceinterface/TestingThreadPoolServiceInterface.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/seviceinterface/TestingThreadPoolServiceInterface.java new file mode 100644 index 0000000000..fd5ab1780d --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/seviceinterface/TestingThreadPoolServiceInterface.java @@ -0,0 +1,17 @@ +/* + * 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 { +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/threadpool/TestingFixedThreadPool.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/threadpool/TestingFixedThreadPool.java new file mode 100644 index 0000000000..d5434d2ed5 --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/threadpool/TestingFixedThreadPool.java @@ -0,0 +1,74 @@ +/* + * 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 allExecutors = Collections + .synchronizedList(Lists.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); + } + +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/threadpool/TestingFixedThreadPoolConfigMXBean.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/threadpool/TestingFixedThreadPoolConfigMXBean.java new file mode 100644 index 0000000000..115eb83b7f --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/threadpool/TestingFixedThreadPoolConfigMXBean.java @@ -0,0 +1,19 @@ +/* + * 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); + +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/threadpool/TestingFixedThreadPoolModule.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/threadpool/TestingFixedThreadPoolModule.java new file mode 100644 index 0000000000..ff33b164c5 --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/threadpool/TestingFixedThreadPoolModule.java @@ -0,0 +1,103 @@ +/* + * 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; + } + +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/threadpool/TestingFixedThreadPoolModuleFactory.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/threadpool/TestingFixedThreadPoolModuleFactory.java new file mode 100644 index 0000000000..ba60b39ca1 --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/threadpool/TestingFixedThreadPoolModuleFactory.java @@ -0,0 +1,65 @@ +/* + * 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> 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 serviceInterface) { + return ifc.contains(serviceInterface); + } +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/threadpool/TestingModifiableThreadPoolIfc.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/threadpool/TestingModifiableThreadPoolIfc.java new file mode 100644 index 0000000000..b51c6e3d84 --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/threadpool/TestingModifiableThreadPoolIfc.java @@ -0,0 +1,12 @@ +/* + * 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); +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/threadpool/TestingThreadPoolConfigMXBean.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/threadpool/TestingThreadPoolConfigMXBean.java new file mode 100644 index 0000000000..792a66676f --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/threadpool/TestingThreadPoolConfigMXBean.java @@ -0,0 +1,13 @@ +/* + * 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(); +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/threadpool/TestingThreadPoolIfc.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/threadpool/TestingThreadPoolIfc.java new file mode 100644 index 0000000000..2e99aa09bd --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/threadpool/TestingThreadPoolIfc.java @@ -0,0 +1,18 @@ +/* + * 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(); + +} diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/threadpool/package-info.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/threadpool/package-info.java new file mode 100644 index 0000000000..bd75a703d7 --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/threadpool/package-info.java @@ -0,0 +1,13 @@ +/* + * 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; diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/threadpool/test/SimpleConfigurationTest.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/threadpool/test/SimpleConfigurationTest.java new file mode 100644 index 0000000000..6372cb35c2 --- /dev/null +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/testingservices/threadpool/test/SimpleConfigurationTest.java @@ -0,0 +1,461 @@ +/* + * 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: + *
    + *
  1. Creation of instance
  2. + *
  3. Destruction of instance
  4. + *
  5. Reconfiguration of live object
  6. + *
  7. Reconfiguration that triggers new object creation
  8. + *
  9. Replacement of running instance with different one with same name
  10. + *
+ * 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 emptyONs = Collections + . emptyList(); + private static final ObjectName platformFixed1ON = ObjectNameUtil + .createReadOnlyModuleON(TestingFixedThreadPoolModuleFactory.NAME, fixed1); + private static final List 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> exception : e + .getFailedValidations().entrySet()) { + for (Map.Entry 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> exception : e + .getFailedValidations().entrySet()) { + for (Map.Entry entry : exception + .getValue().entrySet()) { + assertEquals( + "Parameter 'threadCount' must be greater than 0", + entry.getValue().getMessage()); + } + } + } + try { + transaction.commit(); + } catch (ValidationException e) { + for (Map.Entry> exception : e + .getFailedValidations().entrySet()) { + for (Map.Entry 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); + } + +} diff --git a/opendaylight/config/config-util/.gitignore b/opendaylight/config/config-util/.gitignore new file mode 100644 index 0000000000..fc1d35eb24 --- /dev/null +++ b/opendaylight/config/config-util/.gitignore @@ -0,0 +1,3 @@ +target +.classpath +.settings diff --git a/opendaylight/config/config-util/pom.xml b/opendaylight/config/config-util/pom.xml new file mode 100644 index 0000000000..4fd6ccda93 --- /dev/null +++ b/opendaylight/config/config-util/pom.xml @@ -0,0 +1,59 @@ + + 4.0.0 + + config-subsystem + org.opendaylight + 0.2.1-SNAPSHOT + .. + + config-util + ${project.artifactId} + jar + + + + + ${project.groupId} + config-api + 0.2.1-SNAPSHOT + + + org.jolokia + jolokia-client-java + + + org.jolokia + jolokia-jvm + agent + + + + + com.google.guava + guava + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + 1 + false + false + 1 + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + + diff --git a/opendaylight/config/config-util/src/main/java/org/opendaylight/controller/config/util/AttributeEntry.java b/opendaylight/config/config-util/src/main/java/org/opendaylight/controller/config/util/AttributeEntry.java new file mode 100644 index 0000000000..c3299ac34a --- /dev/null +++ b/opendaylight/config/config-util/src/main/java/org/opendaylight/controller/config/util/AttributeEntry.java @@ -0,0 +1,46 @@ +/* + * 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; + } + +} diff --git a/opendaylight/config/config-util/src/main/java/org/opendaylight/controller/config/util/ConfigRegistryClient.java b/opendaylight/config/config-util/src/main/java/org/opendaylight/controller/config/util/ConfigRegistryClient.java new file mode 100644 index 0000000000..e907502497 --- /dev/null +++ b/opendaylight/config/config-util/src/main/java/org/opendaylight/controller/config/util/ConfigRegistryClient.java @@ -0,0 +1,29 @@ +/* + * 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); + +} diff --git a/opendaylight/config/config-util/src/main/java/org/opendaylight/controller/config/util/ConfigRegistryJMXClient.java b/opendaylight/config/config-util/src/main/java/org/opendaylight/controller/config/util/ConfigRegistryJMXClient.java new file mode 100644 index 0000000000..3a1efaeaaf --- /dev/null +++ b/opendaylight/config/config-util/src/main/java/org/opendaylight/controller/config/util/ConfigRegistryJMXClient.java @@ -0,0 +1,170 @@ +/* + * 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 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 newMBeanProxy(ObjectName on, Class clazz) { + return JMX.newMBeanProxy(configMBeanServer, on, clazz); + } + + public T newMXBeanProxy(ObjectName on, Class 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 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 getAvailableModuleNames() { + return configRegistryProxy.getAvailableModuleNames(); + } + + @Override + public boolean isHealthy() { + return configRegistryProxy.isHealthy(); + } + + @Override + public Set lookupConfigBeans() { + return configRegistryProxy.lookupConfigBeans(); + } + + @Override + public Set lookupConfigBeans(String moduleName) { + return configRegistryProxy.lookupConfigBeans(moduleName); + } + + @Override + public Set 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 lookupRuntimeBeans() { + return configRegistryProxy.lookupRuntimeBeans(); + } + + @Override + public Set 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); + } + } + +} diff --git a/opendaylight/config/config-util/src/main/java/org/opendaylight/controller/config/util/ConfigTransactionClient.java b/opendaylight/config/config-util/src/main/java/org/opendaylight/controller/config/util/ConfigTransactionClient.java new file mode 100644 index 0000000000..75b180921a --- /dev/null +++ b/opendaylight/config/config-util/src/main/java/org/opendaylight/controller/config/util/ConfigTransactionClient.java @@ -0,0 +1,39 @@ +/* + * 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); +} diff --git a/opendaylight/config/config-util/src/main/java/org/opendaylight/controller/config/util/ConfigTransactionJMXClient.java b/opendaylight/config/config-util/src/main/java/org/opendaylight/controller/config/util/ConfigTransactionJMXClient.java new file mode 100644 index 0000000000..548c0e9ea4 --- /dev/null +++ b/opendaylight/config/config-util/src/main/java/org/opendaylight/controller/config/util/ConfigTransactionJMXClient.java @@ -0,0 +1,185 @@ +/* + * 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 newMXBeanProxy(ObjectName on, Class clazz) { + return JMX.newMXBeanProxy(configMBeanServer, on, clazz); + } + + public T newMBeanProxy(ObjectName on, Class 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 getAvailableModuleNames() { + return configControllerProxy.getAvailableModuleNames(); + } + + @Override + public ObjectName getObjectName() { + return configTransactionControllerON; + } + + @Override + public Set lookupConfigBeans() { + return configControllerProxy.lookupConfigBeans(); + } + + @Override + public Set 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 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); + } + } +} diff --git a/opendaylight/config/config-util/src/main/java/org/opendaylight/controller/config/util/jolokia/ConfigRegistryJolokiaClient.java b/opendaylight/config/config-util/src/main/java/org/opendaylight/controller/config/util/jolokia/ConfigRegistryJolokiaClient.java new file mode 100644 index 0000000000..f29f0e03ac --- /dev/null +++ b/opendaylight/config/config-util/src/main/java/org/opendaylight/controller/config/util/jolokia/ConfigRegistryJolokiaClient.java @@ -0,0 +1,139 @@ +/* + * 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 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 newInstances = jsonArrayToObjectNames(newInstancesArray); + JSONArray reusedInstancesArray = (JSONArray) jsonObject + .get("reusedInstances"); + List reusedInstances = jsonArrayToObjectNames(reusedInstancesArray); + JSONArray recreatedInstancesArray = (JSONArray) jsonObject + .get("recreatedInstances"); + List 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 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 lookupRuntimeBeans() { + return lookupSomething("lookupRuntimeBeans()", new Object[0]); + } + + @Override + public Set 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(); + } +} diff --git a/opendaylight/config/config-util/src/main/java/org/opendaylight/controller/config/util/jolokia/ConfigTransactionJolokiaClient.java b/opendaylight/config/config-util/src/main/java/org/opendaylight/controller/config/util/jolokia/ConfigTransactionJolokiaClient.java new file mode 100644 index 0000000000..f4824cd794 --- /dev/null +++ b/opendaylight/config/config-util/src/main/java/org/opendaylight/controller/config/util/jolokia/ConfigTransactionJolokiaClient.java @@ -0,0 +1,165 @@ +/* + * 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 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(); + } + +} diff --git a/opendaylight/config/config-util/src/main/java/org/opendaylight/controller/config/util/jolokia/ListableJolokiaClient.java b/opendaylight/config/config-util/src/main/java/org/opendaylight/controller/config/util/jolokia/ListableJolokiaClient.java new file mode 100644 index 0000000000..e998bebe27 --- /dev/null +++ b/opendaylight/config/config-util/src/main/java/org/opendaylight/controller/config/util/jolokia/ListableJolokiaClient.java @@ -0,0 +1,268 @@ +/* + * 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 , T extends J4pRequest> R execute( + T pRequest) { + try { + Map pProcessingOptions = new HashMap(); + 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> failedValsMap = new HashMap>(); + + for (Object key : failedVals.keySet()) { + checkArgument(key instanceof String, "Unexpected key " + key + + ", expected instance of String"); + Map innerMap = new HashMap(); + 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 getAttributes(ObjectName on) { + J4pListRequest req = new J4pListRequest(on); + J4pResponse response = execute(req); + JSONObject listJSONResponse = response.getValue(); + JSONObject attributes = (JSONObject) listJSONResponse.get("attr"); + Map listMap = new HashMap(); + for (Object entryObject : attributes.entrySet()) { + Entry entry = (Entry) 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 readResponse = execute(j4pReadRequest); + Object readResponseValue = readResponse.getValue(); + // readResponseValue can be String if there is just one attribute or + // JSONObject + Map attribsToValues = new HashMap(); + if (readResponseValue instanceof JSONObject) { + JSONObject readJSONResponse = (JSONObject) readResponseValue; + for (Object entryObject : readJSONResponse.entrySet()) { + Entry entry = (Entry) entryObject; + String key = entry.getKey(); + Object value = entry.getValue(); + attribsToValues.put(key, value); + } + } + + Map resultMap = new HashMap(); + for (Entry 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 response = execute(req); + JSONObject jsonDesc = response.getValue(); + Object description = jsonDesc.get("desc"); + return description == null ? null : description.toString(); + } + + protected List jsonArrayToObjectNames(JSONArray jsonArray) { + List 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 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 lookupSomething(String signature, + Object[] parameters) { + J4pExecRequest req = new J4pExecRequest(objectName, signature, + parameters); + JSONArray jsonArray = execute(req).getValue(); + return new HashSet<>(jsonArrayToObjectNames(jsonArray)); + } + + public Set lookupConfigBeans() { + return lookupSomething("lookupConfigBeans()", new Object[0]); + } + + public Set lookupConfigBeans(String ifcName) { + return lookupSomething("lookupConfigBeans(java.lang.String)", + new Object[] { ifcName }); + } + + public Set 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 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 getAvailableModuleNames() { + J4pReadRequest req = new J4pReadRequest(objectName, + "AvailableModuleNames"); + List value = execute(req).getValue(); + return new HashSet<>(value); + } +} diff --git a/opendaylight/config/config-util/src/test/java/org/opendaylight/controller/config/util/ConfigRegistryClientsTest.java b/opendaylight/config/config-util/src/test/java/org/opendaylight/controller/config/util/ConfigRegistryClientsTest.java new file mode 100644 index 0000000000..85397d2a20 --- /dev/null +++ b/opendaylight/config/config-util/src/test/java/org/opendaylight/controller/config/util/ConfigRegistryClientsTest.java @@ -0,0 +1,140 @@ +/* + * 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 jmxLookup = lookupRuntimeBeans(jmxRegistryClient); + Set jolokiaLookup = lookupRuntimeBeans(jolokiaRegistryClient); + assertEquals(jmxLookup, jolokiaLookup); + } + + private Set lookupRuntimeBeans(ConfigRegistryClient client) + throws Exception { + Set 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 jmxLookup = clientLookupRuntimeBeansWithModuleAndInstance( + jmxRegistryClient, TestingConfigRegistry.moduleName1, + TestingConfigRegistry.instName1); + assertEquals(1, jmxLookup.size()); + Set 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 clientLookupRuntimeBeansWithModuleAndInstance( + ConfigRegistryClient client, String moduleName, String instanceName) { + Set 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")); + } + } + +} diff --git a/opendaylight/config/config-util/src/test/java/org/opendaylight/controller/config/util/ConfigTransactionClientsTest.java b/opendaylight/config/config-util/src/test/java/org/opendaylight/controller/config/util/ConfigTransactionClientsTest.java new file mode 100644 index 0000000000..a6df1e91a5 --- /dev/null +++ b/opendaylight/config/config-util/src/test/java/org/opendaylight/controller/config/util/ConfigTransactionClientsTest.java @@ -0,0 +1,73 @@ +/* + * 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 jmxLookup = testClientLookupConfigBeans(jmxTransactionClient); + Set jolokiaLookup = testClientLookupConfigBeans(jolokiaTransactionClient); + assertEquals(jmxLookup, jolokiaLookup); + } + + private Set testClientLookupConfigBeans( + ConfigTransactionClient client) { + Set beans = client.lookupConfigBeans(); + for (ObjectName on : beans) { + assertEquals("Module", on.getKeyProperty("type")); + } + assertEquals(3, beans.size()); + return beans; + } + +} diff --git a/opendaylight/config/config-util/src/test/java/org/opendaylight/controller/config/util/JolokiaHelper.java b/opendaylight/config/config-util/src/test/java/org/opendaylight/controller/config/util/JolokiaHelper.java new file mode 100644 index 0000000000..322f3daedd --- /dev/null +++ b/opendaylight/config/config-util/src/test/java/org/opendaylight/controller/config/util/JolokiaHelper.java @@ -0,0 +1,56 @@ +/* + * 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(); + } +} diff --git a/opendaylight/config/config-util/src/test/java/org/opendaylight/controller/config/util/LookupTest.java b/opendaylight/config/config-util/src/test/java/org/opendaylight/controller/config/util/LookupTest.java new file mode 100644 index 0000000000..0749204324 --- /dev/null +++ b/opendaylight/config/config-util/src/test/java/org/opendaylight/controller/config/util/LookupTest.java @@ -0,0 +1,144 @@ +/* + * 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> 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> 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> entry : lookupProvidersToClients + .entrySet()) { + for (LookupRegistry client : entry.getValue()) { + try { + client.lookupConfigBean( + InstanceNotFoundException.class.getSimpleName(), ""); + fail(client.toString()); + } catch (InstanceNotFoundException e) { + + } + } + } + } +} diff --git a/opendaylight/config/config-util/src/test/java/org/opendaylight/controller/config/util/TestingBeanImpl.java b/opendaylight/config/config-util/src/test/java/org/opendaylight/controller/config/util/TestingBeanImpl.java new file mode 100644 index 0000000000..1d0b9d6131 --- /dev/null +++ b/opendaylight/config/config-util/src/test/java/org/opendaylight/controller/config/util/TestingBeanImpl.java @@ -0,0 +1,21 @@ +/* + * 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() { + + } +} diff --git a/opendaylight/config/config-util/src/test/java/org/opendaylight/controller/config/util/TestingBeanMXBean.java b/opendaylight/config/config-util/src/test/java/org/opendaylight/controller/config/util/TestingBeanMXBean.java new file mode 100644 index 0000000000..5d8bad361d --- /dev/null +++ b/opendaylight/config/config-util/src/test/java/org/opendaylight/controller/config/util/TestingBeanMXBean.java @@ -0,0 +1,16 @@ +/* + * 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(); + +} diff --git a/opendaylight/config/config-util/src/test/java/org/opendaylight/controller/config/util/TestingConfigRegistry.java b/opendaylight/config/config-util/src/test/java/org/opendaylight/controller/config/util/TestingConfigRegistry.java new file mode 100644 index 0000000000..d4ae42d3de --- /dev/null +++ b/opendaylight/config/config-util/src/test/java/org/opendaylight/controller/config/util/TestingConfigRegistry.java @@ -0,0 +1,146 @@ +/* + * 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 getOpenConfigs() { + return null; + } + + @Override + public boolean isHealthy() { + return false; + } + + @Override + public Set getAvailableModuleNames() { + return null; + } + + @Override + public Set lookupConfigBeans() { + return Sets.newHashSet(conf1, conf2, conf3); + } + + @Override + public Set 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 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 lookupRuntimeBeans() { + return Sets. newHashSet(run1, run2, run3); + } + + @Override + public Set lookupRuntimeBeans(String moduleName, + String instanceName) { + if (moduleName.equals(moduleName1) && instanceName.equals(instName1)) { + return Sets. newHashSet(run2); + } else if (moduleName.equals(moduleName2) + && instanceName.equals(instName2)) { + return Sets. newHashSet(run3); + } else { + return Sets. newHashSet(); + } + } + +} diff --git a/opendaylight/config/config-util/src/test/java/org/opendaylight/controller/config/util/TestingConfigTransactionController.java b/opendaylight/config/config-util/src/test/java/org/opendaylight/controller/config/util/TestingConfigTransactionController.java new file mode 100644 index 0000000000..67e31b05e5 --- /dev/null +++ b/opendaylight/config/config-util/src/test/java/org/opendaylight/controller/config/util/TestingConfigTransactionController.java @@ -0,0 +1,112 @@ +/* + * 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 getAvailableModuleNames() { + return null; + } + + @Override + public Set lookupConfigBeans() { + return Sets.newHashSet(conf1, conf2, conf3); + } + + @Override + public Set 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 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; + } + } +} diff --git a/opendaylight/config/config-util/src/test/java/org/opendaylight/controller/config/util/jolokia/ListableJolokiaClientTest.java b/opendaylight/config/config-util/src/test/java/org/opendaylight/controller/config/util/jolokia/ListableJolokiaClientTest.java new file mode 100644 index 0000000000..bff8006c51 --- /dev/null +++ b/opendaylight/config/config-util/src/test/java/org/opendaylight/controller/config/util/jolokia/ListableJolokiaClientTest.java @@ -0,0 +1,58 @@ +/* + * 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> 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>(); + Map map1 = new HashMap(); + 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())); + } + +} diff --git a/opendaylight/config/pom.xml b/opendaylight/config/pom.xml new file mode 100755 index 0000000000..c9be64af58 --- /dev/null +++ b/opendaylight/config/pom.xml @@ -0,0 +1,297 @@ + + 4.0.0 + + + org.opendaylight.controller + commons.opendaylight + 1.4.0-SNAPSHOT + ../commons/opendaylight + + + + org.opendaylight + 0.2.1-SNAPSHOT + config-subsystem + pom + ${project.artifactId} + + 3.0.4 + + + config-api + config-manager + config-util + yang-jmx-generator + yang-jmx-generator-plugin + yang-jmx-generator-it + yang-store-api + yang-store-impl + yang-test + + + UTF-8 + 1.7 + 1.7 + 4.10 + 2.3.7 + 5.0.0 + 0.6.2.201302030002 + 1.7.2 + 1.1.1 + 0.5.7-SNAPSHOT + 0.5.7-SNAPSHOT + ${project.build.directory}/generated-sources/config + + + + + junit + junit + test + + + ch.qos.logback + logback-classic + ${logback.version} + test + + + + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + org.osgi + org.osgi.core + ${osgi.version} + + + com.google.code.findbugs + jsr305 + 2.0.1 + + + commons-io + commons-io + 2.4 + + + com.google.guava + guava + 14.0.1 + + + org.jolokia + jolokia-core + ${jolokia.version} + + + org.jolokia + jolokia-jvm + ${jolokia.version} + agent + + + org.jolokia + jolokia-client-java + ${jolokia.version} + + + junit + junit + ${junit.version} + + + org.apache.commons + commons-lang3 + ${commons.lang.version} + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.5.1 + + ${java.version.source} + ${java.version.target} + ${java.version.source} + ${java.version.target} + + + + org.jacoco + jacoco-maven-plugin + ${jacoco.version} + + + + prepare-agent + + + + report + prepare-package + + check + report + + + ${basedir}/target/jacoco + false + + 80 + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + org.codehaus.mojo + build-helper-maven-plugin + + + + + + org.opendaylight.yangtools + yang-maven-plugin + ${opendaylight.yang.version} + + + + generate-sources + + + + + + org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator + + ${jmxGeneratorPath} + + + urn:opendaylight:params:xml:ns:yang:controller==org.opendaylight.controller.config.yang + + + + + true + + + + + + org.opendaylight + yang-jmx-generator-plugin + 0.2.1-SNAPSHOT + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 1.8 + + + add-source + generate-sources + + add-source + + + + ${jmxGeneratorPath} + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + package + + test-jar + + + + + + org.apache.felix + maven-bundle-plugin + ${maven.bundle.version} + true + + + ${project.groupId}.${project.artifactId} + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.14.1 + + true + classes + 1C + false + true + 2 + + + + org.apache.maven.plugins + maven-antrun-plugin + 1.3 + + + org.codehaus.groovy.maven + gmaven-plugin + 1.0 + + + + + + + + nexus.opendaylight.org + http://nexus.opendaylight.org/content/repositories/opendaylight.snapshot + + true + + + daily + + + + + + + opendaylight-snapshot + http://nexus.opendaylight.org/content/repositories/opendaylight.snapshot + + daily + + + + diff --git a/opendaylight/config/yang-jmx-generator-it/pom.xml b/opendaylight/config/yang-jmx-generator-it/pom.xml new file mode 100644 index 0000000000..847df6a7c6 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-it/pom.xml @@ -0,0 +1,89 @@ + + + 4.0.0 + + config-subsystem + org.opendaylight + 0.2.1-SNAPSHOT + + + yang-jmx-generator-it + ${project.artifactId} + + + + ${project.groupId} + config-api + 0.2.1-SNAPSHOT + test + + + ${project.groupId} + yang-test + 0.2.1-SNAPSHOT + test + + + ${project.groupId} + config-manager + 0.2.1-SNAPSHOT + test + test-jar + + + ${project.groupId} + config-manager + 0.2.1-SNAPSHOT + test + + + ${project.groupId} + config-util + 0.2.1-SNAPSHOT + test + + + org.opendaylight.bgpcep + mockito-configuration + 0.2.0-SNAPSHOT + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + 1 + false + false + + + + default-test + + true + + + + integration-tests + integration-test + + test + + + + **/org/opendaylight/controller/config/yangjmxgenerator/it/*.java + + false + + + + + + + diff --git a/opendaylight/config/yang-jmx-generator-it/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/it/ITTest.java b/opendaylight/config/yang-jmx-generator-it/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/it/ITTest.java new file mode 100644 index 0000000000..598b6b3d5b --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-it/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/it/ITTest.java @@ -0,0 +1,116 @@ +/* + * 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; + + } + +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/pom.xml b/opendaylight/config/yang-jmx-generator-plugin/pom.xml new file mode 100644 index 0000000000..e13c4e8642 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/pom.xml @@ -0,0 +1,91 @@ + + 4.0.0 + + config-subsystem + org.opendaylight + 0.2.1-SNAPSHOT + .. + + yang-jmx-generator-plugin + + + org.slf4j + slf4j-api + + + org.opendaylight + yang-jmx-generator + 0.2.1-SNAPSHOT + + + org.opendaylight.yangtools + yang-maven-plugin-spi + ${opendaylight.yang.version} + + + org.opendaylight.yangtools + binding-generator-impl + ${opendaylight.binding.version} + + + org.eclipse.jdt + core + 3.3.0-v_771 + test + + + org.freemarker + freemarker + 2.3.20 + + + ${project.groupId} + config-api + 0.2.1-SNAPSHOT + + + commons-io + commons-io + + + + com.googlecode.slf4j-maven-plugin-log + slf4j-maven-plugin-log + 1.0.0 + + + com.google.guava + guava + + + + org.opendaylight + yang-jmx-generator + 0.2.1-SNAPSHOT + test + test-jar + + + org.eclipse + jdt + 3.3.0-v20070607-1300 + test + + + org.opendaylight.bgpcep + mockito-configuration + 0.2.0-SNAPSHOT + test + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/CodeWriter.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/CodeWriter.java new file mode 100644 index 0000000000..13d828a430 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/CodeWriter.java @@ -0,0 +1,23 @@ +/* + * 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 writeMbe(ModuleMXBeanEntry mbe, File targetBaseDir, + File mainBaseDir, File resourceBaseDir); + +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/FreeMarkerCodeWriterImpl.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/FreeMarkerCodeWriterImpl.java new file mode 100644 index 0000000000..2b84ed1d45 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/FreeMarkerCodeWriterImpl.java @@ -0,0 +1,108 @@ +/* + * 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 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 writeMbe(ModuleMXBeanEntry mbe, File targetBaseDir, + File mainBaseDir, File resourceBaseDir) { + try { + List 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 runtimeBeans = mbe.getRuntimeBeans(); + if (runtimeBeans.size() > 0) { + List allFtlFiles = new ArrayList<>(); + { // registrators + Map registratorNamesToFtls = RuntimeRegistratorFtlTemplate + .create(RuntimeRegistratorFtlTemplate.findRoot(runtimeBeans)); + + allFtlFiles.addAll(registratorNamesToFtls.values()); + } + { // TOs, MXBean interfaces + for (RuntimeBeanEntry runtimeBeanEntry : runtimeBeans) { + Collection ftlFiles = TemplateFactory + .getTOAndMXInterfaceFtlFiles(runtimeBeanEntry) + .values(); + allFtlFiles.addAll(ftlFiles); + } + } + boolean overwrite = true; + + FtlFilePersister ftlFilePersister = new FtlFilePersister(); + List persisted = ftlFilePersister.persist(allFtlFiles, + targetBaseDir, overwrite); + // FIXME: check for intersection + generatedFiles.addAll(persisted); + } + + // purge nulls + for (Iterator 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); + } + } + +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/JMXGenerator.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/JMXGenerator.java new file mode 100644 index 0000000000..743ffba739 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/JMXGenerator.java @@ -0,0 +1,291 @@ +/* + * 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 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 generateSources(SchemaContext context, + File outputBaseDir, Set 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 qNamesToSIEs = new HashMap<>(); + + // create SIE structure qNamesToSIEs + for (Module module : context.getModules()) { + String packageName = packageTranslator.getPackageName(module); + Map namesToSIEntries = ServiceInterfaceEntry + .create(module, packageName); + + for (Entry 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 namesToMBEs = ModuleMXBeanEntry + .create(module, qNamesToSIEs, context, new TypeProviderWrapper(new TypeProviderImpl(context)), + packageName); + + for (Entry mbeEntry : namesToMBEs + .entrySet()) { + ModuleMXBeanEntry mbe = mbeEntry.getValue(); + try { + List 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 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 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 extractNamespaceMapping( + Map additionalCfg) { + Map 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 files = Sets.newHashSet(); + + void addFile(File file) { + if (files.contains(file)) { + List 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 files) { + for (File file : files) { + addFile(file); + } + } + + public Set getFiles() { + return files; + } + } +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/AbstractFactoryTemplate.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/AbstractFactoryTemplate.java new file mode 100644 index 0000000000..b9245a497b --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/AbstractFactoryTemplate.java @@ -0,0 +1,91 @@ +/* + * 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 implementedIfcs = Lists + .newArrayList(ModuleFactory.class.getCanonicalName()); + + private final String globallyUniqueName, moduleInstanceType; + private final List providedServices; + + public AbstractFactoryTemplate(Header header, String packageName, + String abstractFactoryName, String globallyUniqueName, + String moduleInstanceType, List fields, + List providedServices) { + super(header, packageName, abstractFactoryName, Collections + . emptyList(), implementedIfcs, fields, Collections + . emptyList(), true, false, Collections + . 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 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"; + } + +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/AbstractFtlTemplate.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/AbstractFtlTemplate.java new file mode 100644 index 0000000000..7f80299f00 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/AbstractFtlTemplate.java @@ -0,0 +1,103 @@ +/* + * 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 fields; + private final List annotations; + private final List methods; + private String javadoc = null; + private final TypeDeclaration typeDeclaration; + private final Header header; + + protected AbstractFtlTemplate(Header header, String packageName, + List fields, List 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 getAnnotations() { + return annotations; + } + + @Override + public List getFields() { + return fields; + } + + @Override + public List 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 + '\'' + '}'; + } +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/AbstractModuleTemplate.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/AbstractModuleTemplate.java new file mode 100644 index 0000000000..c40bfdfb9f --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/AbstractModuleTemplate.java @@ -0,0 +1,102 @@ +/* + * 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 moduleFields; + private final boolean runtime; + private final String registratorType; + + public AbstractModuleTemplate(Header header, String packageName, + String abstractModuleName, List implementedIfcs, + List moduleFields, List methods, + boolean isRuntime, String registratorType) { + super(header, packageName, abstractModuleName, Collections + . emptyList(), implementedIfcs, Collections + . emptyList(), methods, true, false, Collections + . emptyList()); + this.moduleFields = moduleFields; + this.runtime = isRuntime; + this.registratorType = registratorType; + } + + public List 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(); + } + +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/FtlFilePersister.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/FtlFilePersister.java new file mode 100644 index 0000000000..00cc8caa66 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/FtlFilePersister.java @@ -0,0 +1,120 @@ +/* + * 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 serializeFtls( + Collection ftlFiles) { + Map 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 persist(Collection ftlFiles, + File dstFolder, boolean overwrite) throws IOException { + Map ftlFileStringMap = serializeFtls(ftlFiles); + List result = new ArrayList<>(); + for (Entry 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; + } + +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/FtlTemplate.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/FtlTemplate.java new file mode 100644 index 0000000000..8172a58af6 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/FtlTemplate.java @@ -0,0 +1,47 @@ +/* + * 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 getAnnotations(); + + TypeDeclaration getTypeDeclaration(); + + public String getFullyQualifiedName(); + + public List getFields(); + + List getMethods(); + + /** + * @return relative path to file to be created. + */ + public File getRelativeFile(); + + /** + * + * @return ftl template location + */ + public String getFtlTempleteLocation(); +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/GeneralClassTemplate.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/GeneralClassTemplate.java new file mode 100644 index 0000000000..4df7b2a55b --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/GeneralClassTemplate.java @@ -0,0 +1,52 @@ +/* + * 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 constructors; + + public GeneralClassTemplate(Header header, String packageName, String name, + List extendedClasses, List implementedIfcs, + List fields, List methods) { + this(header, packageName, name, extendedClasses, implementedIfcs, + fields, methods, false, false, Collections + . emptyList()); + } + + public GeneralClassTemplate(Header header, String packageName, String name, + List extendedClasses, List implementedIfcs, + List fields, List methods, + boolean isAbstract, boolean isFinal, List constructors) { + super(header, packageName, fields, methods, new TypeDeclaration( + "class", name, checkCardinality(extendedClasses), + implementedIfcs, isAbstract, isFinal)); + this.constructors = constructors; + } + + static List checkCardinality(List extendedClass) { + if (extendedClass.size() > 1) + throw new IllegalArgumentException( + "Class cannot have more than one super " + "class"); + return extendedClass; + } + + public List getConstructors() { + return constructors; + } + +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/GeneralInterfaceTemplate.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/GeneralInterfaceTemplate.java new file mode 100644 index 0000000000..1539e10885 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/GeneralInterfaceTemplate.java @@ -0,0 +1,27 @@ +/* + * 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 extendedInterfaces, + List methods) { + super(header, packageName, Collections. emptyList(), methods, + new TypeDeclaration("interface", name, extendedInterfaces, + Collections. emptyList())); + } +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/RuntimeRegistratorFtlTemplate.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/RuntimeRegistratorFtlTemplate.java new file mode 100644 index 0000000000..d07edae14e --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/RuntimeRegistratorFtlTemplate.java @@ -0,0 +1,296 @@ +/* + * 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 fields, List methods) { + // TODO header + super(null, runtimeBeanEntry.getPackageName(), name, Collections + . emptyList(), Arrays.asList(Closeable.class + .getCanonicalName()), fields, methods); + } + + public static RuntimeBeanEntry findRoot( + Collection 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 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 create(RuntimeBeanEntry rootRB) { + checkArgument(rootRB.isRoot(), "RuntimeBeanEntry must be root"); + String registratorName = getJavaNameOfRuntimeRegistrator(rootRB); + List methods = new ArrayList<>(); + Field rootRuntimeBeanRegistratorField = new Field( + Lists.newArrayList("final"), + RootRuntimeBeanRegistrator.class.getName(), + "rootRuntimeBeanRegistrator"); + List constructorParameters = Lists + .newArrayList(rootRuntimeBeanRegistratorField); + String constructorBody = constructConstructorBody(constructorParameters); + MethodDefinition constructor = MethodDefinition.createConstructor( + registratorName, constructorParameters, constructorBody); + methods.add(constructor); + + LinkedHashMap RuntimeRegistratorFtlTemplates = createRegistrationHierarchy( + rootRB, Collections. 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. emptyList(), Arrays.asList(Closeable.class + .getCanonicalName()), constructorParameters, methods); + + checkState(RuntimeRegistratorFtlTemplates.containsKey(registrator + .getTypeDeclaration().getName()) == false, "Name conflict: " + + registrator.getTypeDeclaration().getName()); + Map 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 createRegistrationHierarchy( + RuntimeBeanEntry parent, Set occupiedKeys) { + LinkedHashMap unorderedResult = new LinkedHashMap<>(); + List methods = new ArrayList<>(); + + // hierarchy of ON is created as follows: + // root RB: , type=RuntimeBean + // 1st RB in hierarchy: , type=RuntimeBean, : key or counter + // n-th RB in hierarchy has same ON as n-1, with added : 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 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 childRegistratorMap = createRegistrationHierarchy( + child, currentOccupiedKeys); + for (Entry 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. emptyList(), + Collections. emptyList(), body.toString()); + methods.add(register); + + } + } + + // create parent registration + String createdName = getJavaNameOfRuntimeRegistration(parent.getJavaNamePrefix()); + + List 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 privateFields = Lists.newArrayList(registratorsMapField); + privateFields.addAll(constructorParameters); + + RuntimeRegistratorFtlTemplate created = new RuntimeRegistratorFtlTemplate( + parent, createdName, privateFields, methods); + + LinkedHashMap 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. emptyList(), "void", + // "close", Collections. emptyList(), + // Arrays.asList(IOException.class.getCanonicalName()), + // Collections. emptyList(), body); + List annotations = Lists.newArrayList(new Annotation( + "Override", Collections. emptyList())); + return new MethodDefinition(Collections. emptyList(), "void", + "close", Collections. emptyList(), + Collections. 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"; + } +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/StubFactoryTemplate.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/StubFactoryTemplate.java new file mode 100644 index 0000000000..be49f93bca --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/StubFactoryTemplate.java @@ -0,0 +1,39 @@ +/* + * 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. emptyList(), Collections + . emptyList(), Collections + . emptyList()); + this.moduleInstanceType = moduleInstanceType; + } + + public String getModuleInstanceType() { + return moduleInstanceType; + } + + public String getDynamicMBeanWithInstanceType() { + return DynamicMBeanWithInstance.class.getCanonicalName(); + } +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/StubModuleTemplate.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/StubModuleTemplate.java new file mode 100644 index 0000000000..f49bbbacab --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/StubModuleTemplate.java @@ -0,0 +1,73 @@ +/* + * 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. emptyList(), + Collections. emptyList(), Collections + . emptyList(), false, true, + Collections. 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"; + } +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/TemplateFactory.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/TemplateFactory.java new file mode 100644 index 0000000000..641fb25c1a --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/TemplateFactory.java @@ -0,0 +1,675 @@ +/* + * 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 getFtlTemplates( + ModuleMXBeanEntry entry) { + Map 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 getFtlStubTemplates( + ModuleMXBeanEntry entry) { + Map 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 getFtlTemplates( + ServiceInterfaceEntry entry) { + + Map 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 getTOAndMXInterfaceFtlFiles( + RuntimeBeanEntry entry) { + Map result = new HashMap<>(); + { // create GeneralInterfaceFtlFile for runtime MXBean. Attributes will + // be transformed to getter methods + String mxBeanTypeName = entry.getJavaNameOfRuntimeMXBean(); + List extendedInterfaces = Arrays.asList(RuntimeBean.class + .getCanonicalName()); + List 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. emptyList()); + methods.add(getter); + } + // add rpc methods + for (Rpc rpc : entry.getRpcs()) { + // convert JavaAttribute parameters into fields + List fields = new ArrayList<>(); + for (JavaAttribute ja : rpc.getParameters()) { + Field field = new Field(Collections. 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 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. 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 transformed = Collections2.transform(mbe + .getProvidedServices().keySet(), + new Function() { + + @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 moduleFields = attrProcessor.getModuleFields(); + List 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. newArrayList(), + attrProcessor.getMethods()); + ifcTemplate.setJavadoc(mbe.getNullableDescription()); + return ifcTemplate; + } + + public static Map tOsFromMbe( + ModuleMXBeanEntry mbe) { + Map 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 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. emptyList(), + Collections. emptyList(), to.getFields(), + to.getMethods(), false, false, constructors)); + } + return retVal; + } + + public static Map tOsFromRbe( + RuntimeBeanEntry rbe) { + Map 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 constructors = Lists.newArrayList(); + constructors.add(new Constructor(to.getName(), "super();")); + + // TODO header + retVal.put( + to.getType(), + new GeneralClassTemplate(null, rbe.getPackageName(), to + .getName(), Collections. emptyList(), + Collections. 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 tos = Lists.newArrayList(); + + void processAttributes(Map attributes, + String packageName) { + for (Entry 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 attrs = ((TOAttribute) attributeIfc) + .getCapitalizedPropertiesToTypesMap(); + // recursive processing + processAttributes(attrs, packageName); + + tos.add(new TOInternal(type, name, attrs, packageName)); + } + + List getTOs() { + return tos; + } + + private static class TOInternal { + private final String type, name; + private List fields; + private List methods; + + public TOInternal(String type, String name, + Map attrs, String packageName) { + super(); + this.type = type; + this.name = name; + processAttrs(attrs, packageName); + } + + private void processAttrs(Map attrs, + String packageName) { + fields = Lists.newArrayList(); + methods = Lists.newArrayList(); + + for (Entry 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. 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 getFields() { + return fields; + } + + List getMethods() { + return methods; + } + } + } + + private static class MXBeanInterfaceAttributesProcessor { + private static final String STRING_FULLY_QUALIFIED_NAME = "java.util.List"; + private final List methods = Lists.newArrayList(); + + void processAttributes(Map attributes, + String packageName) { + for (Entry 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. 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 getMethods() { + return methods; + } + } + + private static class AbstractFactoryAttributesProcessor { + + private final List fields = Lists.newArrayList(); + private static final String STRING_FULLY_QUALIFIED_NAME = "java.util.List"; + + void processAttributes(Map attributes, + String packageName) { + for (Entry 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 getFields() { + return fields; + } + } + + private static class AbstractModuleAttributesProcessor { + + private static final String STRING_FULLY_QUALIFIED_NAME = "java.util.List"; + + private final List moduleFields = Lists.newArrayList(); + private final List methods = Lists.newArrayList(); + + void processAttributes(Map attributes, + String packageName) { + for (Entry 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. emptyList()); + List 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. 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 getModuleFields() { + return moduleFields; + } + + List getMethods() { + return methods; + } + + } + +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/TypeHelper.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/TypeHelper.java new file mode 100644 index 0000000000..e097516943 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/TypeHelper.java @@ -0,0 +1,34 @@ +/* + * 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" 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(); + } +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/directives/AnnotationsDirective.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/directives/AnnotationsDirective.java new file mode 100644 index 0000000000..4a34a1f294 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/directives/AnnotationsDirective.java @@ -0,0 +1,85 @@ +/* + * 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 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 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(" +", " "); + } + +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/directives/ConstructorsDirective.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/directives/ConstructorsDirective.java new file mode 100644 index 0000000000..56b7286eb5 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/directives/ConstructorsDirective.java @@ -0,0 +1,76 @@ +/* + * 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 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()); + } + +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/directives/FieldsDirectiveProg.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/directives/FieldsDirectiveProg.java new file mode 100644 index 0000000000..7dd0f8e992 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/directives/FieldsDirectiveProg.java @@ -0,0 +1,74 @@ +/* + * 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 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()); +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/directives/FieldsDirectiveTemplate.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/directives/FieldsDirectiveTemplate.java new file mode 100644 index 0000000000..268d898477 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/directives/FieldsDirectiveTemplate.java @@ -0,0 +1,57 @@ +/* + * 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 field.static==true>static " + + "${field.type} ${field.name}<#if field.definition??> = ${field.definition};" + + System.lineSeparator() + " "; + Template t = new Template("name", new StringReader(templateStr), + new Configuration()); + + try { + Map 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); + } + } + +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/directives/HeaderDirective.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/directives/HeaderDirective.java new file mode 100644 index 0000000000..93fde59e96 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/directives/HeaderDirective.java @@ -0,0 +1,81 @@ +/* + * 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()); + } +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/directives/JavadocDirective.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/directives/JavadocDirective.java new file mode 100644 index 0000000000..9817be3ea3 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/directives/JavadocDirective.java @@ -0,0 +1,65 @@ +/* + * 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()); + } + +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/directives/MethodsDirective.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/directives/MethodsDirective.java new file mode 100644 index 0000000000..975d8fd620 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/directives/MethodsDirective.java @@ -0,0 +1,115 @@ +/* + * 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 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()); + } +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/directives/ModuleFieldsDirective.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/directives/ModuleFieldsDirective.java new file mode 100644 index 0000000000..144419e8ae --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/directives/ModuleFieldsDirective.java @@ -0,0 +1,88 @@ +/* + * 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 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()); + } +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/directives/TypeDeclarationDirective.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/directives/TypeDeclarationDirective.java new file mode 100644 index 0000000000..42556c243e --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/directives/TypeDeclarationDirective.java @@ -0,0 +1,80 @@ +/* + * 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 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); + } + +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/directives/UnimplementedExceptionDirective.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/directives/UnimplementedExceptionDirective.java new file mode 100644 index 0000000000..5175ade438 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/directives/UnimplementedExceptionDirective.java @@ -0,0 +1,39 @@ +/* + * 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()); + } +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/model/Annotation.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/model/Annotation.java new file mode 100644 index 0000000000..5ec359ea8d --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/model/Annotation.java @@ -0,0 +1,91 @@ +/* + * 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 params; + + public Annotation(String name, List params) { + this.name = name; + this.params = params; + } + + public String getName() { + return name; + } + + public List 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 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; + } + } + +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/model/Constructor.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/model/Constructor.java new file mode 100644 index 0000000000..1aeb86d25d --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/model/Constructor.java @@ -0,0 +1,40 @@ +/* + * 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; + } + +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/model/Field.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/model/Field.java new file mode 100644 index 0000000000..517143e656 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/model/Field.java @@ -0,0 +1,51 @@ +/* + * 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 modifiers; + + public Field(String type, String name) { + this(Lists. newArrayList(), type, name, null); + } + + public Field(List modifiers, String type, String name) { + this(modifiers, type, name, null); + } + + public Field(List 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 getModifiers() { + return modifiers; + } + + public String getName() { + return name; + } + + public String getDefinition() { + return definition; + } +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/model/Header.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/model/Header.java new file mode 100644 index 0000000000..047e87a3dc --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/model/Header.java @@ -0,0 +1,33 @@ +/* + * 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; + } + +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/model/Method.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/model/Method.java new file mode 100644 index 0000000000..3d2fff7f98 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/model/Method.java @@ -0,0 +1,24 @@ +/* + * 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 getModifiers(); + + String getReturnType(); + + String getName(); + + List getParameters(); + + String getJavadoc(); + + List getAnnotations(); +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/model/MethodDeclaration.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/model/MethodDeclaration.java new file mode 100644 index 0000000000..bd4e22e084 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/model/MethodDeclaration.java @@ -0,0 +1,66 @@ +/* + * 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 parameters; + private String javadoc = null; + private final List annotations; + + public MethodDeclaration(String returnType, String name, + List parameters) { + this(returnType, name, parameters, Collections. emptyList()); + } + + public MethodDeclaration(String returnType, String name, + List parameters, List annotations) { + this.returnType = returnType; + this.name = name; + this.parameters = parameters; + this.annotations = annotations; + } + + @Override + public List 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 getParameters() { + return parameters; + } + + @Override + public List getModifiers() { + return Collections.emptyList(); + } +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/model/MethodDefinition.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/model/MethodDefinition.java new file mode 100644 index 0000000000..bf453ac135 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/model/MethodDefinition.java @@ -0,0 +1,96 @@ +/* + * 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 modifiers; + private final String returnType; + private final String name; + private final List parameters; + private final List throwsExceptions; + private final String body; + private String javadoc = null; + private final List annotations; + + // TODO remove, Constructor is in separate class + public static MethodDefinition createConstructor(String name, + List parameters, String body) { + return new MethodDefinition("", name, parameters, body); + + } + + public MethodDefinition(String returnType, String name, + List parameters, String body) { + this(Collections. emptyList(), returnType, name, parameters, + Collections. emptyList(), Collections + . emptyList(), body); + } + + public MethodDefinition(String returnType, String name, + List parameters, List annotations, String body) { + this(Collections. emptyList(), returnType, name, parameters, + Collections. emptyList(), annotations, body); + } + + public MethodDefinition(List modifiers, String returnType, + String name, List parameters, List throwsExceptions, + List 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 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 getParameters() { + return parameters; + } + + public List getThrowsExceptions() { + return throwsExceptions; + } + + public String getBody() { + return body; + } + + @Override + public List getModifiers() { + return modifiers; + } +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/model/ModuleField.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/model/ModuleField.java new file mode 100644 index 0000000000..293696d10e --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/model/ModuleField.java @@ -0,0 +1,52 @@ +/* + * 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 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. 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; + } +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/model/TypeDeclaration.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/model/TypeDeclaration.java new file mode 100644 index 0000000000..ee0aa2ff5c --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/model/TypeDeclaration.java @@ -0,0 +1,62 @@ +/* + * 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 extended, implemented; + private final boolean isAbstract, isFinal; + + public TypeDeclaration(String type, String name, List extended, + List 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 extended, + List 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 getExtended() { + return extended; + } + + public List getImplemented() { + return implemented; + } + + @Override + public String toString() { + return "TypeDeclaration{" + "type='" + type + '\'' + ", name='" + name + + '\'' + '}'; + } +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/resources/freeMarker/abstract_ftl_file.ftl b/opendaylight/config/yang-jmx-generator-plugin/src/main/resources/freeMarker/abstract_ftl_file.ftl new file mode 100644 index 0000000000..0c6bf839fa --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/resources/freeMarker/abstract_ftl_file.ftl @@ -0,0 +1,15 @@ +<@headerD header=header/> +package ${packageName}; + +<@javadocD object=javadoc/> +<@annotationsD object=annotations/> +<#-- class/interface --> +<@typeDeclarationD object=typeDeclaration/> +{ + +<@constructorsD object=constructors> + +<@fieldsD object=fields/> + +<@methodsD object=methods/> +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/resources/freeMarker/factory_abs_template.ftl b/opendaylight/config/yang-jmx-generator-plugin/src/main/resources/freeMarker/factory_abs_template.ftl new file mode 100644 index 0000000000..adfbcebd0b --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/resources/freeMarker/factory_abs_template.ftl @@ -0,0 +1,63 @@ +<@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> serviceIfcs = new java.util.HashSet>(); + <#if providedServices??> + static { + <#list providedServices as refId> + serviceIfcs.add(${refId}); + + } + + + @Override + public final boolean isModuleImplementingServiceInterface(Class 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}()); + + + 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"); + } + +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/resources/freeMarker/module_abs_template_new.ftl b/opendaylight/config/yang-jmx-generator-plugin/src/main/resources/freeMarker/module_abs_template_new.ftl new file mode 100644 index 0000000000..90412d7bb5 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/resources/freeMarker/module_abs_template_new.ftl @@ -0,0 +1,153 @@ +<@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; + + + 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); + } + + + @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); + + + } + + // 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; + } + + + + + @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) { + + + ${field.name}Dependency = dependencyResolver.resolveInstance(${field.dependency.sie.exportedOsgiClassName}.class, ${field.name}, ${field.name}JmxAttribute); + + <#if field.dependency.mandatory==false> + } + + + + + 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; + + + + return true; + } + +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/resources/freeMarker/module_stub_template.ftl b/opendaylight/config/yang-jmx-generator-plugin/src/main/resources/freeMarker/module_stub_template.ftl new file mode 100644 index 0000000000..d2350ac40b --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/resources/freeMarker/module_stub_template.ftl @@ -0,0 +1,27 @@ +<@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/> + } +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/AbstractGeneratorTest.java b/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/AbstractGeneratorTest.java new file mode 100644 index 0000000000..9e2f334dac --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/AbstractGeneratorTest.java @@ -0,0 +1,31 @@ +/* + * 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); + } + +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/JMXGeneratorFileNamesValidationTest.java b/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/JMXGeneratorFileNamesValidationTest.java new file mode 100644 index 0000000000..b49b17e3a0 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/JMXGeneratorFileNamesValidationTest.java @@ -0,0 +1,52 @@ +/* + * 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")); + } + } + +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/JMXGeneratorGeneratedFilesTrackerTest.java b/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/JMXGeneratorGeneratedFilesTrackerTest.java new file mode 100644 index 0000000000..4112ff017a --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/JMXGeneratorGeneratedFilesTrackerTest.java @@ -0,0 +1,26 @@ +/* + * 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")); + } +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/JMXGeneratorTest.java b/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/JMXGeneratorTest.java new file mode 100644 index 0000000000..0c2678fd53 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/JMXGeneratorTest.java @@ -0,0 +1,712 @@ +/* + * 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 map = Maps.newHashMap(); + protected File outputBaseDir; + File generatedResourcesDir; + + private static final List 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 expectedBGPNames = ServiceInterfaceEntryTest + .toFileNames("[AbstractBgpListenerImplModule.java, " + "AbstractBgpListenerImplModuleFactory.java, " + + "BgpListenerImplModule.java, " + "BgpListenerImplModuleFactory.java, " + + "BgpListenerImplModuleMXBean.java, Peers.java]"); + + private static final List 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 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 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 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 files = jmxGenerator.generateSources(context, + outputBaseDir, context.getModules()); + List 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 toFileNames(Collection files) { + List result = new ArrayList<>(); + for (File f : files) { + result.add(f.getName()); + } + Collections.sort(result); + return result; + } + + @Test + public void generateSIEsTest() throws IOException { + Collection files = jmxGenerator.generateSources(context, + outputBaseDir, Sets.newHashSet(threadsModule)); + assertEquals(ServiceInterfaceEntryTest.expectedSIEFileNames, + toFileNames(files)); + + Map 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 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 files = jmxGenerator.generateSources(context, + outputBaseDir, Sets.newHashSet(threadsJavaModule)); + + assertEquals(expectedModuleFileNames, toFileNames(files)); + + Map verifiers = Maps.newHashMap(); + + Collection xmlFiles = Collections2.filter(files, + new Predicate() { + + @Override + public boolean apply(File input) { + return input.getName().endsWith("xml"); + } + }); + + Collection javaFiles = Collections2.filter(files, + new Predicate() { + + @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 lines = Sets.newHashSet(FileUtils + .readLines(moduleFactoryFile)); + Set 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 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 fieldDeclarations = visitor.fieldDeclarations; + assertDeclaredField(fieldDeclarations, + "public static final java.lang.String NAME=\"threadfactory-naming\""); + assertDeclaredField( + fieldDeclarations, + "private static final java.util.Set> serviceIfcs=new java.util.HashSet>()"); + + 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 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 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 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 fieldDeclarations = Sets.newHashSet(); + private final Set constructors = Sets.newHashSet(); + private final Map methods = Maps.newHashMap(); + private final Map requireIfc = Maps.newHashMap(); + private final Map 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 files, + Map 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 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; + } + +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ModuleMXBeanEntryPluginTest.java b/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ModuleMXBeanEntryPluginTest.java new file mode 100644 index 0000000000..b457216480 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ModuleMXBeanEntryPluginTest.java @@ -0,0 +1,160 @@ +/* + * 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 namesToMBEs = loadThreadsJava(); + {// check threadfactory-naming + ModuleMXBeanEntry threadFactoryNaming = namesToMBEs + .get(THREADFACTORY_NAMING_MXB_NAME); + Collection 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 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 runtimeBeans = threadFactoryNaming + .getRuntimeBeans(); + assertThat(runtimeBeans.size(), is(4)); + + { + RuntimeBeanEntry streamRB = findFirstByYangName(runtimeBeans, + "stream"); + assertNotNull(streamRB); + assertFalse(streamRB.getKeyYangName().isPresent()); + assertFalse(streamRB.getKeyJavaName().isPresent()); + Map 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 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 methods, + String name) { + for (Method ms : methods) { + if (name.equals(ms.getName())) { + return ms; + } + } + throw new IllegalArgumentException("Method with given name not found"); + } +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ModuleMXBeanEntryTemplatesTest.java b/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ModuleMXBeanEntryTemplatesTest.java new file mode 100644 index 0000000000..3c47931896 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ModuleMXBeanEntryTemplatesTest.java @@ -0,0 +1,67 @@ +/* + * 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 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; + } + +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/RuntimeRegistratorFtlFileTest.java b/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/RuntimeRegistratorFtlFileTest.java new file mode 100644 index 0000000000..b12ee5023f --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/RuntimeRegistratorFtlFileTest.java @@ -0,0 +1,83 @@ +/* + * 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 + . emptyList()); + Map createdFtls = RuntimeRegistratorFtlTemplate + .create(rootRB); + assertThat(createdFtls.size(), is(2)); + String rootRegistratorName = RuntimeRegistratorFtlTemplate + .getJavaNameOfRuntimeRegistration(rootRB.getJavaNamePrefix()); + FtlTemplate rootFtlFile = createdFtls.get(rootRegistratorName); + assertNotNull(rootFtlFile); + + Map serializedFtls = ftlFilePersister + .serializeFtls(createdFtls.values()); + assertThat(serializedFtls.size(), is(2)); + } + + @Test + public void testHierarchy2() { + RuntimeBeanEntry grandChildRB = prepareChildRB( + Collections. emptyList(), "grand"); + RuntimeBeanEntry childRB = prepareChildRB(Arrays.asList(grandChildRB), + ""); + RuntimeBeanEntry rootRB = prepareRootRB(Arrays.asList(childRB)); + + Map createdFtls = RuntimeRegistratorFtlTemplate + .create(rootRB); + Map 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 createdFtls, + RuntimeBeanEntry rb, Map serializedFtls) { + RuntimeRegistratorFtlTemplate rbFtlFile = (RuntimeRegistratorFtlTemplate) createdFtls + .get(RuntimeRegistratorFtlTemplate.getJavaNameOfRuntimeRegistration(rb.getJavaNamePrefix())); + assertNotNull(rbFtlFile); + String unformatted = serializedFtls.get(rbFtlFile); + assertNotNull(unformatted); + return FormattingUtil.cleanUpEmptyLinesAndIndent(unformatted); + } + +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/FtlFilePersisterTest.java b/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/FtlFilePersisterTest.java new file mode 100644 index 0000000000..2582a602b3 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/FtlFilePersisterTest.java @@ -0,0 +1,72 @@ +/* + * 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 extendedInterfaces = Arrays.asList("List", "Set"); + List methods = new ArrayList<>(); + methods.add(new MethodDeclaration("String", "executeOperation", + Collections. emptyList())); + + List mods = Lists.newArrayList(); + List 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 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); + } + +} diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/util/FormattingUtil.java b/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/util/FormattingUtil.java new file mode 100644 index 0000000000..e62cb7c002 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/util/FormattingUtil.java @@ -0,0 +1,32 @@ +/* + * 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(); + } +} diff --git a/opendaylight/config/yang-jmx-generator/pom.xml b/opendaylight/config/yang-jmx-generator/pom.xml new file mode 100644 index 0000000000..e89b61e6c7 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/pom.xml @@ -0,0 +1,109 @@ + + + 4.0.0 + + config-subsystem + org.opendaylight + 0.2.1-SNAPSHOT + + + yang-jmx-generator + ${project.artifactId} + bundle + + + + org.slf4j + slf4j-api + + + org.opendaylight.yangtools + binding-generator-util + ${opendaylight.binding.version} + + + org.opendaylight.yangtools + binding-generator-spi + ${opendaylight.binding.version} + + + com.google.guava + guava + + + + org.opendaylight.bgpcep + mockito-configuration + 0.2.0-SNAPSHOT + test + + + org.opendaylight.yangtools + yang-parser-impl + ${opendaylight.yang.version} + test + + + ${project.groupId} + config-api + 0.2.1-SNAPSHOT + test + + + org.opendaylight.yangtools + binding-generator-impl + ${opendaylight.binding.version} + test + + + org.apache.commons + commons-lang3 + test + + + + + + + org.apache.felix + maven-bundle-plugin + + + + org.opendaylight.controller.config.yangjmxgenerator.plugin.util, + + + org.slf4j, + com.google.common.base, + com.google.common.collect, + javax.management.*, + + + org.opendaylight.controller.config.yangjmxgenerator, + org.opendaylight.controller.config.yangjmxgenerator.attribute, + + 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, + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + diff --git a/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/AbstractEntry.java b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/AbstractEntry.java new file mode 100644 index 0000000000..0b27218ff3 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/AbstractEntry.java @@ -0,0 +1,38 @@ +/* + * 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; + } + +} diff --git a/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/ConfigConstants.java b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/ConfigConstants.java new file mode 100644 index 0000000000..ae06400195 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/ConfigConstants.java @@ -0,0 +1,65 @@ +/* + * 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); + } + +} diff --git a/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/ModuleMXBeanEntry.java b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/ModuleMXBeanEntry.java new file mode 100644 index 0000000000..590081072b --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/ModuleMXBeanEntry.java @@ -0,0 +1,675 @@ +/* + * 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: + *

+ *

+ * + *
+ *  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;
+ *                  }
+ *              }
+ *          }
+ *      }
+ * }
+ * 
+ * + *
+ *

+ */ +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 yangToAttributes; + + private final String nullableDescription, packageName, javaNamePrefix, + namespace; + + private final Map providedServices; + + private Collection runtimeBeans; + + public ModuleMXBeanEntry(IdentitySchemaNode id, + Map yangToAttributes, String packageName, + Map providedServices2, String javaNamePrefix, + String namespace, Collection 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 getProvidedServices() { + return providedServices; + } + + public void setRuntimeBeans(Collection newRuntimeBeans) { + runtimeBeans = newRuntimeBeans; + } + + public Collection 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 create( + Module currentModule, + Map qNamesToSIEs, + SchemaContext schemaContext, + TypeProviderWrapper typeProviderWrapper, String packageName) { + Map 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 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 result = new HashMap<>(); + // each module name should have an augmentation defined + Map unaugmentedModuleIdentities = new HashMap<>( + moduleIdentities); + for (AugmentationSchema augmentation : currentModule.getAugmentations()) { + Set 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 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 yangToAttributes = null; + // runtime-data + Collection 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 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 entry : result.entrySet()) { + ModuleMXBeanEntry module = entry.getValue(); + if (module.getAttributes() == null) { + module.setYangToAttributes(Collections + . emptyMap()); + } else if (module.getRuntimeBeans() == null) { + module.setRuntimeBeans(Collections + . 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 uniqueGeneratedClassesNames, + DataSchemaNode when, Collection 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 uniqueGeneratedClassNames, QName parentQName, + Map yangToAttributes) { + for (Entry 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 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 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 fillConfiguration( + ChoiceCaseNode choiceCaseNode, Module currentModule, + TypeProviderWrapper typeProviderWrapper, + Map qNamesToSIEs, + SchemaContext schemaContext) { + Map 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 findProvidedServices( + IdentitySchemaNode moduleIdentity, Module currentModule, + Map qNamesToSIEs, + SchemaContext schemaContext) { + Map 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 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 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 getAttributes() { + return yangToAttributes; + } + + private void setYangToAttributes(Map newAttributes) { + this.yangToAttributes = newAttributes; + + } + + public String getNullableDescription() { + return nullableDescription; + } + + @Override + public String toString() { + return "ModuleMXBeanEntry{" + "globallyUniqueName='" + + globallyUniqueName + '\'' + ", packageName='" + packageName + + '\'' + '}'; + } +} diff --git a/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/PackageTranslator.java b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/PackageTranslator.java new file mode 100644 index 0000000000..a6a1ac7dc3 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/PackageTranslator.java @@ -0,0 +1,76 @@ +/* + * 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, where key is namespace prefix and value is package that replaces + * matched prefix. + */ +public class PackageTranslator { + private final Map namespacePrefixToPackageMap; + + public PackageTranslator(Map 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 longestMatch = null; + int longestMatchLength = 0; + String namespace = module.getNamespace().toString(); + for (Entry 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; + } +} diff --git a/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/RuntimeBeanEntry.java b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/RuntimeBeanEntry.java new file mode 100644 index 0000000000..6d1eca1193 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/RuntimeBeanEntry.java @@ -0,0 +1,500 @@ +/* + * 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 keyYangName, keyJavaName; + private final Map attributeMap; + private final List children; + private final Set rpcs; + + @VisibleForTesting + public RuntimeBeanEntry(String packageName, + DataSchemaNode nodeForReporting, String yangName, + String javaNamePrefix, boolean isRoot, + Optional keyYangName, List attributes, + List children, Set 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 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 extractClassNameToRuntimeBeanMap( + String packageName, ChoiceCaseNode container, + String moduleYangName, TypeProviderWrapper typeProviderWrapper, + String javaNamePrefix, Module currentModule) { + + Map> identitiesToRpcs = getIdentitiesToRpcs(currentModule); + + AttributesRpcsAndRuntimeBeans attributesRpcsAndRuntimeBeans = extractSubtree( + packageName, container, typeProviderWrapper, currentModule, + identitiesToRpcs); + Map result = new HashMap<>(); + + List attributes; + Set 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 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> getIdentitiesToRpcs( + Module currentModule) { + // currently only looks for local identities (found in currentModule) + Map> result = new HashMap<>(); + for (IdentitySchemaNode identity : currentModule.getIdentities()) { + // add all + result.put(identity.getQName(), new HashSet()); + } + + 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 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> identitiesToRpcs) { + + List attributes = Lists.newArrayList(); + // List javaAttributes = new ArrayList<>(); + // List toAttributes = new ArrayList<>(); + List 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 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 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 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> identitiesToRpcs) { + + // supported are numeric types, strings, enums + // get all attributes + AttributesRpcsAndRuntimeBeans attributesRpcsAndRuntimeBeans = extractSubtree( + packageName, listSchemaNode, typeProviderWrapper, + currentModule, identitiesToRpcs); + + Optional 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 attributes, String javaNamePrefix, + List children, Set rpcs) { + return new RuntimeBeanEntry(packageName, nodeForReporting, + attributeYangName, javaNamePrefix, true, + Optional. absent(), attributes, children, rpcs); + } + + public boolean isRoot() { + return isRoot; + } + + public Optional getKeyYangName() { + return keyYangName; + } + + public Optional getKeyJavaName() { + return keyJavaName; + } + + public Collection getAttributes() { + return attributeMap.values(); + } + + public Map getYangPropertiesToTypesMap() { + return attributeMap; + } + + public String getYangName() { + return yangName; + } + + public String getPackageName() { + return packageName; + } + + public String getJavaNamePrefix() { + return javaNamePrefix; + } + + public List getChildren() { + return children; + } + + public Set getRpcs() { + return rpcs; + } + + private static class AttributesRpcsAndRuntimeBeans { + private final List runtimeBeanEntries; + private final List attributes; + private final Set rpcs; + + public AttributesRpcsAndRuntimeBeans( + List runtimeBeanEntries, + List attributes, Set rpcs) { + this.runtimeBeanEntries = runtimeBeanEntries; + this.attributes = attributes; + this.rpcs = rpcs; + } + + private List getAttributes() { + return attributes; + } + + public List getRuntimeBeanEntries() { + return runtimeBeanEntries; + } + + public boolean isEmpty() { + return attributes.isEmpty() && rpcs.isEmpty(); + } + + private Set getRpcs() { + return rpcs; + } + } + + public static class Rpc { + private final String name; + private final List parameters; + private final String returnType; + private final String yangName; + + Rpc(String returnType, String name, String yangName, + List 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 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 + '}'; + } +} diff --git a/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/ServiceInterfaceEntry.java b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/ServiceInterfaceEntry.java new file mode 100644 index 0000000000..71cd0900eb --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/ServiceInterfaceEntry.java @@ -0,0 +1,237 @@ +/* + * 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: + *

+ *

+ * + *
+ *  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";
+ *  }
+ * 
+ * + *
+ *

+ */ +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 maybeBaseCache; + private final String exportedOsgiClassName; + private final QName qName; + private final String nullableDescription, packageName, typeName; + + private ServiceInterfaceEntry(IdentitySchemaNode id, String packageName) { + this(Optional. absent(), id, packageName); + } + + private ServiceInterfaceEntry(Optional base, + IdentitySchemaNode id, String packageName) { + checkNotNull(base); + this.maybeBaseCache = base; + List unknownSchemaNodes = id.getUnknownSchemaNodes(); + List 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 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 create(Module module, + String packageName) { + logger.debug("Generating ServiceInterfaces from {} to package {}", + module.getNamespace(), packageName); + + Map identitiesToSIs = new HashMap<>(); + Set 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 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 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 + '\'' + '}'; + } +} diff --git a/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/TypeProviderWrapper.java b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/TypeProviderWrapper.java new file mode 100644 index 0000000000..7490ad6084 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/TypeProviderWrapper.java @@ -0,0 +1,53 @@ +/* + * 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; + } + +} diff --git a/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/attribute/AbstractAttribute.java b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/attribute/AbstractAttribute.java new file mode 100644 index 0000000000..5ffc192137 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/attribute/AbstractAttribute.java @@ -0,0 +1,70 @@ +/* + * 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; + } +} diff --git a/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/attribute/AttributeIfc.java b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/attribute/AttributeIfc.java new file mode 100644 index 0000000000..23985b262e --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/attribute/AttributeIfc.java @@ -0,0 +1,28 @@ +/* + * 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(); +} diff --git a/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/attribute/DependencyAttribute.java b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/attribute/DependencyAttribute.java new file mode 100644 index 0000000000..b6d60e4041 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/attribute/DependencyAttribute.java @@ -0,0 +1,144 @@ +/* + * 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; + } + } + +} diff --git a/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/attribute/JavaAttribute.java b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/attribute/JavaAttribute.java new file mode 100644 index 0000000000..8f516ef181 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/attribute/JavaAttribute.java @@ -0,0 +1,138 @@ +/* + * 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 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("[]"); + } + +} diff --git a/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/attribute/ListAttribute.java b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/attribute/ListAttribute.java new file mode 100644 index 0000000000..083b0b53e9 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/attribute/ListAttribute.java @@ -0,0 +1,113 @@ +/* + * 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); + } + } + +} diff --git a/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/attribute/SimpleTypeResolver.java b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/attribute/SimpleTypeResolver.java new file mode 100644 index 0000000000..349459a931 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/attribute/SimpleTypeResolver.java @@ -0,0 +1,67 @@ +/* + * 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> 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); + } + +} diff --git a/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/attribute/TOAttribute.java b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/attribute/TOAttribute.java new file mode 100644 index 0000000000..20029d06f9 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/attribute/TOAttribute.java @@ -0,0 +1,243 @@ +/* + * 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 yangNameToAttributeMap; + private final Map attributeNameMap; + + private static final Set> 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 TOAttribute create( + T containerSchemaNode, TypeProviderWrapper typeProviderWrapper) { + // Transfer Object: get the leaves + Map map = new HashMap<>(); + Map 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 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 isAllowedType( + DataSchemaNode dataSchemaNode) { + for (Class 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 transferObject, + Map attributeNameMap, String nullableDescription) { + super(attrNode); + yangNameToAttributeMap = transferObject; + this.attributeNameMap = attributeNameMap; + this.nullableDescription = nullableDescription; + nullableDefault = null; + } + + public Map getAttributeNameMap() { + return attributeNameMap; + } + + public Map getCapitalizedPropertiesToTypesMap() { + Map capitalizedPropertiesToTypesMap = Maps + .newHashMap(); + for (Entry entry : yangNameToAttributeMap + .entrySet()) { + + capitalizedPropertiesToTypesMap.put( + ModuleMXBeanEntry.convertToJavaName(entry.getKey(), true), + entry.getValue()); + } + return capitalizedPropertiesToTypesMap; + } + + public Map getJmxPropertiesToTypesMap() { + Map jmxPropertiesToTypesMap = Maps.newHashMap(); + for (Entry entry : yangNameToAttributeMap + .entrySet()) { + + jmxPropertiesToTypesMap.put( + ModuleMXBeanEntry.convertToJavaName(entry.getKey(), false), + entry.getValue()); + } + return jmxPropertiesToTypesMap; + } + + public Map 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 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, OpenType> { + private final String[] itemNames; + int i = 0; + + private FunctionImpl(String[] itemNames) { + this.itemNames = itemNames; + } + + @Override + public OpenType apply(Entry input) { + AttributeIfc innerType = input.getValue(); + itemNames[i++] = input.getKey(); + return innerType.getOpenType(); + } + } +} diff --git a/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/attribute/TypedAttribute.java b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/attribute/TypedAttribute.java new file mode 100644 index 0000000000..98a85d476f --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/attribute/TypedAttribute.java @@ -0,0 +1,16 @@ +/* + * 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(); + +} diff --git a/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/attribute/Util.java b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/attribute/Util.java new file mode 100644 index 0000000000..30c7919c1a --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/attribute/Util.java @@ -0,0 +1,29 @@ +/* + * 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); + } +} diff --git a/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/util/FullyQualifiedNameHelper.java b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/util/FullyQualifiedNameHelper.java new file mode 100644 index 0000000000..50b4485e01 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/util/FullyQualifiedNameHelper.java @@ -0,0 +1,17 @@ +/* + * 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; + } +} diff --git a/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/util/NameConflictException.java b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/util/NameConflictException.java new file mode 100644 index 0000000000..2ae70562cf --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/util/NameConflictException.java @@ -0,0 +1,41 @@ +/* + * 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; + } +} diff --git a/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/AbstractYangTest.java b/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/AbstractYangTest.java new file mode 100644 index 0000000000..1100b35437 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/AbstractYangTest.java @@ -0,0 +1,100 @@ +/* + * 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 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 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 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 getConfigApiYangInputStreams() { + return getStreams("/META-INF/yang/config.yang", + "/META-INF/yang/rpc-context.yang"); + } + + public Map mapIdentitiesByQNames(Module module) { + Map 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 getStreams(String... paths) { + List result = new ArrayList<>(); + for (String path : paths) { + InputStream is = AbstractYangTest.class.getResourceAsStream(path); + assertNotNull(path + " is null", is); + result.add(is); + } + return result; + } +} diff --git a/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/ModuleMXBeanEntryNameConflictTest.java b/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/ModuleMXBeanEntryNameConflictTest.java new file mode 100644 index 0000000000..deef08a292 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/ModuleMXBeanEntryNameConflictTest.java @@ -0,0 +1,136 @@ +/* + * 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 testedFilesToYangModules = new HashMap<>(); + Map testedYangModulesToExpectedConflictingName = new HashMap<>(); + + @Test + public void testNameConflicts() throws Exception { + prepareSamples(); + prepareExceptionAssertions(); + + for (Map.Entry 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(), 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 yangISs = new ArrayList<>(); + yangISs.addAll(getStreams("/ietf-inet-types.yang")); + + yangISs.add(new FileInputStream(testedModule)); + + yangISs.addAll(getConfigApiYangInputStreams()); + + YangParserImpl parser = new YangParserImpl(); + Set 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; + } + +} diff --git a/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/ModuleMXBeanEntryTest.java b/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/ModuleMXBeanEntryTest.java new file mode 100644 index 0000000000..661dbd7da3 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/ModuleMXBeanEntryTest.java @@ -0,0 +1,304 @@ +/* + * 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 modulesToSIEs; + + protected Map loadThreadsJava() { + Map namesToMBEs = ModuleMXBeanEntry + .create(threadsJavaModule, modulesToSIEs, context, new TypeProviderWrapper(new TypeProviderImpl + (context)), PACKAGE_NAME); + assertNotNull(namesToMBEs); + Set 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 modulesToSIEs = ServiceInterfaceEntry + .create(threadsModule, PACKAGE_NAME); + modulesToSIEs.putAll(ServiceInterfaceEntry.create(jmxModule, + PACKAGE_NAME)); + Map namesToMBEs = ModuleMXBeanEntry + .create(jmxImplModule, modulesToSIEs, context, new TypeProviderWrapper(new TypeProviderImpl(context)) + , PACKAGE_NAME); + Map 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 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 namesToMBEs = loadThreadsJava(); + + { // check threadpool-dynamic + ModuleMXBeanEntry dynamicThreadPool = namesToMBEs + .get(THREADPOOL_DYNAMIC_MXB_NAME); + Map attributes = dynamicThreadPool + .getAttributes(); + // core-size, keepalive, maximum-size + // threadfactory + Set 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 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 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 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 propsExpected = new HashSet(2); + propsExpected.add("port"); + propsExpected.add("core-size"); + assertThat(toAttr.getYangPropertiesToTypesMap().keySet(), + is(propsExpected)); + propsExpected = new HashSet(2); + propsExpected.add("Port"); + propsExpected.add("CoreSize"); + assertThat( + toAttr.getCapitalizedPropertiesToTypesMap().keySet(), + is(propsExpected)); + propsExpected = new HashSet(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)); + + } + + } + } + +} diff --git a/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/PackageTranslatorTest.java b/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/PackageTranslatorTest.java new file mode 100644 index 0000000000..031acfa312 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/PackageTranslatorTest.java @@ -0,0 +1,37 @@ +/* + * 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 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)); + } + +} diff --git a/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/RuntimeBeanEntryTest.java b/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/RuntimeBeanEntryTest.java new file mode 100644 index 0000000000..597cf4ccc9 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/RuntimeBeanEntryTest.java @@ -0,0 +1,155 @@ +/* + * 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()).when(caseNode).getChildNodes(); + doReturn(new ArrayList()).when(caseNode) + .getUnknownSchemaNodes(); + Map 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 modulesToSIEs = ServiceInterfaceEntry + .create(threadsModule, "packages.sis"); + assertNotNull(modulesToSIEs); + + // create MXBeans map + Map 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 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 rpcs = new ArrayList( + 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 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 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 rpcs, String name) { + if (rpcs != null && !rpcs.isEmpty()) { + for (RuntimeBeanEntry.Rpc rpc : rpcs) { + if (rpc.getName().equals(name)) { + return rpc; + } + } + } + return null; + } + +} diff --git a/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/RuntimeRegistratorTest.java b/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/RuntimeRegistratorTest.java new file mode 100644 index 0000000000..6dd64441df --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/RuntimeRegistratorTest.java @@ -0,0 +1,67 @@ +/* + * 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 children) { + + DataSchemaNode dataSchemaNodeForReporting = mock(DataSchemaNode.class); + doReturn("DataSchemaNode").when(dataSchemaNodeForReporting).toString(); + return new RuntimeBeanEntry("pa.cka.ge", dataSchemaNodeForReporting, + "module-name", "ModuleName", true, Optional. absent(), + Collections. emptyList(), children, + Collections. emptySet()); + } + + protected RuntimeBeanEntry prepareChildRB(List 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. absent(), + Collections. emptyList(), children, + Collections. 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(); + + } + +} diff --git a/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/SchemaContextTest.java b/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/SchemaContextTest.java new file mode 100644 index 0000000000..24c025755b --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/SchemaContextTest.java @@ -0,0 +1,111 @@ +/* + * 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 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> expectedIdentitiesToBases = ImmutableMap + .of("eventbus", Optional.absent(), "threadfactory", Optional.absent(), "threadpool", + Optional.absent(), "scheduled-threadpool", Optional.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 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> expectedIdentitiesToBases) { + Map> copyOfExpectedNames = new HashMap<>( + expectedIdentitiesToBases); + for (IdentitySchemaNode id : module.getIdentities()) { + String localName = id.getQName().getLocalName(); + assertTrue("Unexpected identity " + localName, + copyOfExpectedNames.containsKey(localName)); + Optional 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> 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.absent()); + assertAllIdentitiesAreExpected(threadsJavaModule, + expectedIdentitiesToBases); + } + +} diff --git a/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/ServiceInterfaceEntryTest.java b/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/ServiceInterfaceEntryTest.java new file mode 100644 index 0000000000..0da61b46c3 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/ServiceInterfaceEntryTest.java @@ -0,0 +1,140 @@ +/* + * 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 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 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 namesToSIEntries = ServiceInterfaceEntry + .create(threadsModule, PACKAGE_NAME); + // expected eventbus, threadfactory, threadpool, + // scheduled-threadpool,thread-rpc-context + assertThat(namesToSIEntries.size(), is(expectedSIEFileNames.size())); + + Set withNoBaseType = Sets.newHashSet(EVENTBUS_QNAME, + THREADFACTORY_QNAME, THREADPOOL_QNAME, + SCHEDULED_EXECUTOR_SERVICE_QNAME); + HashSet withBaseType = new HashSet<>(); + for (Entry 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,}", " "); + } +} diff --git a/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/util/YangModelSearchUtils.java b/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/util/YangModelSearchUtils.java new file mode 100644 index 0000000000..11c7d04bd7 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/util/YangModelSearchUtils.java @@ -0,0 +1,32 @@ +/* + * 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 mapModulesByNames( + Collection modules) { + Map 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; + } +} diff --git a/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/unknownextension/UnknownExtensionTest.java b/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/unknownextension/UnknownExtensionTest.java new file mode 100644 index 0000000000..91e92f70e0 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/unknownextension/UnknownExtensionTest.java @@ -0,0 +1,59 @@ +/* + * 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 yangISs = Lists.newArrayList(getClass() + .getResourceAsStream("test-ifcWithUnknownExtension.yang")); + yangISs.addAll(getConfigApiYangInputStreams()); + try { + YangParserImpl parser = new YangParserImpl(); + Set 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(); + } + } + } + +} diff --git a/opendaylight/config/yang-jmx-generator/src/test/resources/config-bgp-listener-impl.yang b/opendaylight/config/yang-jmx-generator/src/test/resources/config-bgp-listener-impl.yang new file mode 100644 index 0000000000..fca83fd6be --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/test/resources/config-bgp-listener-impl.yang @@ -0,0 +1,43 @@ +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; + } + } + } +} diff --git a/opendaylight/config/yang-jmx-generator/src/test/resources/config-jmx-it-impl.yang b/opendaylight/config/yang-jmx-generator/src/test/resources/config-jmx-it-impl.yang new file mode 100644 index 0000000000..97078e033a --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/test/resources/config-jmx-it-impl.yang @@ -0,0 +1,217 @@ +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; + } + + } + } +} diff --git a/opendaylight/config/yang-jmx-generator/src/test/resources/config-jmx-it.yang b/opendaylight/config/yang-jmx-generator/src/test/resources/config-jmx-it.yang new file mode 100644 index 0000000000..e2b45b84aa --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/test/resources/config-jmx-it.yang @@ -0,0 +1,26 @@ +// 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"; + } +} diff --git a/opendaylight/config/yang-jmx-generator/src/test/resources/duplicates/config-test-duplicate-attribute-in-list.yang b/opendaylight/config/yang-jmx-generator/src/test/resources/duplicates/config-test-duplicate-attribute-in-list.yang new file mode 100644 index 0000000000..4901b41157 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/test/resources/duplicates/config-test-duplicate-attribute-in-list.yang @@ -0,0 +1,84 @@ +// 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; + } + + } + } +} diff --git a/opendaylight/config/yang-jmx-generator/src/test/resources/duplicates/config-test-duplicate-attribute-runtime-bean.yang b/opendaylight/config/yang-jmx-generator/src/test/resources/duplicates/config-test-duplicate-attribute-runtime-bean.yang new file mode 100644 index 0000000000..44e6dd0b2b --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/test/resources/duplicates/config-test-duplicate-attribute-runtime-bean.yang @@ -0,0 +1,63 @@ +// 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; + } + } + } + } +} diff --git a/opendaylight/config/yang-jmx-generator/src/test/resources/duplicates/config-test-duplicate-attribute.yang b/opendaylight/config/yang-jmx-generator/src/test/resources/duplicates/config-test-duplicate-attribute.yang new file mode 100644 index 0000000000..bf081028cc --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/test/resources/duplicates/config-test-duplicate-attribute.yang @@ -0,0 +1,81 @@ +// 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; + } + + } + } +} diff --git a/opendaylight/config/yang-jmx-generator/src/test/resources/duplicates/config-test-generated-attributes-name-conflict.yang b/opendaylight/config/yang-jmx-generator/src/test/resources/duplicates/config-test-generated-attributes-name-conflict.yang new file mode 100644 index 0000000000..b437b6416a --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/test/resources/duplicates/config-test-generated-attributes-name-conflict.yang @@ -0,0 +1,69 @@ +// 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; + } + } + } + } + } +} diff --git a/opendaylight/config/yang-jmx-generator/src/test/resources/duplicates/config-test-runtime-bean-list-name-conflict.yang b/opendaylight/config/yang-jmx-generator/src/test/resources/duplicates/config-test-runtime-bean-list-name-conflict.yang new file mode 100644 index 0000000000..c39692ddcf --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/test/resources/duplicates/config-test-runtime-bean-list-name-conflict.yang @@ -0,0 +1,57 @@ +// 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; + } + } + } + } +} diff --git a/opendaylight/config/yang-jmx-generator/src/test/resources/duplicates/config-test-runtime-bean-list-name-conflict2.yang b/opendaylight/config/yang-jmx-generator/src/test/resources/duplicates/config-test-runtime-bean-list-name-conflict2.yang new file mode 100644 index 0000000000..1acba7314e --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/test/resources/duplicates/config-test-runtime-bean-list-name-conflict2.yang @@ -0,0 +1,57 @@ +// 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; + } + } + } + } +} diff --git a/opendaylight/config/yang-jmx-generator/src/test/resources/duplicates/config-test-runtime-bean-name-conflict.yang b/opendaylight/config/yang-jmx-generator/src/test/resources/duplicates/config-test-runtime-bean-name-conflict.yang new file mode 100644 index 0000000000..b9f21402f9 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/test/resources/duplicates/config-test-runtime-bean-name-conflict.yang @@ -0,0 +1,59 @@ +// 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; + } + } + } + } + } +} diff --git a/opendaylight/config/yang-jmx-generator/src/test/resources/duplicates/config-test-runtime-bean-name-conflict2.yang b/opendaylight/config/yang-jmx-generator/src/test/resources/duplicates/config-test-runtime-bean-name-conflict2.yang new file mode 100644 index 0000000000..9f90480b50 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/test/resources/duplicates/config-test-runtime-bean-name-conflict2.yang @@ -0,0 +1,77 @@ +// 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; + } + + } + } + } +} diff --git a/opendaylight/config/yang-jmx-generator/src/test/resources/ietf-inet-types.yang b/opendaylight/config/yang-jmx-generator/src/test/resources/ietf-inet-types.yang new file mode 100644 index 0000000000..c3ada6c688 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/test/resources/ietf-inet-types.yang @@ -0,0 +1,418 @@ +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: + WG List: + + WG Chair: David Partain + + + WG Chair: David Kessens + + + Editor: Juergen Schoenwaelder + "; + + 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 . + + 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)"; + } + +} diff --git a/opendaylight/config/yang-jmx-generator/src/test/resources/org/opendaylight/controller/config/yangjmxgenerator/unknownextension/test-ifcWithUnknownExtension.yang b/opendaylight/config/yang-jmx-generator/src/test/resources/org/opendaylight/controller/config/yangjmxgenerator/unknownextension/test-ifcWithUnknownExtension.yang new file mode 100644 index 0000000000..0aae571b52 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/test/resources/org/opendaylight/controller/config/yangjmxgenerator/unknownextension/test-ifcWithUnknownExtension.yang @@ -0,0 +1,32 @@ +// 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"; + } + +} diff --git a/opendaylight/config/yang-jmx-generator/src/test/resources/test-config-files.yang b/opendaylight/config/yang-jmx-generator/src/test/resources/test-config-files.yang new file mode 100644 index 0000000000..1db0279506 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/test/resources/test-config-files.yang @@ -0,0 +1,71 @@ +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; + } + + } + } +} diff --git a/opendaylight/config/yang-jmx-generator/src/test/resources/test-config-files1.yang b/opendaylight/config/yang-jmx-generator/src/test/resources/test-config-files1.yang new file mode 100644 index 0000000000..0a152e7baa --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/test/resources/test-config-files1.yang @@ -0,0 +1,71 @@ +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; + } + + } + } +} diff --git a/opendaylight/config/yang-jmx-generator/src/test/resources/test-config-threads-java.yang b/opendaylight/config/yang-jmx-generator/src/test/resources/test-config-threads-java.yang new file mode 100644 index 0000000000..2972cec04f --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/test/resources/test-config-threads-java.yang @@ -0,0 +1,244 @@ +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; + } + } + } +} diff --git a/opendaylight/config/yang-jmx-generator/src/test/resources/test-config-threads.yang b/opendaylight/config/yang-jmx-generator/src/test/resources/test-config-threads.yang new file mode 100644 index 0000000000..ff4c426063 --- /dev/null +++ b/opendaylight/config/yang-jmx-generator/src/test/resources/test-config-threads.yang @@ -0,0 +1,63 @@ +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"; + } +} diff --git a/opendaylight/config/yang-store-api/pom.xml b/opendaylight/config/yang-store-api/pom.xml new file mode 100644 index 0000000000..a92baa9051 --- /dev/null +++ b/opendaylight/config/yang-store-api/pom.xml @@ -0,0 +1,51 @@ + + 4.0.0 + + config-subsystem + org.opendaylight + 0.2.1-SNAPSHOT + .. + + yang-store-api + ${project.artifactId} + bundle + + + + org.opendaylight + yang-jmx-generator + 0.2.1-SNAPSHOT + + + + + + + org.apache.felix + maven-bundle-plugin + + + + + + + + org.opendaylight.controller.config.yangjmxgenerator, + org.opendaylight.yangtools.yang.model.api + + + org.opendaylight.controller.config.yang.store.api + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + + diff --git a/opendaylight/config/yang-store-api/src/main/java/org/opendaylight/controller/config/yang/store/api/YangStoreException.java b/opendaylight/config/yang-store-api/src/main/java/org/opendaylight/controller/config/yang/store/api/YangStoreException.java new file mode 100644 index 0000000000..68d62f896a --- /dev/null +++ b/opendaylight/config/yang-store-api/src/main/java/org/opendaylight/controller/config/yang/store/api/YangStoreException.java @@ -0,0 +1,18 @@ +/* + * 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); + } + +} diff --git a/opendaylight/config/yang-store-api/src/main/java/org/opendaylight/controller/config/yang/store/api/YangStoreService.java b/opendaylight/config/yang-store-api/src/main/java/org/opendaylight/controller/config/yang/store/api/YangStoreService.java new file mode 100644 index 0000000000..15619a88cc --- /dev/null +++ b/opendaylight/config/yang-store-api/src/main/java/org/opendaylight/controller/config/yang/store/api/YangStoreService.java @@ -0,0 +1,22 @@ +/* + * 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; + +} diff --git a/opendaylight/config/yang-store-api/src/main/java/org/opendaylight/controller/config/yang/store/api/YangStoreSnapshot.java b/opendaylight/config/yang-store-api/src/main/java/org/opendaylight/controller/config/yang/store/api/YangStoreSnapshot.java new file mode 100644 index 0000000000..40daf4018a --- /dev/null +++ b/opendaylight/config/yang-store-api/src/main/java/org/opendaylight/controller/config/yang/store/api/YangStoreSnapshot.java @@ -0,0 +1,26 @@ +/* + * 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> getModuleMXBeanEntryMap(); + + Map> getModuleMap(); + + int countModuleMXBeanEntries(); + + @Override + void close(); +} diff --git a/opendaylight/config/yang-store-impl/.gitignore b/opendaylight/config/yang-store-impl/.gitignore new file mode 100644 index 0000000000..fc1d35eb24 --- /dev/null +++ b/opendaylight/config/yang-store-impl/.gitignore @@ -0,0 +1,3 @@ +target +.classpath +.settings diff --git a/opendaylight/config/yang-store-impl/pom.xml b/opendaylight/config/yang-store-impl/pom.xml new file mode 100644 index 0000000000..ead52e03b8 --- /dev/null +++ b/opendaylight/config/yang-store-impl/pom.xml @@ -0,0 +1,138 @@ + + 4.0.0 + + config-subsystem + org.opendaylight + 0.2.1-SNAPSHOT + .. + + yang-store-impl + ${project.artifactId} + bundle + + + + ${project.groupId} + yang-store-api + 0.2.1-SNAPSHOT + + + org.osgi + org.osgi.core + + + org.slf4j + slf4j-api + + + ${project.groupId} + yang-jmx-generator + 0.2.1-SNAPSHOT + + + org.opendaylight.yangtools + binding-generator-impl + ${opendaylight.binding.version} + + + commons-io + commons-io + + + com.google.guava + guava + + + org.opendaylight.bgpcep + mockito-configuration + 0.2.0-SNAPSHOT + test + + + + + + + org.apache.felix + maven-bundle-plugin + + + org.opendaylight.controller.config.yang.store.impl.YangStoreActivator + + + org.opendaylight.controller.config.yang.store.impl, + + 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, + + + 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, + + + org.eclipse.xtext.xbase.lib.*, + org.apache.commons.lang.*, + + + + 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.*, + + 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, + + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + + diff --git a/opendaylight/config/yang-store-impl/src/main/java/org/opendaylight/controller/config/yang/store/impl/ExtenderYangTracker.java b/opendaylight/config/yang-store-impl/src/main/java/org/opendaylight/controller/config/yang/store/impl/ExtenderYangTracker.java new file mode 100644 index 0000000000..e3be734624 --- /dev/null +++ b/opendaylight/config/yang-store-impl/src/main/java/org/opendaylight/controller/config/yang/store/impl/ExtenderYangTracker.java @@ -0,0 +1,171 @@ +/* + * 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 implements + YangStoreService { + + private static final Logger logger = LoggerFactory + .getLogger(ExtenderYangTracker.class); + + private final Multimap 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 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 urls = bundlesToYangURLs.removeAll(bundle); + logger.debug( + "Removed following yang URLs {} because of removed bundle {}", + urls, bundle); + } + } + + @Override + public synchronized YangStoreSnapshot getYangStoreSnapshot() + throws YangStoreException { + Optional 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 fromUrlsToInputStreams() { + return Collections2.transform(bundlesToYangURLs.values(), + new Function() { + + @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 cachedUrls; + YangStoreSnapshot cachedYangStoreSnapshot; + + Optional getCachedYangStore( + Multimap bundlesToYangURLs) { + Set urls = setFromMultimapValues(bundlesToYangURLs); + if (cachedUrls != null && cachedUrls.equals(urls)) { + Preconditions.checkState(cachedYangStoreSnapshot != null); + return Optional.of(cachedYangStoreSnapshot); + } + return Optional.absent(); + } + + private static Set setFromMultimapValues( + Multimap bundlesToYangURLs) { + Set urls = Sets.newHashSet(bundlesToYangURLs.values()); + Preconditions.checkState(bundlesToYangURLs.size() == urls.size()); + return urls; + } + + void cacheYangStore(Multimap urls, + YangStoreSnapshot yangStoreSnapshot) { + this.cachedUrls = setFromMultimapValues(urls); + this.cachedYangStoreSnapshot = yangStoreSnapshot; + } + + } +} diff --git a/opendaylight/config/yang-store-impl/src/main/java/org/opendaylight/controller/config/yang/store/impl/MbeParser.java b/opendaylight/config/yang-store-impl/src/main/java/org/opendaylight/controller/config/yang/store/impl/MbeParser.java new file mode 100644 index 0000000000..fc895eb51d --- /dev/null +++ b/opendaylight/config/yang-store-impl/src/main/java/org/opendaylight/controller/config/yang/store/impl/MbeParser.java @@ -0,0 +1,137 @@ +/* + * 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 allInput) + throws YangStoreException { + YangParserImpl parser = new YangParserImpl(); + + List 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 allYangModules = parser + .parseYangModelsFromStreamsMapped(bufferedInputStreams); + + SchemaContext resolveSchemaContext = parser.resolveSchemaContext(Sets + .newHashSet(allYangModules.values())); + + // JMX generator + + Map namespaceToPackageMapping = Maps.newHashMap(); + PackageTranslator packageTranslator = new PackageTranslator( + namespaceToPackageMapping); + + Map qNamesToSIEs = new HashMap<>(); + + // create SIE structure qNamesToSIEs + for (Module module : resolveSchemaContext.getModules()) { + String packageName = packageTranslator.getPackageName(module); + Map namesToSIEntries = ServiceInterfaceEntry + .create(module, packageName); + + for (Entry 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> retVal = Maps.newHashMap(); + Map> modulesMap = new HashMap<>(); + + for (Entry 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 namesToMBEs = ModuleMXBeanEntry + .create(moduleEntry.getValue(), qNamesToSIEs, resolveSchemaContext, typeProviderWrapper, + packageName); + retVal.put(moduleEntry.getValue().getNamespace().toString(), + namesToMBEs); + } + + return new YangStoreSnapshotImpl(retVal, modulesMap); + } + + public Map parseYangFilesToString( + Collection allYangs) { + YangParserImpl parser = new YangParserImpl(); + + Map allYangModules = parser + .parseYangModelsFromStreamsMapped(Lists.newArrayList(allYangs)); + Map retVal = new HashMap<>(); + + for (Entry 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; + } + +} diff --git a/opendaylight/config/yang-store-impl/src/main/java/org/opendaylight/controller/config/yang/store/impl/YangStoreActivator.java b/opendaylight/config/yang-store-impl/src/main/java/org/opendaylight/controller/config/yang/store/impl/YangStoreActivator.java new file mode 100644 index 0000000000..2331fd15a8 --- /dev/null +++ b/opendaylight/config/yang-store-impl/src/main/java/org/opendaylight/controller/config/yang/store/impl/YangStoreActivator.java @@ -0,0 +1,53 @@ +/* + * 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 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 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); + } + } +} diff --git a/opendaylight/config/yang-store-impl/src/main/java/org/opendaylight/controller/config/yang/store/impl/YangStoreSnapshotImpl.java b/opendaylight/config/yang-store-impl/src/main/java/org/opendaylight/controller/config/yang/store/impl/YangStoreSnapshotImpl.java new file mode 100644 index 0000000000..d5169eac38 --- /dev/null +++ b/opendaylight/config/yang-store-impl/src/main/java/org/opendaylight/controller/config/yang/store/impl/YangStoreSnapshotImpl.java @@ -0,0 +1,63 @@ +/* + * 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> moduleMXBeanEntryMap; + + private final Map> moduleMap; + + public YangStoreSnapshotImpl( + Map> moduleMXBeanEntryMap, + Map> moduleMap) { + this.moduleMXBeanEntryMap = moduleMXBeanEntryMap; + this.moduleMap = moduleMap; + } + + @Override + public Map> getModuleMXBeanEntryMap() { + return moduleMXBeanEntryMap; + } + + @Override + public Map> getModuleMap() { + return moduleMap; + } + + @Override + public int countModuleMXBeanEntries() { + int i = 0; + for (Map value : moduleMXBeanEntryMap + .values()) { + i += value.keySet().size(); + } + return i; + } + + @Override + public void close() { + // TODO: reference counting + } + +} diff --git a/opendaylight/config/yang-store-impl/src/test/java/org/opendaylight/controller/config/yang/store/impl/ExtenderYangTrackerTest.java b/opendaylight/config/yang-store-impl/src/test/java/org/opendaylight/controller/config/yang/store/impl/ExtenderYangTrackerTest.java new file mode 100644 index 0000000000..e40d7348a0 --- /dev/null +++ b/opendaylight/config/yang-store-impl/src/test/java/org/opendaylight/controller/config/yang/store/impl/ExtenderYangTrackerTest.java @@ -0,0 +1,137 @@ +/* + * 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 urls = Lists.newArrayList(); + for (int i = 0; i < sizeOfUrls; i++) { + urls.add(new URL("http://127.0." + bundleCounter++ + "." + i)); + } + Enumeration 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 { + + private final List urls; + int currentPos = 0; + + public TestEnumeration(List 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; + } + + } +} diff --git a/opendaylight/config/yang-store-impl/src/test/java/org/opendaylight/controller/config/yang/store/impl/HardcodedYangStoreService.java b/opendaylight/config/yang-store-impl/src/test/java/org/opendaylight/controller/config/yang/store/impl/HardcodedYangStoreService.java new file mode 100644 index 0000000000..96833bd507 --- /dev/null +++ b/opendaylight/config/yang-store-impl/src/test/java/org/opendaylight/controller/config/yang/store/impl/HardcodedYangStoreService.java @@ -0,0 +1,51 @@ +/* + * 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 byteArrayInputStreams; + + public HardcodedYangStoreService( + Collection 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); + } +} diff --git a/opendaylight/config/yang-test/README.txt b/opendaylight/config/yang-test/README.txt new file mode 100644 index 0000000000..c26df02b4f --- /dev/null +++ b/opendaylight/config/yang-test/README.txt @@ -0,0 +1,9 @@ +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. diff --git a/opendaylight/config/yang-test/pom.xml b/opendaylight/config/yang-test/pom.xml new file mode 100644 index 0000000000..9ee414ebfe --- /dev/null +++ b/opendaylight/config/yang-test/pom.xml @@ -0,0 +1,41 @@ + + 4.0.0 + + org.opendaylight + config-subsystem + 0.2.1-SNAPSHOT + + + yang-test + + Artifact that contains only generated code from yang files. Suitable for testing. + + + + + ${project.groupId} + config-api + 0.2.1-SNAPSHOT + + + org.slf4j + slf4j-api + + + + ${project.artifactId} + + 3.0.4 + + + + + + org.opendaylight.yangtools + yang-maven-plugin + + + + diff --git a/opendaylight/config/yang-test/src/main/java/org/opendaylight/controller/config/yang/test/impl/DepTestImplModule.java b/opendaylight/config/yang-test/src/main/java/org/opendaylight/controller/config/yang/test/impl/DepTestImplModule.java new file mode 100644 index 0000000000..4b37aa1fe5 --- /dev/null +++ b/opendaylight/config/yang-test/src/main/java/org/opendaylight/controller/config/yang/test/impl/DepTestImplModule.java @@ -0,0 +1,46 @@ +/** + * 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 { + } + }; + } +} diff --git a/opendaylight/config/yang-test/src/main/java/org/opendaylight/controller/config/yang/test/impl/DepTestImplModuleFactory.java b/opendaylight/config/yang-test/src/main/java/org/opendaylight/controller/config/yang/test/impl/DepTestImplModuleFactory.java new file mode 100644 index 0000000000..b07cf40f82 --- /dev/null +++ b/opendaylight/config/yang-test/src/main/java/org/opendaylight/controller/config/yang/test/impl/DepTestImplModuleFactory.java @@ -0,0 +1,19 @@ +/** + * 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 { + +} diff --git a/opendaylight/config/yang-test/src/main/java/org/opendaylight/controller/config/yang/test/impl/NetconfTestImplModule.java b/opendaylight/config/yang-test/src/main/java/org/opendaylight/controller/config/yang/test/impl/NetconfTestImplModule.java new file mode 100644 index 0000000000..1cfbd0ec4a --- /dev/null +++ b/opendaylight/config/yang-test/src/main/java/org/opendaylight/controller/config/yang/test/impl/NetconfTestImplModule.java @@ -0,0 +1,46 @@ +/** + * 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 { + } + }; + } +} diff --git a/opendaylight/config/yang-test/src/main/java/org/opendaylight/controller/config/yang/test/impl/NetconfTestImplModuleFactory.java b/opendaylight/config/yang-test/src/main/java/org/opendaylight/controller/config/yang/test/impl/NetconfTestImplModuleFactory.java new file mode 100644 index 0000000000..e99c64dd3b --- /dev/null +++ b/opendaylight/config/yang-test/src/main/java/org/opendaylight/controller/config/yang/test/impl/NetconfTestImplModuleFactory.java @@ -0,0 +1,19 @@ +/** + * 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 { + +} diff --git a/opendaylight/config/yang-test/src/main/java/org/opendaylight/controller/config/yang/test/impl/TestImplModule.java b/opendaylight/config/yang-test/src/main/java/org/opendaylight/controller/config/yang/test/impl/TestImplModule.java new file mode 100644 index 0000000000..bc5f1bb67c --- /dev/null +++ b/opendaylight/config/yang-test/src/main/java/org/opendaylight/controller/config/yang/test/impl/TestImplModule.java @@ -0,0 +1,46 @@ +/** + * 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 { + } + }; + } +} diff --git a/opendaylight/config/yang-test/src/main/java/org/opendaylight/controller/config/yang/test/impl/TestImplModuleFactory.java b/opendaylight/config/yang-test/src/main/java/org/opendaylight/controller/config/yang/test/impl/TestImplModuleFactory.java new file mode 100644 index 0000000000..1e86c83655 --- /dev/null +++ b/opendaylight/config/yang-test/src/main/java/org/opendaylight/controller/config/yang/test/impl/TestImplModuleFactory.java @@ -0,0 +1,19 @@ +/** + * 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 { + +} diff --git a/opendaylight/config/yang-test/src/main/yang/config-test-impl.yang b/opendaylight/config/yang-test/src/main/yang/config-test-impl.yang new file mode 100644 index 0000000000..6245e6197a --- /dev/null +++ b/opendaylight/config/yang-test/src/main/yang/config-test-impl.yang @@ -0,0 +1,381 @@ +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; + } + } + } +} diff --git a/opendaylight/config/yang-test/src/main/yang/config-test.yang b/opendaylight/config/yang-test/src/main/yang/config-test.yang new file mode 100644 index 0000000000..2daf4055db --- /dev/null +++ b/opendaylight/config/yang-test/src/main/yang/config-test.yang @@ -0,0 +1,23 @@ +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"; + } +} diff --git a/opendaylight/distribution/opendaylight/pom.xml b/opendaylight/distribution/opendaylight/pom.xml index ccdb26845b..eac5a705f9 100644 --- a/opendaylight/distribution/opendaylight/pom.xml +++ b/opendaylight/distribution/opendaylight/pom.xml @@ -28,6 +28,7 @@ ../../md-sal + ../../config