Configuration system employs two phase commit to validate and push configuration changes to a running system. It allows changing simple attributes, complex transfer objects, and also provides dependency injection between configured modules.
Module config-api contains base yang model config.yang, defining language extensions and other elements required by all configuration models. Other than that it contains both apis implemented by config-manager and spis to be implemeted by configuration providers. Since the configuration system is internally driven by JMX, package org.opendaylight.controller.config.api.jmx contains all M(X)Bean interfaces exposed by config-manager.
Module config-manager is implementation of config-api.
Module config-util contains clients (both JMX and jolokia, which is http based bridge to JMX) of configuration system.
Module yang-jmx-generator parses yang models and creates java representation of configuration models and service interfaces.
Module yang-jmx-generator-plugin is connector to yangtools yang-maven-plugin that generates skeletons of java files needed by configuration providers.
Module yang-test contains example yang file, from which code is being generated.
Module yang-jmx-generator-it tests yang-test.
Module yang-store-api contains api for a registry of all yang models parsed by yang-jmx-generator.
Module yang-store-impl uses OSGi extender pattern to read META-INF/yang/*.yang from all bundles and provides snapshot view of currently available configuration models.
Change-Id: Icf3201f9754e4ca28ebce3411d2a667dcd7e75c8
Signed-off-by: Tomas Olvecky <tolvecky@cisco.com>
<enforcer.version>1.3.1</enforcer.version>
<bundle.plugin.version>2.3.7</bundle.plugin.version>
<junit.version>4.8.1</junit.version>
+ <bgpcep.version>0.2.0-SNAPSHOT</bgpcep.version>
</properties>
<dependencyManagement>
<artifactId>javassist</artifactId>
<version>${javassist.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.opendaylight.bgpcep</groupId>
+ <artifactId>concepts</artifactId>
+ <version>${bgpcep.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.bgpcep</groupId>
+ <artifactId>util</artifactId>
+ <version>${bgpcep.version}</version>
+ </dependency>
</dependencies>
<properties>
<build.suffix>${project.version}</build.suffix>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
- <version>2.3</version>
+ <version>2.4</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
+ <version>1.7</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ <version>${commons.lang.version}</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
--- /dev/null
+target
+.classpath
+.settings
--- /dev/null
+<?xml version="1.0"?>
+<project
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+ xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <artifactId>config-subsystem</artifactId>
+ <groupId>org.opendaylight</groupId>
+ <version>0.2.1-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>config-api</artifactId>
+ <name>${project.artifactId}</name>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.bgpcep</groupId>
+ <artifactId>concepts</artifactId>
+ <version>0.2.0-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Import-Package>
+ javax.management,
+ org.opendaylight.protocol.concepts
+ </Import-Package>
+ <Export-Package>
+ org.opendaylight.controller.config.api,
+ org.opendaylight.controller.config.api.annotations,
+ org.opendaylight.controller.config.spi,
+ org.opendaylight.controller.config.api.jmx,
+ org.opendaylight.controller.config.api.jmx.constants,
+ org.opendaylight.controller.config.api.runtime,
+ </Export-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.api;
+
+import java.util.List;
+import java.util.Set;
+
+import javax.management.ObjectName;
+
+import org.opendaylight.controller.config.api.jmx.CommitStatus;
+import org.opendaylight.controller.config.api.jmx.ConfigTransactionControllerMXBean;
+import org.opendaylight.controller.config.api.jmx.constants.ConfigRegistryConstants;
+
+/**
+ * Provides functionality for working with configuration registry - mainly
+ * creating and committing config transactions.
+ */
+public interface ConfigRegistry extends LookupRegistry {
+
+ /**
+ * Only well-known ObjectName in configuration system, under which
+ * ConfigRegisry is registered.
+ */
+ public static final ObjectName OBJECT_NAME = ConfigRegistryConstants.OBJECT_NAME;
+
+ /**
+ * Opens new configuration transaction.
+ *
+ * @return {@link ObjectName} of {@link ConfigTransactionControllerMXBean}
+ */
+ ObjectName beginConfig();
+
+ /**
+ * Verifies and commits transaction.
+ *
+ * @param transactionControllerON
+ * {@link ObjectName} of
+ * {@link ConfigTransactionControllerMXBean} that was received in
+ * {@link #beginConfig()} method call.
+ * @return CommitStatus
+ * @throws ValidationException
+ * if validation fails
+ * @throws ConflictingVersionException
+ * if configuration state was changed
+ */
+ CommitStatus commitConfig(ObjectName transactionControllerON)
+ throws ConflictingVersionException, ValidationException;
+
+ /**
+ * @return list of open configuration transactions.
+ */
+ List<ObjectName> getOpenConfigs();
+
+ /**
+ * Will return true unless there was a transaction that succeeded during
+ * validation but failed in second phase of commit. In this case the server
+ * is unstable and its state is undefined.
+ */
+ boolean isHealthy();
+
+ Set<String> getAvailableModuleNames();
+
+ /**
+ * Find all runtime beans
+ *
+ * @return objectNames
+ */
+ Set<ObjectName> lookupRuntimeBeans();
+
+ /**
+ * Find all runtime of specified module
+ *
+ * @param moduleName
+ * of bean
+ * @param instanceName
+ * of bean
+ * @return objectNames
+ */
+ Set<ObjectName> lookupRuntimeBeans(String moduleName, String instanceName);
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.api;
+
+import java.util.Set;
+
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.InstanceNotFoundException;
+import javax.management.ObjectName;
+
+/**
+ * Represents functionality provided by configuration transaction.
+ */
+public interface ConfigTransactionController extends LookupRegistry {
+
+ /**
+ * Create new configuration bean.
+ *
+ * @param moduleName
+ * @param instanceName
+ * @return ObjectName of newly created module
+ * @throws InstanceAlreadyExistsException
+ * if given ifcName and instanceName is already registered
+ */
+ ObjectName createModule(String moduleName, String instanceName)
+ throws InstanceAlreadyExistsException;
+
+ /**
+ * Destroy existing module.
+ *
+ * @param objectName
+ * can be either read-only module name that can be obtained using
+ * {@link ConfigRegistry#lookupConfigBean(String, String)} or
+ * writable module name that must contain current transaction
+ * name.
+ * @throws InstanceNotFoundException
+ * if module is not found
+ * @throws IllegalArgumentException
+ * if object name contains wrong transaction name or domain
+ */
+ void destroyModule(ObjectName objectName) throws InstanceNotFoundException;
+
+ /**
+ * Destroy current transaction.
+ */
+ void abortConfig();
+
+ /**
+ * This method can be called multiple times, has no side effects.
+ *
+ * @throws ValidationException
+ * if validation fails
+ */
+ void validateConfig() throws ValidationException;
+
+ /**
+ *
+ * @return transactionName
+ */
+ String getTransactionName();
+
+ Set<String> getAvailableModuleNames();
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.api;
+
+/**
+ * Can be thrown during
+ * {@link ConfigRegistry#commitConfig(javax.management.ObjectName)} to indicate
+ * that the transaction cannot be committed due to the fact that another
+ * transaction was committed after creating this transaction. Clients can create
+ * new transaction and merge the changes.
+ */
+public class ConflictingVersionException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ public ConflictingVersionException() {
+ super();
+ }
+
+ public ConflictingVersionException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ConflictingVersionException(String message) {
+ super(message);
+ }
+
+ public ConflictingVersionException(Throwable cause) {
+ super(cause);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.api;
+
+import javax.management.ObjectName;
+
+import org.opendaylight.controller.config.api.annotations.AbstractServiceInterface;
+
+/**
+ * Each new {@link org.opendaylight.controller.config.spi.Module} can receive
+ * resolver from {@link org.opendaylight.controller.config.spi.ModuleFactory}
+ * for looking up dependencies during validation and second phase commit.
+ *
+ * @see org.opendaylight.controller.config.spi.Module
+ */
+public interface DependencyResolver {
+
+ /**
+ * To be used during validation phase to validate serice interface of
+ * dependent module.
+ *
+ * @param expectedServiceInterface
+ * MBean/MXBean interface which will back the proxy object.
+ * @param objectName
+ * ObjectName of dependent module without transaction name
+ * (platformON).
+ * @param jmxAttribute
+ * @throws {@link IllegalArgumentException} when module is not found
+ * @throws {@link IllegalStateException} if module does not export this
+ * service interface.
+ */
+ void validateDependency(
+ Class<? extends AbstractServiceInterface> expectedServiceInterface,
+ ObjectName objectName, JmxAttribute jmxAttribute);
+
+ @Deprecated
+ // TODO remove once all config code is generated
+ void validateDependency(
+ Class<? extends AbstractServiceInterface> expectedServiceInterface,
+ ObjectName objectName, String attributeNameForErrorReporting);
+
+ /**
+ * To be used during commit phase to wire actual dependencies.
+ *
+ * @return dependency instance using
+ * {@link org.opendaylight.controller.config.spi.Module#getInstance()}
+ * @throws {@link IllegalArgumentException} when module is not found
+ */
+ <T> T resolveInstance(Class<T> expectedType, ObjectName objectName,
+ JmxAttribute jmxAttribute);
+
+ @Deprecated
+ <T> T resolveInstance(Class<T> expectedType, ObjectName objectName);
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.api;
+
+import javax.management.DynamicMBean;
+
+import org.opendaylight.controller.config.spi.Module;
+
+/**
+ * Each {@link org.opendaylight.controller.config.spi.Module} that is committed
+ * will be wrapped into this interface.
+ */
+public interface DynamicMBeanWithInstance extends DynamicMBean {
+
+ /**
+ * Get original module that is wrapped with this instance.
+ */
+ Module getModule();
+
+ /**
+ * Gets 'live object' associated with current config object. Useful when
+ * reconfiguring {@link org.opendaylight.controller.config.spi.Module}
+ * instances.
+ */
+ AutoCloseable getInstance();
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.api;
+
+/**
+ * Wrapper around strings to make {@link JmxAttributeValidationException} type
+ * safe.
+ */
+public class JmxAttribute {
+ private final String attributeName;
+
+ public JmxAttribute(String attributeName) {
+ if (attributeName == null)
+ throw new NullPointerException("Parameter 'attributeName' is null");
+ this.attributeName = attributeName;
+ }
+
+ public String getAttributeName() {
+ return attributeName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ JmxAttribute that = (JmxAttribute) o;
+
+ if (attributeName != null ? !attributeName.equals(that.attributeName)
+ : that.attributeName != null)
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return attributeName != null ? attributeName.hashCode() : 0;
+ }
+
+ @Override
+ public String toString() {
+ return "JmxAttribute{'" + attributeName + "'}";
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.api;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Exception that can be thrown during validation phase. This allows for
+ * pointing user to a specific list of parameters that fail the validation. Note
+ * that {@link org.opendaylight.controller.config.spi.Module#validate()} can
+ * throw any runtime exception to trigger validation failure.
+ */
+public class JmxAttributeValidationException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+ private final List<JmxAttribute> attributeNames;
+
+ public JmxAttributeValidationException(JmxAttribute jmxAttribute) {
+ this(Arrays.asList(jmxAttribute));
+ }
+
+ public JmxAttributeValidationException(List<JmxAttribute> jmxAttribute) {
+ this.attributeNames = jmxAttribute;
+ }
+
+ public JmxAttributeValidationException(String message,
+ JmxAttribute jmxAttribute) {
+ this(message, Arrays.asList(jmxAttribute));
+ }
+
+ public JmxAttributeValidationException(String message,
+ List<JmxAttribute> jmxAttributes) {
+ super(message);
+ this.attributeNames = jmxAttributes;
+ }
+
+ public JmxAttributeValidationException(String message, Throwable cause,
+ JmxAttribute jmxAttribute) {
+ this(message, cause, Arrays.asList(jmxAttribute));
+ }
+
+ public JmxAttributeValidationException(String message, Throwable cause,
+ List<JmxAttribute> jmxAttributes) {
+ super(message, cause);
+ this.attributeNames = jmxAttributes;
+ }
+
+ public List<JmxAttribute> getAttributeNames() {
+ return attributeNames;
+ }
+
+ public static <T> T checkNotNull(T param, JmxAttribute jmxAttribute) {
+ String message = "is null";
+ return checkNotNull(param, message, jmxAttribute);
+ }
+
+ public static <T> T checkNotNull(T param, String message,
+ JmxAttribute jmxAttribute) {
+ if (param == null) {
+ throw new JmxAttributeValidationException(
+ jmxAttribute.getAttributeName() + " " + message,
+ jmxAttribute);
+ }
+ return param;
+ }
+
+ public static JmxAttributeValidationException wrap(Throwable throwable,
+ JmxAttribute jmxAttribute) throws JmxAttributeValidationException {
+ return wrap(throwable, throwable.getMessage(), jmxAttribute);
+ }
+
+ public static JmxAttributeValidationException wrap(Throwable throwable,
+ String message, JmxAttribute jmxAttribute) {
+
+ throw new JmxAttributeValidationException(
+ jmxAttribute.getAttributeName() + " " + message, throwable,
+ jmxAttribute);
+ }
+
+ public static void checkCondition(boolean condition, String message,
+ JmxAttribute jmxAttribute) throws JmxAttributeValidationException {
+ if (condition == false) {
+ throw new JmxAttributeValidationException(
+ jmxAttribute.getAttributeName() + " " + message,
+ jmxAttribute);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.api;
+
+import java.util.Set;
+
+import javax.management.InstanceNotFoundException;
+import javax.management.ObjectName;
+
+public interface LookupRegistry {
+
+ /**
+ * Find all modules. Same Module can be registered multiple times.
+ *
+ * @return objectNames
+ */
+ Set<ObjectName> lookupConfigBeans();
+
+ /**
+ * Find modules with given module name.
+ *
+ * @param moduleName
+ * @return objectNames
+ */
+ Set<ObjectName> lookupConfigBeans(String moduleName);
+
+ /**
+ * Find read modules.
+ *
+ * @param moduleName
+ * exact match for searched module name, can contain '*' to match
+ * all values.
+ * @param instanceName
+ * exact match for searched instance name, can contain '*' to
+ * match all values.
+ * @return objectNames
+ */
+ Set<ObjectName> lookupConfigBeans(String moduleName, String instanceName);
+
+ /**
+ * Find read module.
+ *
+ * @param moduleName
+ * exact match for searched module name, can contain '*' to match
+ * all values.
+ * @param instanceName
+ * exact match for searched instance name, can contain '*' to
+ * match all values.
+ * @return objectNames
+ * @throws InstanceNotFoundException
+ * if search did not find exactly one instance
+ */
+ ObjectName lookupConfigBean(String moduleName, String instanceName)
+ throws InstanceNotFoundException;
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.api;
+
+import org.opendaylight.protocol.concepts.Identifier;
+
+public class ModuleIdentifier implements Identifier {
+ private static final long serialVersionUID = 1L;
+ private final String factoryName, instanceName;
+
+ public ModuleIdentifier(String factoryName, String instanceName) {
+ if (factoryName == null)
+ throw new IllegalArgumentException(
+ "Parameter 'factoryName' is null");
+ if (instanceName == null)
+ throw new IllegalArgumentException(
+ "Parameter 'instanceName' is null");
+ this.factoryName = factoryName;
+ this.instanceName = instanceName;
+ }
+
+ public String getFactoryName() {
+ return factoryName;
+ }
+
+ public String getInstanceName() {
+ return instanceName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ ModuleIdentifier that = (ModuleIdentifier) o;
+
+ if (!factoryName.equals(that.factoryName))
+ return false;
+ if (!instanceName.equals(that.instanceName))
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = factoryName.hashCode();
+ result = 31 * result + instanceName.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "ModuleIdentifier{" + "factoryName='" + factoryName + '\''
+ + ", instanceName='" + instanceName + '\'' + '}';
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.api;
+
+import java.io.Closeable;
+
+import org.opendaylight.controller.config.api.runtime.RootRuntimeBeanRegistrator;
+import org.opendaylight.controller.config.spi.Module;
+
+/**
+ * Module implementing this interface will receive
+ * {@link RootRuntimeBeanRegistrator} before getInstance() is invoked.
+ */
+public interface RuntimeBeanRegistratorAwareModule extends Module {
+ /**
+ * Configuration framework will call this setter on all modules implementing
+ * this interface. It is responsibility of modules or rather their instances
+ * to close registrator in their {@link Closeable#close()} method. Same
+ * module will get the same registrator during reconfiguration.
+ *
+ * @param rootRuntimeBeanRegistrator
+ */
+ public void setRuntimeBeanRegistrator(
+ RootRuntimeBeanRegistrator rootRuntimeBeanRegistrator);
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.api;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * This exception is not intended to be used while implementing modules,
+ * itaggregates validation exceptions and sends them back to the user.
+ */
+public class ValidationException extends RuntimeException {
+ private static final long serialVersionUID = -6072893219820274247L;
+
+ private final Map<String, Map<String, ExceptionMessageWithStackTrace>> failedValidations;
+
+ public ValidationException(
+ Map<String /* module name */, Map<String /* instance name */, ExceptionMessageWithStackTrace>> failedValidations) {
+ super(failedValidations.toString());
+ this.failedValidations = Collections.unmodifiableMap(failedValidations);
+ }
+
+ public static ValidationException createFromCollectedValidationExceptions(
+ List<ValidationException> collectedExceptions) {
+ Map<String, Map<String, ExceptionMessageWithStackTrace>> failedValidations = new HashMap<>();
+ for (ValidationException ve : collectedExceptions) {
+ for (Entry<String, Map<String, ExceptionMessageWithStackTrace>> outerEntry : ve
+ .getFailedValidations().entrySet()) {
+ for (Entry<String, ExceptionMessageWithStackTrace> innerEntry : outerEntry
+ .getValue().entrySet()) {
+ String moduleName = outerEntry.getKey();
+ String instanceName = innerEntry.getKey();
+ ExceptionMessageWithStackTrace ex = innerEntry.getValue();
+ Map<String, ExceptionMessageWithStackTrace> instanceToExMap = failedValidations
+ .get(moduleName);
+ if (instanceToExMap == null) {
+ instanceToExMap = new HashMap<>();
+ failedValidations.put(moduleName, instanceToExMap);
+ }
+ if (instanceToExMap.containsKey(instanceName)) {
+ throw new IllegalArgumentException(
+ "Cannot merge with same module name "
+ + moduleName + " and instance name "
+ + instanceName);
+ }
+ instanceToExMap.put(instanceName, ex);
+ }
+ }
+ }
+ return new ValidationException(failedValidations);
+ }
+
+ public static ValidationException createForSingleException(
+ ModuleIdentifier moduleIdentifier, Exception e) {
+ Map<String, Map<String, ExceptionMessageWithStackTrace>> failedValidations = new HashMap<>();
+ Map<String, ExceptionMessageWithStackTrace> innerMap = new HashMap<>();
+
+ failedValidations.put(moduleIdentifier.getFactoryName(), innerMap);
+ innerMap.put(moduleIdentifier.getInstanceName(),
+ new ExceptionMessageWithStackTrace(e));
+ return new ValidationException(failedValidations);
+ }
+
+ public Map<String, Map<String, ExceptionMessageWithStackTrace>> getFailedValidations() {
+ return failedValidations;
+ }
+
+ public static class ExceptionMessageWithStackTrace {
+ private String message, stackTrace;
+
+ public ExceptionMessageWithStackTrace() {
+ }
+
+ public ExceptionMessageWithStackTrace(String message, String stackTrace) {
+ this.message = message;
+ this.stackTrace = stackTrace;
+ }
+
+ public ExceptionMessageWithStackTrace(Exception e) {
+ this(e.getMessage(), Arrays.toString(e.getStackTrace()));
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public String getTrace() {
+ return stackTrace;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public void setTrace(String stackTrace) {
+ this.stackTrace = stackTrace;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + ((message == null) ? 0 : message.hashCode());
+ result = prime * result
+ + ((stackTrace == null) ? 0 : stackTrace.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ ExceptionMessageWithStackTrace other = (ExceptionMessageWithStackTrace) obj;
+ if (message == null) {
+ if (other.message != null)
+ return false;
+ } else if (!message.equals(other.message))
+ return false;
+ if (stackTrace == null) {
+ if (other.stackTrace != null)
+ return false;
+ } else if (!stackTrace.equals(other.stackTrace))
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "ExceptionMessageWithStackTrace [message=" + message
+ + ", stackTrace=" + stackTrace + "]";
+ }
+
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.api.annotations;
+
+/**
+ * Marker interface on all Service Interface annotated classes, each Service
+ * Interface must extend this interface. Service Intefaces can form hierarchies,
+ * one SI can extend another one, in which case all annotations in hierarchy
+ * will be observed.
+ */
+public abstract interface AbstractServiceInterface {
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.api.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Exports attribute and module class descriptions. Description annotation can
+ * be applied to module directly or to its super class or MXBean interface.
+ */
+@Target({ ElementType.TYPE, ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Description {
+ /**
+ * Returns a human-readable description of the annotated attribute.
+ * Descriptions should be clear and concise, describing what the attribute
+ * affects.
+ *
+ * @return attribute description
+ */
+ String value();
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.api.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import javax.management.ObjectName;
+
+/**
+ * Indicates what service interface is expected to be obtained as a dependency
+ * of a module. This annotation must be present for each dependency setter in
+ * {@link org.opendaylight.controller.config.spi.Module} M(X)Bean interface.
+ * Together with name of dependent bean the {@link #value()} will be used to get
+ * {@link ObjectName} of dependency.
+ *
+ * <p>
+ * Example:<br>
+ *
+ * <code>
+ *
+ * @RequireInterface(value = ThreadPoolServiceInterface.class, optional =
+ * false)<br/> void setThreadPool(ObjectName on);
+ * </code>
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface RequireInterface {
+
+ /**
+ * Declares dependency on service interface.
+ */
+ Class<? extends AbstractServiceInterface> value();
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.api.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks an interface implemented by
+ * {@link org.opendaylight.controller.config.spi.Module} as a Service Interface.
+ * Each service interface is identified by globally unique and human readable
+ * name. By convention the name is all lower case without spaces.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface ServiceInterfaceAnnotation {
+
+ /**
+ * Specifies human readable name of this service. Each service name should
+ * be globally unique. Should not contain spaces.
+ */
+ String value();
+
+ /**
+ * Mandatory class which will be used as key for OSGi service registration
+ * once {@link org.opendaylight.controller.config.spi.Module#getInstance()}
+ * is called.
+ */
+ Class<?> osgiRegistrationType();
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.api.jmx;
+
+import java.beans.ConstructorProperties;
+import java.util.Collections;
+import java.util.List;
+
+import javax.annotation.concurrent.Immutable;
+import javax.management.ObjectName;
+
+@Immutable
+public class CommitStatus {
+ private final List<ObjectName> newInstances, reusedInstances,
+ recreatedInstances;
+
+ /**
+ *
+ * @param newInstances
+ * newly created instances
+ * @param reusedInstances
+ * reused instances
+ * @param recreatedInstances
+ * recreated instances
+ */
+ @ConstructorProperties({ "newInstances", "reusedInstances",
+ "recreatedInstances" })
+ public CommitStatus(List<ObjectName> newInstances,
+ List<ObjectName> reusedInstances,
+ List<ObjectName> recreatedInstances) {
+ this.newInstances = Collections.unmodifiableList(newInstances);
+ this.reusedInstances = Collections.unmodifiableList(reusedInstances);
+ this.recreatedInstances = Collections
+ .unmodifiableList(recreatedInstances);
+ }
+
+ /**
+ *
+ * @return list of objectNames representing newly created instances
+ */
+ public List<ObjectName> getNewInstances() {
+ return newInstances;
+ }
+
+ /**
+ *
+ * @return list of objectNames representing reused instances
+ */
+ public List<ObjectName> getReusedInstances() {
+ return reusedInstances;
+ }
+
+ /**
+ *
+ * @return list of objectNames representing recreated instances
+ */
+ public List<ObjectName> getRecreatedInstances() {
+ return recreatedInstances;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + ((newInstances == null) ? 0 : newInstances.hashCode());
+ result = prime
+ * result
+ + ((recreatedInstances == null) ? 0 : recreatedInstances
+ .hashCode());
+ result = prime * result
+ + ((reusedInstances == null) ? 0 : reusedInstances.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ CommitStatus other = (CommitStatus) obj;
+ if (newInstances == null) {
+ if (other.newInstances != null)
+ return false;
+ } else if (!newInstances.equals(other.newInstances))
+ return false;
+ if (recreatedInstances == null) {
+ if (other.recreatedInstances != null)
+ return false;
+ } else if (!recreatedInstances.equals(other.recreatedInstances))
+ return false;
+ if (reusedInstances == null) {
+ if (other.reusedInstances != null)
+ return false;
+ } else if (!reusedInstances.equals(other.reusedInstances))
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "CommitStatus [newInstances=" + newInstances
+ + ", reusedInstances=" + reusedInstances
+ + ", recreatedInstances=" + recreatedInstances + "]";
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.api.jmx;
+
+import org.opendaylight.controller.config.api.ConfigRegistry;
+
+/**
+ * Represents entry point of configuration management. Note: Reason for having
+ * methods in super interface is that JMX allows only one MXBean to be
+ * implemented and implementations can expose additional methods to be exported.<br>
+ * Implementation of {@link ConfigRegistry} is not required to implement this
+ * interface, but is required to export all methods of ConfigRegistry to JMX so
+ * that this interface can be used as a proxy.
+ */
+public interface ConfigRegistryMXBean extends ConfigRegistry {
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.api.jmx;
+
+import org.opendaylight.controller.config.api.ConfigTransactionController;
+
+/**
+ * Those are executed by Jolokia clients on configuration transaction
+ * represented by {@link ConfigMBeanServer} instance. Note: Reason for having
+ * methods in super interface is that JMX allows only one MXBean to be
+ * implemented and implementations can expose additional methods to be exported. <br>
+ * Implementation of {@link ConfigTransactionController} is not required to
+ * implement this interface, but is required to export all methods of
+ * ConfigTransactionController to JMX so that this interface can be used as a
+ * proxy.
+ */
+public interface ConfigTransactionControllerMXBean extends
+ ConfigTransactionController {
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.api.jmx;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.annotation.concurrent.ThreadSafe;
+import javax.management.ObjectName;
+
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.jmx.constants.ConfigRegistryConstants;
+
+/**
+ * Provides ObjectName creation. Each created ObjectName consists of domain that
+ * is defined as {@link #ON_DOMAIN} and at least one key-value pair. The only
+ * mandatory property is {@link #TYPE_KEY}. All transaction related mbeans have
+ * {@link #TRANSACTION_NAME_KEY} property set.
+ *
+ */
+@ThreadSafe
+public class ObjectNameUtil {
+
+ public static final String ON_DOMAIN = ConfigRegistryConstants.ON_DOMAIN;
+ public static final String MODULE_FACTORY_NAME_KEY = "moduleFactoryName";
+ public static final String INSTANCE_NAME_KEY = "instanceName";
+ public static final String TYPE_KEY = ConfigRegistryConstants.TYPE_KEY;
+ public static final String TYPE_CONFIG_REGISTRY = ConfigRegistryConstants.TYPE_CONFIG_REGISTRY;
+ public static final String TYPE_CONFIG_TRANSACTION = "ConfigTransaction";
+ public static final String TYPE_MODULE = "Module";
+ public static final String TYPE_RUNTIME_BEAN = "RuntimeBean";
+
+ public static final String TRANSACTION_NAME_KEY = "TransactionName";
+
+ public static ObjectName createON(String on) {
+ try {
+ return new ObjectName(on);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static ObjectName createONWithDomainAndType(String type) {
+ return ConfigRegistryConstants.createONWithDomainAndType(type);
+ }
+
+ public static ObjectName createON(String name, String key, String value) {
+ return ConfigRegistryConstants.createON(name, key, value);
+ }
+
+ public static ObjectName createON(String name, Map<String, String> attribs) {
+ Hashtable<String, String> table = new Hashtable<>(attribs);
+ try {
+ return new ObjectName(name, table);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ public static ObjectName createTransactionControllerON(
+ String transactionName) {
+ Map<String, String> onParams = new HashMap<>();
+ onParams.put(TRANSACTION_NAME_KEY, transactionName);
+ onParams.put(TYPE_KEY, TYPE_CONFIG_TRANSACTION);
+ return createON(ON_DOMAIN, onParams);
+ }
+
+ public static ObjectName createTransactionModuleON(String transactionName,
+ ModuleIdentifier moduleIdentifier) {
+ return createTransactionModuleON(transactionName,
+ moduleIdentifier.getFactoryName(),
+ moduleIdentifier.getInstanceName());
+ }
+
+ public static ObjectName createTransactionModuleON(String transactionName,
+ String moduleName, String instanceName) {
+ Map<String, String> onParams = createModuleON(moduleName, instanceName);
+ onParams.put(TRANSACTION_NAME_KEY, transactionName);
+ return createON(ON_DOMAIN, onParams);
+ }
+
+ public static ObjectName createTransactionModuleON(String transactionName,
+ ObjectName on) {
+ return createTransactionModuleON(transactionName, getFactoryName(on),
+ getInstanceName(on));
+ }
+
+ public static ObjectName createReadOnlyModuleON(
+ ModuleIdentifier moduleIdentifier) {
+ return createReadOnlyModuleON(moduleIdentifier.getFactoryName(),
+ moduleIdentifier.getInstanceName());
+ }
+
+ public static ObjectName createReadOnlyModuleON(String moduleName,
+ String instanceName) {
+ Map<String, String> onParams = createModuleON(moduleName, instanceName);
+ return createON(ON_DOMAIN, onParams);
+ }
+
+ private static Map<String, String> createModuleON(String moduleName,
+ String instanceName) {
+ Map<String, String> onParams = new HashMap<>();
+ onParams.put(TYPE_KEY, TYPE_MODULE);
+ onParams.put(MODULE_FACTORY_NAME_KEY, moduleName);
+ onParams.put(INSTANCE_NAME_KEY, instanceName);
+ return onParams;
+ }
+
+ public static String getFactoryName(ObjectName objectName) {
+ return objectName.getKeyProperty(MODULE_FACTORY_NAME_KEY);
+ }
+
+ public static String getInstanceName(ObjectName objectName) {
+ return objectName.getKeyProperty(INSTANCE_NAME_KEY);
+ }
+
+ public static String getTransactionName(ObjectName objectName) {
+ return objectName.getKeyProperty(TRANSACTION_NAME_KEY);
+ }
+
+ public static ObjectName withoutTransactionName(ObjectName on) {
+ if (getTransactionName(on) == null) {
+ throw new IllegalArgumentException(
+ "Expected ObjectName with transaction:" + on);
+ }
+ if (ON_DOMAIN.equals(on.getDomain()) == false) {
+ throw new IllegalArgumentException("Expected different domain: "
+ + on);
+ }
+ String moduleName = getFactoryName(on);
+ String instanceName = getInstanceName(on);
+ return createReadOnlyModuleON(moduleName, instanceName);
+ }
+
+ private static void assertDoesNotContain(
+ Map<String, String> additionalProperties, String key) {
+ if (additionalProperties.containsKey(key)) {
+ throw new IllegalArgumentException(
+ "Map 'additionalProperties' cannot overwrite attribute "
+ + key);
+ }
+ }
+
+ public static ObjectName createRuntimeBeanName(String moduleName,
+ String instanceName, Map<String, String> additionalProperties) {
+ // check that there is no overwriting of default attributes
+ assertDoesNotContain(additionalProperties, MODULE_FACTORY_NAME_KEY);
+ assertDoesNotContain(additionalProperties, INSTANCE_NAME_KEY);
+ assertDoesNotContain(additionalProperties, TYPE_KEY);
+ assertDoesNotContain(additionalProperties, TRANSACTION_NAME_KEY);
+ Map<String, String> map = new HashMap<>(additionalProperties);
+ map.put(MODULE_FACTORY_NAME_KEY, moduleName);
+ map.put(INSTANCE_NAME_KEY, instanceName);
+ map.put(TYPE_KEY, TYPE_RUNTIME_BEAN);
+ return createON(ON_DOMAIN, map);
+ }
+
+ private static Set<String> blacklist = new HashSet<>(Arrays.asList(
+ MODULE_FACTORY_NAME_KEY, INSTANCE_NAME_KEY, TYPE_KEY));
+
+ public static Map<String, String> getAdditionalPropertiesOfRuntimeBeanName(
+ ObjectName on) {
+ checkType(on, TYPE_RUNTIME_BEAN);
+ Map<String, String> allProperties = getAdditionalProperties(on);
+ Map<String, String> result = new HashMap<>();
+ for (Entry<String, String> entry : allProperties.entrySet()) {
+ if (blacklist.contains(entry.getKey()) == false) {
+ result.put(entry.getKey(), entry.getValue());
+ }
+ }
+ return result;
+ }
+
+ public static Map<String, String> getAdditionalProperties(ObjectName on) {
+ Hashtable<String, String> keyPropertyList = on.getKeyPropertyList();
+ Map<String, String> result = new HashMap<>();
+ for (Entry<String, String> entry : keyPropertyList.entrySet()) {
+ result.put(entry.getKey(), entry.getValue());
+ }
+ return result;
+ }
+
+ public static void checkDomain(ObjectName objectName) {
+ if (!ON_DOMAIN.equals(objectName.getDomain())) {
+ throw new IllegalArgumentException("Wrong domain " + objectName);
+ }
+
+ }
+
+ public static void checkType(ObjectName objectName, String type) {
+ if (!type.equals(objectName.getKeyProperty(TYPE_KEY))) {
+ throw new IllegalArgumentException("Wrong type, expected '" + type
+ + "', got " + objectName);
+ }
+ }
+
+ public static ObjectName createModulePattern(String moduleName,
+ String instanceName) {
+ if (moduleName == null)
+ moduleName = "*";
+ if (instanceName == null)
+ instanceName = "*";
+ // do not return object names containing transaction name
+ ObjectName namePattern = ObjectNameUtil
+ .createON(ObjectNameUtil.ON_DOMAIN + ":"
+ + ObjectNameUtil.TYPE_KEY + "="
+ + ObjectNameUtil.TYPE_MODULE + ","
+ + ObjectNameUtil.MODULE_FACTORY_NAME_KEY + "="
+ + moduleName + "," + ""
+ + ObjectNameUtil.INSTANCE_NAME_KEY + "=" + instanceName);
+ return namePattern;
+ }
+
+ public static ObjectName createModulePattern(String ifcName,
+ String instanceName, String transactionName) {
+ return ObjectNameUtil.createON(ObjectNameUtil.ON_DOMAIN
+ + ":type=Module," + ObjectNameUtil.MODULE_FACTORY_NAME_KEY
+ + "=" + ifcName + "," + ObjectNameUtil.INSTANCE_NAME_KEY + "="
+ + instanceName + "," + ObjectNameUtil.TRANSACTION_NAME_KEY
+ + "=" + transactionName);
+ }
+
+ public static ObjectName createRuntimeBeanPattern(String moduleName,
+ String instanceName) {
+ return ObjectNameUtil.createON(ObjectNameUtil.ON_DOMAIN + ":"
+ + ObjectNameUtil.TYPE_KEY + "="
+ + ObjectNameUtil.TYPE_RUNTIME_BEAN + ","
+ + ObjectNameUtil.MODULE_FACTORY_NAME_KEY + "=" + moduleName
+ + "," + ObjectNameUtil.INSTANCE_NAME_KEY + "=" + instanceName
+ + ",*");
+
+ }
+
+ public static ModuleIdentifier fromON(ObjectName objectName,
+ String expectedType) {
+ checkType(objectName, expectedType);
+ String factoryName = getFactoryName(objectName);
+ if (factoryName == null)
+ throw new IllegalArgumentException(
+ "ObjectName does not contain module name");
+ String instanceName = getInstanceName(objectName);
+ if (instanceName == null)
+ throw new IllegalArgumentException(
+ "ObjectName does not contain instance name");
+ return new ModuleIdentifier(factoryName, instanceName);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.api.jmx.constants;
+
+import javax.management.ObjectName;
+
+public class ConfigRegistryConstants {
+
+ public static final String TYPE_CONFIG_REGISTRY = "ConfigRegistry";
+
+ public static final String ON_DOMAIN = "org.opendaylight.controller";
+
+ public static final String TYPE_KEY = "type";
+
+ public static final ObjectName OBJECT_NAME = createONWithDomainAndType(TYPE_CONFIG_REGISTRY);
+
+ public static String GET_AVAILABLE_MODULE_NAMES_ATTRIBUT_NAME = "AvailableModuleNames";
+
+ public static ObjectName createONWithDomainAndType(String type) {
+ return createON(ON_DOMAIN, TYPE_KEY, type);
+ }
+
+ public static ObjectName createON(String name, String key, String value) {
+ try {
+ return new ObjectName(name, key, value);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.api.runtime;
+
+import javax.management.ObjectName;
+
+public interface HierarchicalRuntimeBeanRegistration extends AutoCloseable {
+
+ ObjectName getObjectName();
+
+ HierarchicalRuntimeBeanRegistration register(String key, String value,
+ RuntimeBean mxBean);
+
+ /**
+ * Unregister beans that were registered using this registrator and its
+ * child registrators. This method is not idempotent, it is not allowed to
+ * use this instance or child registrators after they are closed.
+ */
+ @Override
+ void close();
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.api.runtime;
+
+import java.io.Closeable;
+
+/**
+ * Entry point for runtime bean functionality. Allows for registering root
+ * runtime bean, returning an object that allows for hierarchical registrations.
+ */
+public interface RootRuntimeBeanRegistrator extends Closeable {
+
+ HierarchicalRuntimeBeanRegistration registerRoot(RuntimeBean mxBean);
+
+ /**
+ * Close all runtime beans. This method is idempotent. It is allowed to use
+ * this instance to register root or create new child registrators
+ * afterwards, but it is not allowed to use closed registrations.
+ */
+ @Override
+ void close();
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.api.runtime;
+
+/**
+ * Marker interface for all runtime beans
+ */
+
+public interface RuntimeBean {
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.spi;
+
+import javax.annotation.concurrent.NotThreadSafe;
+
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.annotations.RequireInterface;
+import org.opendaylight.protocol.concepts.NamedObject;
+
+/**
+ * Represents one service that is to be configured. These methods need to be
+ * implemented in addition to the usual attribute getters/setters. Dependencies
+ * should always be injected as ObjectName references to the corresponding
+ * ConfigBeans.
+ * <p>
+ * In order to guide dependency resolution, the setter method should be
+ * annotated with {@link RequireInterface}.
+ * </p>
+ * <p>
+ * Thread safety note: implementations of this interface are not required to be
+ * thread safe as thread safety is enforced by configuration manager.
+ * </p>
+ */
+@NotThreadSafe
+public interface Module extends NamedObject<ModuleIdentifier> {
+ /**
+ * This method will be called as first phase in two phase commit. Instance
+ * can check attributes, but is not allowed to do any kind of work that
+ * could leave any resources open. It is prohibited to call
+ * {@link #getInstance()} on dependent {@link Module} because it would
+ * destroy separation between validation and commit phase.
+ *
+ */
+ void validate();
+
+ /**
+ * Returns 'live' object that was configured using this object. It is
+ * allowed to call this method only after all ConfigBeans were validated. In
+ * this method new resources might be opened or old instance might be
+ * modified. Note that when obtaining dependent Module using
+ * {@link org.opendaylight.controller.config.api.DependencyResolver#validateDependency(Class, javax.management.ObjectName, String)}
+ * a proxy will be created that will disallow calling this method before
+ * second commit phase begins.
+ *
+ * @return closeable instance: After bundle update the factory might be able
+ * to copy old configuration into new one without being able to cast
+ * Module or the instance. Thus to clean up old instance, it will
+ * call close().
+ */
+ AutoCloseable getInstance();
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.spi;
+
+import javax.management.DynamicMBean;
+
+import org.opendaylight.controller.config.api.DependencyResolver;
+import org.opendaylight.controller.config.api.DynamicMBeanWithInstance;
+import org.opendaylight.controller.config.api.annotations.AbstractServiceInterface;
+
+/**
+ * Factory which creates {@link Module instances. An instance of this interface
+ * needs to be exported into the OSGi Service Registry. Such an instance
+ * provides metadata describing services which can be published from it.
+ *
+ * Each {@link Module } can optionally be instantiated with a
+ * {@link DynamicMBean} which represents the configuration of the currently
+ * running instance.
+ */
+public interface ModuleFactory {
+
+ /**
+ * Returns the human-friendly implementation name. This value needs to be
+ * unique within all implementations of all interfaces returned by
+ * getImplementedInterfaces().
+ *
+ * @return human-friendly implementation name
+ */
+ public String getImplementationName();
+
+ /**
+ * Create a new Module instance. The returned object is expected to use the
+ * dependencyResolver provided when resolving ObjectNames to actual Module
+ * instances.
+ *
+ * @param dependencyResolver
+ * This resolver will return actual config mbean based on its
+ * ObjectName.
+ * @return newly created module
+ *
+ */
+ public Module createModule(String instanceName,
+ DependencyResolver dependencyResolver);
+
+ /**
+ * Create a new Module instance. The returned object is expected to use the
+ * dependencyResolver provided when resolving ObjectNames to actual Module
+ * instances. A reference to an abstract view of the previous configuration
+ * is also provided in the form of a {@link DynamicMBean}. Implementations
+ * should use the MBeanInfo interface to understand the structure of the
+ * configuration information.
+ *
+ * Structural information impacts hot-swap operations in that in order to
+ * perform such a swap the newly loaded code needs to understand the
+ * previously-running instance configuration layout and how to map it onto
+ * itself.
+ *
+ * @param dependencyResolver
+ * This resolver will return actual config mbean based on its
+ * ObjectName.
+ * @param old
+ * existing module from platform MBeanServer that is being
+ * reconfigured. Implementations should inspect its attributes
+ * using {@link DynamicMBean#getAttribute(String)} and set those
+ * attributes on newly created module. If reconfiguration of live
+ * instances is supported, this live instance can be retreived
+ * using
+ * {@link org.opendaylight.controller.config.api.DynamicMBeanWithInstance#getInstance()}
+ * . It is possible that casting this old instance throws
+ * {@link ClassCastException} when OSGi bundle is being updated.
+ * In this case, implementation should revert to creating new
+ * instance.
+ * @return newly created module
+ * @throws Exception
+ * if it is not possible to recover configuration from old. This
+ * leaves server in a running state but no configuration
+ * transaction can be created.
+ */
+ public Module createModule(String instanceName,
+ DependencyResolver dependencyResolver, DynamicMBeanWithInstance old)
+ throws Exception;
+
+ boolean isModuleImplementingServiceInterface(
+ Class<? extends AbstractServiceInterface> serviceInterface);
+
+}
--- /dev/null
+// vi: set smarttab et sw=4 tabstop=4:
+module config {
+ yang-version 1;
+ namespace "urn:opendaylight:params:xml:ns:yang:controller:config";
+ prefix "config";
+
+
+ description
+ "This module contains the base YANG definitions for NS-OS
+ configuration subsystem. The system modeled revolves around two
+ major concepts: modules and services.";
+
+ revision "2013-04-05" {
+ description
+ "Reworked to give modules their own space.";
+ }
+
+ revision "2013-04-03" {
+ description
+ "Initial revision.";
+ }
+
+ extension java-class {
+ description
+ "YANG language extension carrying the fully-qualified name of
+ a Java class. Code generation tools use the provided reference
+ to tie a specific construct to its Java representation.";
+
+ argument "name";
+ }
+
+ extension required-identity {
+ description
+ "YANG language extension which indicates that a particular
+ leafref, which points to a identityref, should additionally
+ require the target node is actually set to a descendant to
+ of a particular identity.
+
+ This is a workaround to two YANG deficiencies:
+ 1) not being able to leafref instances of identityref
+ 2) not being able to refine an identityref
+
+ This extension takes one argument, name, which MUST be the name
+ of an identity. Furthermore, that identity MUST be based,
+ directly or indirectly, on the identity, which is referenced by
+ the leaf reference, which is annotated with this extension.";
+
+ argument "name";
+ }
+
+ extension inner-state-bean {
+ description
+ "YANG language extension which indicates that a particular
+ list located under module's state should be treated as a list
+ of child state beans instead of just an ordinary list attribute";
+ }
+
+ extension provided-service {
+ description
+ "YANG language extension which indicates that a particular
+ module provides certain service. This extension can be placed
+ on identities that are based on module-type. Zero or more services
+ can be provided.
+ This extension takes one argument - name - which MUST be the name
+ of an identity. Furthermore, this identity MUST be based on
+ service-type.";
+
+ argument "name";
+ }
+
+ extension java-name-prefix {
+ description
+ "YANG language extension carrying java simple class name prefix
+ that will be taken into account when generating java code from
+ identities that are based on module-type.";
+ argument "java-prefix";
+ }
+
+ identity module-type {
+ description
+ "Module identity base type. All module identities must be derived
+ from this type. A module type uniquely defines a single atomic
+ component, such as an application. Each such component is assumed
+ to have its unique, stable and versioned configuration structure.";
+ }
+
+ identity service-type {
+ description
+ "Service identity base type. All service identities must be
+ derived from this type. A service type uniquely defines a single
+ atomic API contract, such as a Java interface, a set of C
+ function declarations, or similar.
+
+ If the service type has a corresponding Java interface, the name
+ of that interface should be attached to the derived identity MUST
+ include a java-class keyword, whose name argument points to that
+ interface.";
+ }
+
+ typedef service-type-ref {
+ description
+ "Internal type of references to service type identity.";
+
+ type identityref {
+ base service-type;
+ }
+ }
+
+ grouping service-ref {
+ description
+ "Type of references to a particular service instance. This type
+ can be used when defining module configuration to refer to a
+ particular service instance. Containers using this grouping
+ should not define anything else. The run-time implementation
+ is expected to inject a reference to the service as the value
+ of the container.";
+
+ leaf type {
+ description
+ "Type of the service being referenced. Users of this grouping
+ should refine this leaf with required-identity pointing to
+ the actual service-type which is actually required.";
+
+ mandatory true;
+ type leafref {
+ path "/config:services/config:service/config:type";
+ }
+ }
+
+ leaf name {
+ mandatory true;
+ type leafref {
+ path "/config:services/config:service[config:type=current()/../type]/config:instance/config:name";
+ }
+ }
+ }
+
+ container modules {
+ description
+ "Top level container encapsulating configuration of all modules.";
+
+ list module {
+ key "name";
+ leaf name {
+ description "Unique module instance name";
+ type string;
+ mandatory true;
+ }
+
+ leaf type {
+ type identityref {
+ base module-type;
+ }
+ mandatory true;
+ }
+
+ choice configuration {
+ mandatory true;
+ config true;
+ }
+
+ choice state {
+ config false;
+ }
+ }
+ }
+
+
+ container services {
+ list service {
+ key "type";
+ leaf type {
+ type service-type-ref;
+ }
+ list instance {
+ key "name";
+ leaf name {
+ type string;
+ }
+
+ leaf provider {
+ mandatory true;
+ type leafref {
+ path "/modules/module/name";
+ }
+ }
+ }
+ }
+ }
+
+
+}
--- /dev/null
+module rpc-context {
+ yang-version 1;
+ namespace "urn:ietf:params:xml:ns:yang:rpc-context";
+ prefix "rpcx";
+
+ organization "TBD";
+
+ contact "TBD";
+
+ description "";
+
+ revision 2013-06-17 {
+ description "Initial mock";
+ }
+
+
+ grouping rpc-context-ref {
+ description "A reference to RPC context.";
+ leaf context-instance {
+ type instance-identifier;
+ description "Pointer to the context. ";
+ }
+ }
+
+ extension "rpc-context-instance" {
+ description
+ "Marks enclosing (parent) schema node as suitable RPC context.
+ The argument is identity which is used to identify RPC context
+ type.";
+ argument "context-type";
+ }
+}
--- /dev/null
+target
+.classpath
+.settings
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <artifactId>config-subsystem</artifactId>
+ <groupId>org.opendaylight</groupId>
+ <version>0.2.1-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+ <artifactId>config-manager</artifactId>
+ <name>${project.artifactId}</name>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <!-- compile dependencies -->
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>config-api</artifactId>
+ <version>0.2.1-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.googlecode.json-simple</groupId>
+ <artifactId>json-simple</artifactId>
+ <version>1.1</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ </dependency>
+
+ <!-- test dependencies -->
+ <dependency>
+ <groupId>org.opendaylight.bgpcep</groupId>
+ <artifactId>mockito-configuration</artifactId>
+ <version>0.2.0-SNAPSHOT</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>config-util</artifactId>
+ <version>0.2.1-SNAPSHOT</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>config-util</artifactId>
+ <version>0.2.1-SNAPSHOT</version>
+ <scope>test</scope>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Bundle-Activator>org.opendaylight.controller.config.manager.impl.osgi.ConfigManagerActivator
+ </Bundle-Activator>
+ <Private-Package>
+ org.opendaylight.controller.config.manager.*,
+ javax.annotation.*,
+ </Private-Package>
+ <Import-Package>
+ org.opendaylight.controller.config.api.*,
+ org.opendaylight.controller.config.spi.*,
+ org.slf4j,
+ javax.management,
+ org.osgi.framework,
+ org.opendaylight.protocol.concepts,
+ org.apache.commons.io,
+ org.osgi.util.tracker,
+ </Import-Package>
+ <Export-Package>
+ </Export-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <forkCount>1</forkCount>
+ <reuseForks>false</reuseForks>
+ <perCoreThreadCount>false</perCoreThreadCount>
+ <threadCount>1</threadCount>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.concurrent.Immutable;
+
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+
+/**
+ * Structure obtained during first phase commit, contains destroyed modules from
+ * previous transactions that need to be closed and all committed modules with
+ * meta data.
+ */
+@Immutable
+public class CommitInfo {
+ private final List<DestroyedModule> destroyedFromPreviousTransactions;
+ private final Map<ModuleIdentifier, ModuleInternalTransactionalInfo> commitMap;
+
+ public CommitInfo(List<DestroyedModule> destroyedFromPreviousTransactions,
+ Map<ModuleIdentifier, ModuleInternalTransactionalInfo> commitMap) {
+ this.destroyedFromPreviousTransactions = Collections
+ .unmodifiableList(destroyedFromPreviousTransactions);
+ this.commitMap = Collections.unmodifiableMap(commitMap);
+ }
+
+ /**
+ * Get ordered list of modules that can be closed in the same order, i.e.
+ * first element will be a leaf on which no other module depends, n-th
+ * element can only have dependencies with index smaller than n.
+ */
+ public List<DestroyedModule> getDestroyedFromPreviousTransactions() {
+ return destroyedFromPreviousTransactions;
+ }
+
+ public Map<ModuleIdentifier, ModuleInternalTransactionalInfo> getCommitted() {
+ return commitMap;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.annotation.concurrent.GuardedBy;
+import javax.annotation.concurrent.NotThreadSafe;
+import javax.annotation.concurrent.ThreadSafe;
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.InstanceNotFoundException;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerFactory;
+import javax.management.ObjectName;
+
+import org.opendaylight.controller.config.api.ConflictingVersionException;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.RuntimeBeanRegistratorAwareModule;
+import org.opendaylight.controller.config.api.ValidationException;
+import org.opendaylight.controller.config.api.jmx.CommitStatus;
+import org.opendaylight.controller.config.api.jmx.ConfigRegistryMXBean;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.manager.impl.dynamicmbean.DynamicReadableWrapper;
+import org.opendaylight.controller.config.manager.impl.factoriesresolver.HierarchicalConfigMBeanFactoriesHolder;
+import org.opendaylight.controller.config.manager.impl.factoriesresolver.ModuleFactoriesResolver;
+import org.opendaylight.controller.config.manager.impl.jmx.BaseJMXRegistrator;
+import org.opendaylight.controller.config.manager.impl.jmx.ModuleJMXRegistrator;
+import org.opendaylight.controller.config.manager.impl.jmx.RootRuntimeBeanRegistratorImpl;
+import org.opendaylight.controller.config.manager.impl.jmx.TransactionJMXRegistrator;
+import org.opendaylight.controller.config.manager.impl.osgi.BeanToOsgiServiceManager;
+import org.opendaylight.controller.config.manager.impl.osgi.BeanToOsgiServiceManager.OsgiRegistration;
+import org.opendaylight.controller.config.manager.impl.util.LookupBeansUtil;
+import org.opendaylight.controller.config.spi.Module;
+import org.osgi.framework.BundleContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Singleton that is responsible for creating and committing Config
+ * Transactions. It is registered in Platform MBean Server.
+ */
+@ThreadSafe
+public class ConfigRegistryImpl implements AutoCloseable,
+ ConfigRegistryImplMXBean {
+ private static final Logger logger = LoggerFactory
+ .getLogger(ConfigRegistryImpl.class);
+
+ private final ModuleFactoriesResolver resolver;
+ private final MBeanServer configMBeanServer;
+ @GuardedBy("this")
+ private long version = 0;
+ @GuardedBy("this")
+ private long versionCounter = 0;
+
+ /**
+ * Contains current configuration in form of {moduleName:{instanceName,read
+ * only module}} for copying state to new transaction. Each running module
+ * is present just once, no matter how many interfaces it exposes.
+ */
+ @GuardedBy("this")
+ private final ConfigHolder currentConfig = new ConfigHolder();
+
+ /**
+ * Will return true unless there was a transaction that succeeded during
+ * validation but failed in second phase of commit. In this case the server
+ * is unstable and its state is undefined.
+ */
+ @GuardedBy("this")
+ private boolean isHealthy = true;
+
+ /**
+ * Holds Map<transactionName, transactionController> and purges it each time
+ * its content is requested.
+ */
+ @GuardedBy("this")
+ private final TransactionsHolder transactionsHolder = new TransactionsHolder();
+
+ private final BaseJMXRegistrator baseJMXRegistrator;
+
+ private final BeanToOsgiServiceManager beanToOsgiServiceManager;
+
+ // internal jmx server for read only beans
+ private final MBeanServer registryMBeanServer;
+ // internal jmx server shared by all transactions
+ private final MBeanServer transactionsMBeanServer;
+
+ // constructor
+ public ConfigRegistryImpl(ModuleFactoriesResolver resolver,
+ BundleContext bundleContext, MBeanServer configMBeanServer) {
+ this(resolver, bundleContext, configMBeanServer,
+ new BaseJMXRegistrator(configMBeanServer));
+ }
+
+ // constructor
+ public ConfigRegistryImpl(ModuleFactoriesResolver resolver,
+ BundleContext bundleContext, MBeanServer configMBeanServer,
+ BaseJMXRegistrator baseJMXRegistrator) {
+ this.resolver = resolver;
+ this.beanToOsgiServiceManager = new BeanToOsgiServiceManager(
+ bundleContext);
+ this.configMBeanServer = configMBeanServer;
+ this.baseJMXRegistrator = baseJMXRegistrator;
+ this.registryMBeanServer = MBeanServerFactory
+ .createMBeanServer("ConfigRegistry" + configMBeanServer.getDefaultDomain());
+ this.transactionsMBeanServer = MBeanServerFactory
+ .createMBeanServer("ConfigTransactions" + configMBeanServer.getDefaultDomain());
+ }
+
+ /**
+ * Create new {@link ConfigTransactionControllerImpl }
+ */
+ @Override
+ public synchronized ObjectName beginConfig() {
+ return beginConfigInternal().getControllerObjectName();
+ }
+
+ private synchronized ConfigTransactionControllerInternal beginConfigInternal() {
+ versionCounter++;
+ String transactionName = "ConfigTransaction-" + version + "-"
+ + versionCounter;
+ TransactionJMXRegistrator transactionRegistrator = baseJMXRegistrator
+ .createTransactionJMXRegistrator(transactionName);
+ ConfigTransactionControllerInternal transactionController = new ConfigTransactionControllerImpl(
+ transactionName, transactionRegistrator, version,
+ versionCounter, resolver.getAllFactories(),
+ transactionsMBeanServer, configMBeanServer);
+ try {
+ transactionRegistrator.registerMBean(transactionController, transactionController.getControllerObjectName
+ ());
+ } catch (InstanceAlreadyExistsException e) {
+ throw new IllegalStateException(e);
+ }
+
+ // copy old configuration to this server
+ for (ModuleInternalInfo oldConfigInfo : currentConfig.getEntries()) {
+ try {
+ transactionController.copyExistingModule(oldConfigInfo);
+ } catch (InstanceAlreadyExistsException e) {
+ throw new IllegalStateException("Error while copying "
+ + oldConfigInfo, e);
+ }
+ }
+ transactionsHolder.add(transactionName, transactionController);
+ return transactionController;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public synchronized CommitStatus commitConfig(
+ ObjectName transactionControllerON)
+ throws ConflictingVersionException, ValidationException {
+ final String transactionName = ObjectNameUtil
+ .getTransactionName(transactionControllerON);
+ logger.info(
+ "About to commit {}. Current parentVersion: {}, versionCounter {}",
+ transactionName, version, versionCounter);
+
+ // find ConfigTransactionController
+ Map<String, ConfigTransactionControllerInternal> transactions = transactionsHolder
+ .getCurrentTransactions();
+ ConfigTransactionControllerInternal configTransactionController = transactions
+ .get(transactionName);
+ if (configTransactionController == null) {
+ throw new IllegalArgumentException(String.format(
+ "Transaction with name '%s' not found", transactionName));
+ }
+ // check optimistic lock
+ if (version != configTransactionController.getParentVersion()) {
+ throw new ConflictingVersionException(
+ String.format(
+ "Optimistic lock failed. Expected parent version %d, was %d",
+ version,
+ configTransactionController.getParentVersion()));
+ }
+ // optimistic lock ok
+
+ CommitInfo commitInfo = configTransactionController
+ .validateBeforeCommitAndLockTransaction();
+ final ConfigRegistryImpl a = this;
+ // non recoverable from here:
+ try {
+ final CommitStatus secondPhaseCommitStatus = secondPhaseCommit(
+ configTransactionController, commitInfo);
+
+ return secondPhaseCommitStatus;
+ } catch (Throwable t) { // some libs throw Errors: e.g.
+ // javax.xml.ws.spi.FactoryFinder$ConfigurationError
+ isHealthy = false;
+ logger.error(
+ "Configuration Transaction failed on 2PC, server is unhealthy",
+ t);
+ if (t instanceof RuntimeException)
+ throw (RuntimeException) t;
+ else if (t instanceof Error)
+ throw (Error) t;
+ else
+ throw new RuntimeException(t);
+ }
+ }
+
+ private CommitStatus secondPhaseCommit(
+ ConfigTransactionControllerInternal configTransactionController,
+ CommitInfo commitInfo) {
+
+ // close instances which were destroyed by the user, including
+ // (hopefully) runtime beans
+ for (DestroyedModule toBeDestroyed : commitInfo
+ .getDestroyedFromPreviousTransactions()) {
+ toBeDestroyed.close(); // closes instance (which should close
+ // runtime jmx registrator),
+ // also closes osgi registration and ModuleJMXRegistrator
+ // registration
+ currentConfig.remove(toBeDestroyed.getName());
+ }
+
+ // set RuntimeBeanRegistrators on beans implementing
+ // RuntimeBeanRegistratorAwareModule, each module
+ // should have exactly one runtime jmx registrator.
+ Map<ModuleIdentifier, RootRuntimeBeanRegistratorImpl> runtimeRegistrators = new HashMap<>();
+ for (ModuleInternalTransactionalInfo entry : commitInfo.getCommitted()
+ .values()) {
+ RootRuntimeBeanRegistratorImpl runtimeBeanRegistrator;
+ if (entry.hasOldModule() == false) {
+ runtimeBeanRegistrator = baseJMXRegistrator
+ .createRuntimeBeanRegistrator(entry.getName());
+ } else {
+ // reuse old JMX registrator
+ runtimeBeanRegistrator = entry.getOldInternalInfo()
+ .getRuntimeBeanRegistrator();
+ }
+ // set runtime jmx registrator if required
+ Module module = entry.getModule();
+ if (module instanceof RuntimeBeanRegistratorAwareModule) {
+ ((RuntimeBeanRegistratorAwareModule) module)
+ .setRuntimeBeanRegistrator(runtimeBeanRegistrator);
+ }
+ // save it to info so it is accessible afterwards
+ runtimeRegistrators.put(entry.getName(), runtimeBeanRegistrator);
+ }
+
+ // can register runtime beans
+ List<ModuleIdentifier> orderedModuleIdentifiers = configTransactionController
+ .secondPhaseCommit();
+
+ // copy configuration to read only mode
+ List<ObjectName> newInstances = new LinkedList<>();
+ List<ObjectName> reusedInstances = new LinkedList<>();
+ List<ObjectName> recreatedInstances = new LinkedList<>();
+
+ Map<Module, ModuleInternalInfo> newConfigEntries = new HashMap<>();
+
+ int orderingIdx = 0;
+ for (ModuleIdentifier moduleIdentifier : orderedModuleIdentifiers) {
+ ModuleInternalTransactionalInfo entry = commitInfo.getCommitted()
+ .get(moduleIdentifier);
+ if (entry == null)
+ throw new NullPointerException("Module not found "
+ + moduleIdentifier);
+ Module module = entry.getModule();
+ ObjectName primaryReadOnlyON = ObjectNameUtil
+ .createReadOnlyModuleON(moduleIdentifier);
+
+ // determine if current instance was recreated or reused or is new
+
+ // rules for closing resources:
+ // osgi registration - will be (re)created every time, so it needs
+ // to be closed here
+ // module jmx registration - will be (re)created every time, needs
+ // to be closed here
+ // runtime jmx registration - should be taken care of by module
+ // itself
+ // instance - is closed only if it was destroyed
+ ModuleJMXRegistrator newModuleJMXRegistrator = baseJMXRegistrator
+ .createModuleJMXRegistrator();
+
+ if (entry.hasOldModule()) {
+ ModuleInternalInfo oldInternalInfo = entry.getOldInternalInfo();
+ DynamicReadableWrapper oldReadableConfigBean = oldInternalInfo
+ .getReadableModule();
+ currentConfig.remove(entry.getName());
+
+ // test if old instance == new instance
+ if (oldReadableConfigBean.getInstance().equals(
+ module.getInstance())) {
+ // reused old instance:
+ // wrap in readable dynamic mbean
+ reusedInstances.add(primaryReadOnlyON);
+ } else {
+ // recreated instance:
+ // it is responsibility of module to call the old instance -
+ // we just need to unregister configbean
+ recreatedInstances.add(primaryReadOnlyON);
+ }
+ // close old osgi registration in any case
+ oldInternalInfo.getOsgiRegistration().close();
+ // close old module jmx registrator
+ oldInternalInfo.getModuleJMXRegistrator().close();
+ } else {
+ // new instance:
+ // wrap in readable dynamic mbean
+ newInstances.add(primaryReadOnlyON);
+ }
+
+ DynamicReadableWrapper newReadableConfigBean = new DynamicReadableWrapper(
+ module, module.getInstance(), moduleIdentifier,
+ registryMBeanServer, configMBeanServer);
+
+ // register to JMX
+ try {
+ newModuleJMXRegistrator.registerMBean(newReadableConfigBean,
+ primaryReadOnlyON);
+ } catch (InstanceAlreadyExistsException e) {
+ throw new IllegalStateException(e);
+ }
+
+ // register to OSGi
+ OsgiRegistration osgiRegistration = beanToOsgiServiceManager
+ .registerToOsgi(module.getClass(),
+ newReadableConfigBean.getInstance(),
+ entry.getName());
+
+ RootRuntimeBeanRegistratorImpl runtimeBeanRegistrator = runtimeRegistrators
+ .get(entry.getName());
+ ModuleInternalInfo newInfo = new ModuleInternalInfo(
+ entry.getName(), newReadableConfigBean, osgiRegistration,
+ runtimeBeanRegistrator, newModuleJMXRegistrator,
+ orderingIdx);
+
+ newConfigEntries.put(module, newInfo);
+ orderingIdx++;
+ }
+ currentConfig.addAll(newConfigEntries.values());
+
+ // update version
+ version = configTransactionController.getVersion();
+ return new CommitStatus(newInstances, reusedInstances,
+ recreatedInstances);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public synchronized List<ObjectName> getOpenConfigs() {
+ Map<String, ConfigTransactionControllerInternal> transactions = transactionsHolder
+ .getCurrentTransactions();
+ List<ObjectName> result = new ArrayList<>(transactions.size());
+ for (ConfigTransactionControllerInternal configTransactionController : transactions
+ .values()) {
+ result.add(configTransactionController.getControllerObjectName());
+ }
+ return result;
+ }
+
+ /**
+ * Abort open transactions and unregister read only modules. Since this
+ * class is not responsible for registering itself under
+ * {@link ConfigRegistryMXBean#OBJECT_NAME}, it will not unregister itself
+ * here.
+ */
+ @Override
+ public synchronized void close() {
+ // abort transactions
+ Map<String, ConfigTransactionControllerInternal> transactions = transactionsHolder
+ .getCurrentTransactions();
+ for (ConfigTransactionControllerInternal configTransactionController : transactions
+ .values()) {
+ try {
+ configTransactionController.abortConfig();
+ } catch (RuntimeException e) {
+ logger.warn("Ignoring exception while aborting {}",
+ configTransactionController, e);
+ }
+ }
+
+ // destroy all live objects one after another in order of the dependency
+ // hierarchy
+ List<DestroyedModule> destroyedModules = currentConfig
+ .getModulesToBeDestroyed();
+ for (DestroyedModule destroyedModule : destroyedModules) {
+ destroyedModule.close();
+ }
+ // unregister MBeans that failed to unregister properly
+ baseJMXRegistrator.close();
+ // remove jmx servers
+ MBeanServerFactory.releaseMBeanServer(registryMBeanServer);
+ MBeanServerFactory.releaseMBeanServer(transactionsMBeanServer);
+
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public long getVersion() {
+ return version;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Set<String> getAvailableModuleNames() {
+ return new HierarchicalConfigMBeanFactoriesHolder(
+ resolver.getAllFactories()).getModuleNames();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isHealthy() {
+ return isHealthy;
+ }
+
+ // filtering methods
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Set<ObjectName> lookupConfigBeans() {
+ return lookupConfigBeans("*", "*");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Set<ObjectName> lookupConfigBeans(String moduleName) {
+ return lookupConfigBeans(moduleName, "*");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ObjectName lookupConfigBean(String moduleName, String instanceName)
+ throws InstanceNotFoundException {
+ return LookupBeansUtil.lookupConfigBean(this, moduleName, instanceName);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Set<ObjectName> lookupConfigBeans(String moduleName,
+ String instanceName) {
+ ObjectName namePattern = ObjectNameUtil.createModulePattern(moduleName,
+ instanceName);
+ return baseJMXRegistrator.queryNames(namePattern, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Set<ObjectName> lookupRuntimeBeans() {
+ return lookupRuntimeBeans("*", "*");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Set<ObjectName> lookupRuntimeBeans(String moduleName,
+ String instanceName) {
+ if (moduleName == null)
+ moduleName = "*";
+ if (instanceName == null)
+ instanceName = "*";
+ ObjectName namePattern = ObjectNameUtil.createRuntimeBeanPattern(
+ moduleName, instanceName);
+ return baseJMXRegistrator.queryNames(namePattern, null);
+ }
+
+}
+
+/**
+ * Holds currently running modules
+ */
+@NotThreadSafe
+class ConfigHolder {
+ private final Map<ModuleIdentifier, ModuleInternalInfo> currentConfig = new HashMap<>();
+
+ /**
+ * Add all modules to the internal map. Also add service instance to OSGi
+ * Service Registry.
+ */
+ public void addAll(Collection<ModuleInternalInfo> configInfos) {
+ if (currentConfig.size() > 0) {
+ throw new IllegalStateException(
+ "Error - some config entries were not removed: "
+ + currentConfig);
+ }
+ for (ModuleInternalInfo configInfo : configInfos) {
+ add(configInfo);
+ }
+ }
+
+ private void add(ModuleInternalInfo configInfo) {
+ ModuleInternalInfo oldValue = currentConfig.put(configInfo.getName(),
+ configInfo);
+ if (oldValue != null) {
+ throw new IllegalStateException(
+ "Cannot overwrite module with same name:"
+ + configInfo.getName() + ":" + configInfo);
+ }
+ }
+
+ /**
+ * Remove entry from current config.
+ */
+ public void remove(ModuleIdentifier name) {
+ ModuleInternalInfo removed = currentConfig.remove(name);
+ if (removed == null) {
+ throw new IllegalStateException(
+ "Cannot remove from ConfigHolder - name not found:" + name);
+ }
+ }
+
+ public Collection<ModuleInternalInfo> getEntries() {
+ return currentConfig.values();
+ }
+
+ public List<DestroyedModule> getModulesToBeDestroyed() {
+ List<DestroyedModule> result = new ArrayList<>();
+ for (ModuleInternalInfo moduleInternalInfo : getEntries()) {
+ result.add(moduleInternalInfo.toDestroyedModule());
+ }
+ Collections.sort(result);
+ return result;
+ }
+}
+
+/**
+ * Holds Map<transactionName, transactionController> and purges it each time its
+ * content is requested.
+ */
+@NotThreadSafe
+class TransactionsHolder {
+ /**
+ * This map keeps transaction names and
+ * {@link ConfigTransactionControllerInternal} instances, because platform
+ * MBeanServer transforms mbeans into another representation. Map is cleaned
+ * every time current transactions are requested.
+ *
+ */
+ @GuardedBy("ConfigRegistryImpl.this")
+ private final Map<String /* transactionName */, ConfigTransactionControllerInternal> transactions = new HashMap<>();
+
+ /**
+ * Can only be called from within synchronized method.
+ */
+ public void add(String transactionName,
+ ConfigTransactionControllerInternal transactionController) {
+ Object oldValue = transactions.put(transactionName,
+ transactionController);
+ if (oldValue != null) {
+ throw new IllegalStateException(
+ "Error: two transactions with same name");
+ }
+ }
+
+ /**
+ * Purges closed transactions from transactions map. Can only be called from
+ * within synchronized method. Calling this method more than once within the
+ * method can modify the resulting map that was obtained in previous calls.
+ *
+ * @return current view on transactions map.
+ */
+ public Map<String, ConfigTransactionControllerInternal> getCurrentTransactions() {
+ // first, remove closed transaction
+ for (Iterator<Entry<String, ConfigTransactionControllerInternal>> it = transactions
+ .entrySet().iterator(); it.hasNext();) {
+ Entry<String, ConfigTransactionControllerInternal> entry = it
+ .next();
+ if (entry.getValue().isClosed()) {
+ it.remove();
+ }
+ }
+ return Collections.unmodifiableMap(transactions);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl;
+
+import org.opendaylight.controller.config.api.ConfigRegistry;
+
+/**
+ * Exposes version of config registry.
+ */
+public interface ConfigRegistryImplMXBean extends ConfigRegistry {
+ /**
+ * @return version of last committed transaction that is now used as base
+ * version. Transactions can only be committed if their parent
+ * version matches this value, that means, transaction must be
+ * started after last one has been committed.
+ */
+ long getVersion();
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl;
+
+import static java.lang.String.format;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+import javax.management.DynamicMBean;
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.InstanceNotFoundException;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import org.opendaylight.controller.config.api.DependencyResolver;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.ValidationException;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.manager.impl.dependencyresolver.DependencyResolverManager;
+import org.opendaylight.controller.config.manager.impl.dynamicmbean.DynamicWritableWrapper;
+import org.opendaylight.controller.config.manager.impl.dynamicmbean.ReadOnlyAtomicBoolean;
+import org.opendaylight.controller.config.manager.impl.dynamicmbean.ReadOnlyAtomicBoolean.ReadOnlyAtomicBooleanImpl;
+import org.opendaylight.controller.config.manager.impl.factoriesresolver.HierarchicalConfigMBeanFactoriesHolder;
+import org.opendaylight.controller.config.manager.impl.jmx.TransactionJMXRegistrator;
+import org.opendaylight.controller.config.manager.impl.jmx.TransactionModuleJMXRegistrator;
+import org.opendaylight.controller.config.manager.impl.jmx.TransactionModuleJMXRegistrator
+ .TransactionModuleJMXRegistration;
+import org.opendaylight.controller.config.manager.impl.util.LookupBeansUtil;
+import org.opendaylight.controller.config.spi.Module;
+import org.opendaylight.controller.config.spi.ModuleFactory;
+import org.opendaylight.protocol.concepts.NamedObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is a JMX bean representing current transaction. It contains
+ * {@link #transactionIdentifier}, unique version and parent version for
+ * optimistic locking.
+ */
+class ConfigTransactionControllerImpl implements
+ ConfigTransactionControllerInternal,
+ ConfigTransactionControllerImplMXBean,
+ NamedObject<TransactionIdentifier> {
+ private static final Logger logger = LoggerFactory
+ .getLogger(ConfigTransactionControllerImpl.class);
+
+ private final TransactionIdentifier transactionIdentifier;
+ private final ObjectName controllerON;
+ private final TransactionJMXRegistrator transactionRegistrator;
+ private final TransactionModuleJMXRegistrator txModuleJMXRegistrator;
+ private final long parentVersion, currentVersion;
+ private final HierarchicalConfigMBeanFactoriesHolder factoriesHolder;
+ private final DependencyResolverManager dependencyResolverManager;
+ private final TransactionStatus transactionStatus;
+ private final MBeanServer transactionsMBeanServer;
+
+ /**
+ * Disables ability of {@link DynamicWritableWrapper} to change attributes
+ * during validation.
+ */
+ @GuardedBy("this")
+ private final AtomicBoolean configBeanModificationDisabled = new AtomicBoolean(
+ false);
+ private final ReadOnlyAtomicBoolean readOnlyAtomicBoolean = new ReadOnlyAtomicBooleanImpl(
+ configBeanModificationDisabled);
+ private final MBeanServer configMBeanServer;
+
+ public ConfigTransactionControllerImpl(String transactionName,
+ TransactionJMXRegistrator transactionRegistrator,
+ long parentVersion, long currentVersion,
+ List<? extends ModuleFactory> currentlyRegisteredFactories,
+ MBeanServer transactionsMBeanServer, MBeanServer configMBeanServer) {
+
+ this.transactionIdentifier = new TransactionIdentifier(transactionName);
+ this.controllerON = ObjectNameUtil
+ .createTransactionControllerON(transactionName);
+ this.transactionRegistrator = transactionRegistrator;
+ txModuleJMXRegistrator = transactionRegistrator
+ .createTransactionModuleJMXRegistrator();
+ this.parentVersion = parentVersion;
+ this.currentVersion = currentVersion;
+ this.factoriesHolder = new HierarchicalConfigMBeanFactoriesHolder(
+ currentlyRegisteredFactories);
+ this.transactionStatus = new TransactionStatus();
+ this.dependencyResolverManager = new DependencyResolverManager(
+ transactionName, transactionStatus);
+ this.transactionsMBeanServer = transactionsMBeanServer;
+ this.configMBeanServer = configMBeanServer;
+ }
+
+ @Override
+ public synchronized void copyExistingModule(
+ ModuleInternalInfo oldConfigBeanInfo)
+ throws InstanceAlreadyExistsException {
+ transactionStatus.checkNotCommitStarted();
+ transactionStatus.checkNotAborted();
+ ModuleIdentifier moduleIdentifier = oldConfigBeanInfo.getName();
+ dependencyResolverManager.assertNotExists(moduleIdentifier);
+
+ ModuleFactory moduleFactory = factoriesHolder
+ .findByModuleName(moduleIdentifier.getFactoryName());
+
+ Module module;
+ DependencyResolver dependencyResolver = dependencyResolverManager
+ .getOrCreate(moduleIdentifier);
+ try {
+ module = moduleFactory.createModule(
+ moduleIdentifier.getInstanceName(), dependencyResolver,
+ oldConfigBeanInfo.getReadableModule());
+ } catch (Exception e) {
+ throw new IllegalStateException(format(
+ "Error while copying old configuration from %s to %s",
+ oldConfigBeanInfo, moduleFactory), e);
+ }
+ putConfigBeanToJMXAndInternalMaps(moduleIdentifier, module,
+ moduleFactory, oldConfigBeanInfo);
+ }
+
+ @Override
+ public synchronized ObjectName createModule(String factoryName,
+ String instanceName) throws InstanceAlreadyExistsException {
+
+ transactionStatus.checkNotCommitStarted();
+ transactionStatus.checkNotAborted();
+ ModuleIdentifier moduleIdentifier = new ModuleIdentifier(factoryName,
+ instanceName);
+ dependencyResolverManager.assertNotExists(moduleIdentifier);
+
+ // find factory
+ ModuleFactory moduleFactory = factoriesHolder
+ .findByModuleName(factoryName);
+ DependencyResolver dependencyResolver = dependencyResolverManager
+ .getOrCreate(moduleIdentifier);
+ Module module = moduleFactory.createModule(instanceName,
+ dependencyResolver);
+ return putConfigBeanToJMXAndInternalMaps(moduleIdentifier, module,
+ moduleFactory, null);
+ }
+
+ private synchronized ObjectName putConfigBeanToJMXAndInternalMaps(
+ ModuleIdentifier moduleIdentifier, Module module,
+ ModuleFactory moduleFactory,
+ @Nullable ModuleInternalInfo maybeOldConfigBeanInfo)
+ throws InstanceAlreadyExistsException {
+
+ DynamicMBean writableDynamicWrapper = new DynamicWritableWrapper(
+ module, moduleIdentifier, transactionIdentifier,
+ readOnlyAtomicBoolean, transactionsMBeanServer,
+ configMBeanServer);
+
+ ObjectName writableON = ObjectNameUtil.createTransactionModuleON(
+ transactionIdentifier.getName(), moduleIdentifier);
+ // put wrapper to jmx
+ TransactionModuleJMXRegistration transactionModuleJMXRegistration = txModuleJMXRegistrator
+ .registerMBean(writableDynamicWrapper, writableON);
+ ModuleInternalTransactionalInfo moduleInternalTransactionalInfo = new ModuleInternalTransactionalInfo(
+ moduleIdentifier, module, moduleFactory,
+ maybeOldConfigBeanInfo, transactionModuleJMXRegistration);
+
+ dependencyResolverManager.put(moduleInternalTransactionalInfo);
+ return writableON;
+ }
+
+ @Override
+ public void destroyModule(ObjectName objectName)
+ throws InstanceNotFoundException {
+ String foundTransactionName = ObjectNameUtil
+ .getTransactionName(objectName);
+ if (transactionIdentifier.getName().equals(foundTransactionName) == false) {
+ throw new IllegalArgumentException("Wrong transaction name "
+ + objectName);
+ }
+ ObjectNameUtil.checkDomain(objectName);
+ transactionStatus.checkNotAborted();
+ ModuleIdentifier moduleIdentifier = ObjectNameUtil.fromON(objectName,
+ ObjectNameUtil.TYPE_MODULE);
+ ModuleInternalTransactionalInfo removedTInfo = dependencyResolverManager
+ .destroyModule(moduleIdentifier);
+ // remove from jmx
+ removedTInfo.getTransactionModuleJMXRegistration().close();
+ }
+
+ @Override
+ public long getParentVersion() {
+ return parentVersion;
+ }
+
+ @Override
+ public long getVersion() {
+ return currentVersion;
+ }
+
+ @Override
+ public synchronized void validateConfig() throws ValidationException {
+ if (configBeanModificationDisabled.get())
+ throw new IllegalStateException("Cannot start validation");
+ configBeanModificationDisabled.set(true);
+ try {
+ validate_noLocks();
+ } finally {
+ configBeanModificationDisabled.set(false);
+ }
+ }
+
+ private void validate_noLocks() throws ValidationException {
+ transactionStatus.checkNotAborted();
+ logger.info("Validating transaction {}", transactionIdentifier);
+ // call validate()
+ List<ValidationException> collectedExceptions = new ArrayList<>();
+ for (Entry<ModuleIdentifier, Module> entry : dependencyResolverManager
+ .getAllModules().entrySet()) {
+ ModuleIdentifier name = entry.getKey();
+ Module module = entry.getValue();
+ try {
+ module.validate();
+ } catch (Exception e) {
+ logger.warn("Validation exception in {}", getTransactionName(),
+ e);
+ collectedExceptions.add(ValidationException
+ .createForSingleException(name, e));
+ }
+ }
+ if (collectedExceptions.size() > 0) {
+ throw ValidationException
+ .createFromCollectedValidationExceptions(collectedExceptions);
+ }
+ logger.info("Validated transaction {}", transactionIdentifier);
+ }
+
+ /**
+ * If this method passes validation, it will grab
+ * {@link TransactionStatus#secondPhaseCommitStarted} lock. This lock will
+ * prevent calling @{link #validateBeforeCommitAndLockTransaction},
+ * effectively only allowing to call {@link #secondPhaseCommit} after
+ * successful return of this method.
+ */
+ @Override
+ public synchronized CommitInfo validateBeforeCommitAndLockTransaction()
+ throws ValidationException {
+ transactionStatus.checkNotAborted();
+ transactionStatus.checkNotCommitStarted();
+ configBeanModificationDisabled.set(true);
+ try {
+ validate_noLocks();
+ } catch (ValidationException e) {
+ logger.info("Commit failed on validation");
+ configBeanModificationDisabled.set(false); // recoverable error
+ throw e;
+ }
+ // errors in this state are not recoverable. modules are not mutable
+ // anymore.
+ transactionStatus.setSecondPhaseCommitStarted();
+ return dependencyResolverManager.toCommitInfo();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public synchronized List<ModuleIdentifier> secondPhaseCommit() {
+ transactionStatus.checkNotAborted();
+ transactionStatus.checkCommitStarted();
+ if (configBeanModificationDisabled.get() == false) {
+ throw new IllegalStateException(
+ "Internal error - validateBeforeCommitAndLockTransaction should be called "
+ + "to obtain a lock");
+ }
+
+ logger.info("Committing transaction {}", transactionIdentifier);
+
+ // call getInstance()
+ for (Entry<ModuleIdentifier, Module> entry : dependencyResolverManager
+ .getAllModules().entrySet()) {
+ Module module = entry.getValue();
+ ModuleIdentifier name = entry.getKey();
+ try {
+ logger.debug("About to commit {} in transaction {}",
+ transactionIdentifier, name);
+ module.getInstance();
+ } catch (Exception e) {
+ logger.error("Commit failed on {} in transaction {}", name,
+ transactionIdentifier, e);
+ internalAbort();
+ throw new RuntimeException(
+ format("Error - getInstance() failed for %s in transaction %s",
+ name, transactionIdentifier), e);
+ }
+ }
+
+ // count dependency order
+
+ logger.info("Committed configuration {}", transactionIdentifier);
+ transactionStatus.setCommitted();
+ // unregister this and all modules from jmx
+ close();
+
+ return dependencyResolverManager.getSortedModuleIdentifiers();
+ }
+
+ @Override
+ public synchronized void abortConfig() {
+ transactionStatus.checkNotCommitStarted();
+ transactionStatus.checkNotAborted();
+ internalAbort();
+ }
+
+ private void internalAbort() {
+ transactionStatus.setAborted();
+ close();
+ }
+
+ private void close() {
+ transactionRegistrator.close();
+ }
+
+ @Override
+ public ObjectName getControllerObjectName() {
+ return controllerON;
+ }
+
+ @Override
+ public String getTransactionName() {
+ return transactionIdentifier.getName();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Set<ObjectName> lookupConfigBeans() {
+ return lookupConfigBeans("*", "*");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Set<ObjectName> lookupConfigBeans(String moduleName) {
+ return lookupConfigBeans(moduleName, "*");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ObjectName lookupConfigBean(String moduleName, String instanceName)
+ throws InstanceNotFoundException {
+ return LookupBeansUtil.lookupConfigBean(this, moduleName, instanceName);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Set<ObjectName> lookupConfigBeans(String moduleName,
+ String instanceName) {
+ ObjectName namePattern = ObjectNameUtil.createModulePattern(moduleName,
+ instanceName, transactionIdentifier.getName());
+ return txModuleJMXRegistrator.queryNames(namePattern, null);
+ }
+
+ @Override
+ public Set<String> getAvailableModuleNames() {
+ return factoriesHolder.getModuleNames();
+ }
+
+ @Override
+ public boolean isClosed() {
+ return transactionStatus.isAbortedOrCommitted();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("transactionName=");
+ sb.append(getTransactionName());
+ return sb.toString();
+ }
+
+ // @VisibleForTesting
+
+ TransactionModuleJMXRegistrator getTxModuleJMXRegistrator() {
+ return txModuleJMXRegistrator;
+ }
+
+ @Override
+ public TransactionIdentifier getName() {
+ return transactionIdentifier;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl;
+
+import org.opendaylight.controller.config.api.ConfigTransactionController;
+
+/**
+ * Exposes optimistic locking details about transaction.
+ */
+public interface ConfigTransactionControllerImplMXBean extends
+ ConfigTransactionController {
+
+ /**
+ * Get version of config registry when this transaction was created.
+ */
+ long getParentVersion();
+
+ /**
+ * Get version of current transaction.
+ */
+ long getVersion();
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl;
+
+import java.util.List;
+
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.ObjectName;
+
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.ValidationException;
+
+/**
+ * Defines contract between {@link ConfigTransactionControllerImpl} (producer)
+ * and {@link ConfigRegistryImpl} (consumer).
+ */
+interface ConfigTransactionControllerInternal extends
+ ConfigTransactionControllerImplMXBean {
+
+ /**
+ * Copy already committed module to current transaction.
+ */
+ void copyExistingModule(ModuleInternalInfo oldConfigBeanInfo)
+ throws InstanceAlreadyExistsException;
+
+ /**
+ * Call {@link org.opendaylight.controller.config.spi.Module#validate()} on
+ * all beans in transaction. Lock transaction after successful validation.
+ * This method can be called multiple times if validation fails, but cannot
+ * be called after it did not throw exception.
+ *
+ * @throws {@link RuntimeException} if validation fails. It is safe to run
+ * this method in future
+ * @return CommitInfo
+ */
+ CommitInfo validateBeforeCommitAndLockTransaction()
+ throws ValidationException;
+
+ /**
+ * Call {@link org.opendaylight.controller.config.spi.Module#getInstance()}
+ * on all beans in transaction. This method can be only called once.
+ *
+ * @throws {@link RuntimeException} commit fails, indicates bug in config
+ * bean
+ * @return ordered list of module identifiers that respects dependency
+ * order.
+ */
+ List<ModuleIdentifier> secondPhaseCommit();
+
+ /**
+ * @return ObjectName of this transaction controller
+ */
+ ObjectName getControllerObjectName();
+
+ /**
+ * @return true iif transaction was committed or aborted.
+ */
+ boolean isClosed();
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl;
+
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.manager.impl.jmx.ModuleJMXRegistrator;
+import org.opendaylight.controller.config.manager.impl.osgi.BeanToOsgiServiceManager.OsgiRegistration;
+import org.opendaylight.protocol.concepts.NamedObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Transfer object representing already committed module that needs to be
+ * destroyed. Implements comparable in order to preserve order in which modules
+ * were created. Module instances should be closed in order defined by the
+ * compareTo method.
+ */
+public class DestroyedModule implements NamedObject<ModuleIdentifier>,
+ AutoCloseable, Comparable<DestroyedModule> {
+ private static final Logger logger = LoggerFactory
+ .getLogger(DestroyedModule.class);
+
+ private final ModuleIdentifier name;
+ private final AutoCloseable instance;
+ private final ModuleJMXRegistrator oldJMXRegistrator;
+ private final OsgiRegistration osgiRegistration;
+ private final int orderingIdx;
+
+ DestroyedModule(ModuleIdentifier name, AutoCloseable instance,
+ ModuleJMXRegistrator oldJMXRegistrator,
+ OsgiRegistration osgiRegistration, int orderingIdx) {
+ this.name = name;
+ this.instance = instance;
+ this.oldJMXRegistrator = oldJMXRegistrator;
+ this.osgiRegistration = osgiRegistration;
+ this.orderingIdx = orderingIdx;
+ }
+
+ @Override
+ public ModuleIdentifier getName() {
+ return name;
+ }
+
+ @Override
+ public void close() {
+ logger.info("Destroying {}", name);
+ try {
+ instance.close();
+ } catch (Exception e) {
+ logger.error("Error while closing instance of {}", name, e);
+ }
+ try {
+ oldJMXRegistrator.close();
+ } catch (Exception e) {
+ logger.error("Error while closing jmx registrator of {}", name, e);
+ }
+ try {
+ osgiRegistration.close();
+ } catch (Exception e) {
+ logger.error("Error while closing osgi registration of {}", name, e);
+ }
+ }
+
+ @Override
+ public int compareTo(DestroyedModule o) {
+ return Integer.compare(orderingIdx, o.orderingIdx);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl;
+
+import javax.annotation.Nullable;
+
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.manager.impl.dynamicmbean.DynamicReadableWrapper;
+import org.opendaylight.controller.config.manager.impl.jmx.ModuleJMXRegistrator;
+import org.opendaylight.controller.config.manager.impl.jmx.RootRuntimeBeanRegistratorImpl;
+import org.opendaylight.controller.config.manager.impl.osgi.BeanToOsgiServiceManager.OsgiRegistration;
+import org.opendaylight.protocol.concepts.NamedObject;
+
+/**
+ * Provides metadata about Module from controller to registry.
+ */
+public class ModuleInternalInfo implements NamedObject<ModuleIdentifier>,
+ Comparable<ModuleInternalInfo> {
+
+ private final ModuleIdentifier name;
+ // this registrator is passed to runtime bean registrator and config
+ // registry to register read only module.
+ // writable modules are registered using TransactionJMXRegistrator
+ @Nullable
+ private final DynamicReadableWrapper readableModule;
+
+ private final RootRuntimeBeanRegistratorImpl runtimeBeanRegistrator;
+ // added when bean instance is registered to Osgi
+ // can be unregistered using this registration
+ private final OsgiRegistration osgiRegistration;
+ private final ModuleJMXRegistrator moduleJMXRegistrator;
+ private final int orderingIdx;
+
+ public ModuleInternalInfo(ModuleIdentifier name,
+ @Nullable DynamicReadableWrapper readableModule,
+ OsgiRegistration osgiRegistration,
+ RootRuntimeBeanRegistratorImpl runtimeBeanRegistrator,
+ ModuleJMXRegistrator moduleJMXRegistrator, int orderingIdx) {
+
+ if (osgiRegistration == null) {
+ throw new IllegalArgumentException(
+ "Parameter 'osgiRegistration' is missing");
+ }
+ if (runtimeBeanRegistrator == null) {
+ throw new IllegalArgumentException(
+ "Parameter 'runtimeBeanRegistrator' is missing");
+ }
+ this.readableModule = readableModule;
+ this.osgiRegistration = osgiRegistration;
+ this.runtimeBeanRegistrator = runtimeBeanRegistrator;
+ this.name = name;
+ this.moduleJMXRegistrator = moduleJMXRegistrator;
+ this.orderingIdx = orderingIdx;
+ }
+
+ public DynamicReadableWrapper getReadableModule() {
+ return readableModule;
+ }
+
+ public ModuleJMXRegistrator getModuleJMXRegistrator() {
+ return moduleJMXRegistrator;
+ }
+
+ /**
+ *
+ * @return iif an running instance exists in the system.
+ */
+ public boolean hasReadableModule() {
+ return readableModule != null;
+ }
+
+ @Override
+ public String toString() {
+ return "ModuleInternalInfo [name=" + name + "]";
+ }
+
+ public RootRuntimeBeanRegistratorImpl getRuntimeBeanRegistrator() {
+ return runtimeBeanRegistrator;
+ }
+
+ public OsgiRegistration getOsgiRegistration() {
+ return osgiRegistration;
+ }
+
+ @Override
+ public ModuleIdentifier getName() {
+ return name;
+ }
+
+ /**
+ * Get index representing dependency ordering within a transaction.
+ */
+ public int getOrderingIdx() {
+ return orderingIdx;
+ }
+
+ /**
+ * Compare using orderingIdx
+ */
+ @Override
+ public int compareTo(ModuleInternalInfo o) {
+ return Integer.compare(orderingIdx, o.orderingIdx);
+ }
+
+ public DestroyedModule toDestroyedModule() {
+ return new DestroyedModule(getName(),
+ getReadableModule().getInstance(), getModuleJMXRegistrator(),
+ getOsgiRegistration(), getOrderingIdx());
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl;
+
+import javax.annotation.Nullable;
+
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.manager.impl.dynamicmbean.DynamicReadableWrapper;
+import org.opendaylight.controller.config.manager.impl.jmx.TransactionModuleJMXRegistrator;
+import org.opendaylight.controller.config.manager.impl.jmx.TransactionModuleJMXRegistrator
+ .TransactionModuleJMXRegistration;
+import org.opendaylight.controller.config.spi.Module;
+import org.opendaylight.controller.config.spi.ModuleFactory;
+import org.opendaylight.protocol.concepts.NamedObject;
+
+public class ModuleInternalTransactionalInfo implements
+ NamedObject<ModuleIdentifier> {
+ private final ModuleIdentifier name;
+ private final Module module;
+ private final ModuleFactory moduleFactory;
+ @Nullable
+ private final ModuleInternalInfo maybeOldInternalInfo;
+ private final TransactionModuleJMXRegistration transactionModuleJMXRegistration;
+
+ ModuleInternalTransactionalInfo(ModuleIdentifier name, Module module,
+ ModuleFactory moduleFactory,
+ ModuleInternalInfo maybeOldInternalInfo,
+ TransactionModuleJMXRegistration transactionModuleJMXRegistration) {
+ this.name = name;
+ this.module = module;
+ this.moduleFactory = moduleFactory;
+ this.maybeOldInternalInfo = maybeOldInternalInfo;
+ this.transactionModuleJMXRegistration = transactionModuleJMXRegistration;
+ }
+
+ @Override
+ public ModuleIdentifier getName() {
+ return name;
+ }
+
+ public boolean hasOldModule() {
+ return maybeOldInternalInfo != null;
+ }
+
+ public DestroyedModule toDestroyedModule() {
+ if (maybeOldInternalInfo == null) {
+ throw new IllegalStateException("Cannot destoy uncommitted module");
+ }
+ DynamicReadableWrapper oldModule = maybeOldInternalInfo
+ .getReadableModule();
+ return new DestroyedModule(name, oldModule.getInstance(),
+ maybeOldInternalInfo.getModuleJMXRegistrator(),
+ maybeOldInternalInfo.getOsgiRegistration(),
+ maybeOldInternalInfo.getOrderingIdx());
+ }
+
+ public Module getModule() {
+ return module;
+ }
+
+ public ModuleFactory getModuleFactory() {
+ return moduleFactory;
+ }
+
+ @Nullable
+ public ModuleInternalInfo getOldInternalInfo() {
+ if (maybeOldInternalInfo == null)
+ throw new NullPointerException();
+ return maybeOldInternalInfo;
+ }
+
+ public TransactionModuleJMXRegistration getTransactionModuleJMXRegistration() {
+ return transactionModuleJMXRegistration;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl;
+
+import org.opendaylight.protocol.concepts.Identifier;
+
+public class TransactionIdentifier implements Identifier {
+ private final String name;
+
+ public TransactionIdentifier(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String toString() {
+ return "TransactionIdentifier{" + "name='" + name + '\'' + '}';
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ TransactionIdentifier that = (TransactionIdentifier) o;
+
+ if (name != null ? !name.equals(that.name) : that.name != null)
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return name != null ? name.hashCode() : 0;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl;
+
+import javax.annotation.concurrent.GuardedBy;
+
+public class TransactionStatus {
+ @GuardedBy("this")
+ private boolean secondPhaseCommitStarted = false;
+ // switches to true during abort or commit failure
+ @GuardedBy("this")
+ private boolean aborted;
+ // switches to true during second phase commit
+ @GuardedBy("this")
+ private boolean committed;
+
+ public synchronized boolean isSecondPhaseCommitStarted() {
+ return secondPhaseCommitStarted;
+ }
+
+ synchronized void setSecondPhaseCommitStarted() {
+ this.secondPhaseCommitStarted = true;
+ }
+
+ public synchronized boolean isAborted() {
+ return aborted;
+ }
+
+ synchronized void setAborted() {
+ this.aborted = true;
+ }
+
+ public synchronized boolean isCommitted() {
+ return committed;
+ }
+
+ synchronized void setCommitted() {
+ this.committed = true;
+ }
+
+ public synchronized boolean isAbortedOrCommitted() {
+ return aborted || committed;
+ }
+
+ public synchronized void checkNotCommitStarted() {
+ if (secondPhaseCommitStarted == true)
+ throw new IllegalStateException("Commit was triggered");
+ }
+
+ public synchronized void checkCommitStarted() {
+ if (secondPhaseCommitStarted == false)
+ throw new IllegalStateException("Commit was not triggered");
+ }
+
+ public synchronized void checkNotAborted() {
+ if (aborted == true)
+ throw new IllegalStateException("Configuration was aborted");
+ }
+
+ public synchronized void checkNotCommitted() {
+ if (committed == true) {
+ throw new IllegalStateException(
+ "Cannot use this method after second phase commit");
+ }
+ }
+
+ public synchronized void checkCommitted() {
+ if (committed == false) {
+ throw new IllegalStateException(
+ "Cannot use this method before second phase commit");
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl.dependencyresolver;
+
+import static java.lang.String.format;
+
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import javax.annotation.concurrent.GuardedBy;
+import javax.management.ObjectName;
+
+import org.opendaylight.controller.config.api.DependencyResolver;
+import org.opendaylight.controller.config.api.JmxAttribute;
+import org.opendaylight.controller.config.api.JmxAttributeValidationException;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.annotations.AbstractServiceInterface;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.manager.impl.TransactionStatus;
+import org.opendaylight.controller.config.spi.Module;
+import org.opendaylight.controller.config.spi.ModuleFactory;
+import org.opendaylight.protocol.concepts.NamedObject;
+
+/**
+ * Protect {@link org.opendaylight.controller.config.spi.Module#getInstance()}
+ * by creating proxy that would throw exception if those methods are called
+ * during validation. Tracks dependencies for ordering purposes.
+ */
+final class DependencyResolverImpl implements DependencyResolver,
+ NamedObject<ModuleIdentifier>, Comparable<DependencyResolverImpl> {
+ private final ModulesHolder modulesHolder;
+ private final ModuleIdentifier name;
+ private final TransactionStatus transactionStatus;
+ @GuardedBy("this")
+ private final Set<ModuleIdentifier> dependencies = new HashSet<>();
+
+ DependencyResolverImpl(ModuleIdentifier currentModule,
+ TransactionStatus transactionStatus, ModulesHolder modulesHolder) {
+ this.name = currentModule;
+ this.transactionStatus = transactionStatus;
+ this.modulesHolder = modulesHolder;
+ }
+
+ @Override
+ public ModuleIdentifier getName() {
+ return name;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void validateDependency(
+ Class<? extends AbstractServiceInterface> expectedServiceInterface,
+ ObjectName dependentModuleReadOnlyON, JmxAttribute jmxAttribute) {
+
+ transactionStatus.checkNotCommitted();
+ if (expectedServiceInterface == null) {
+ throw new NullPointerException(
+ "Parameter 'expectedServiceInterface' is null");
+ }
+ if (jmxAttribute == null)
+ throw new NullPointerException("Parameter 'jmxAttribute' is null");
+
+ JmxAttributeValidationException.checkNotNull(dependentModuleReadOnlyON,
+ "is null, " + "expected dependency implementing "
+ + expectedServiceInterface, jmxAttribute);
+
+ // check that objectName belongs to this transaction - this should be
+ // stripped
+ // in DynamicWritableWrapper
+ boolean hasTransaction = ObjectNameUtil
+ .getTransactionName(dependentModuleReadOnlyON) != null;
+ JmxAttributeValidationException.checkCondition(
+ hasTransaction == false,
+ format("ObjectName should not contain "
+ + "transaction name. %s set to %s. ", jmxAttribute,
+ dependentModuleReadOnlyON), jmxAttribute);
+
+ ModuleIdentifier moduleIdentifier = ObjectNameUtil.fromON(dependentModuleReadOnlyON, ObjectNameUtil
+ .TYPE_MODULE);
+
+ ModuleFactory foundFactory = modulesHolder.findModuleFactory(moduleIdentifier, jmxAttribute);
+
+ boolean implementsSI = foundFactory
+ .isModuleImplementingServiceInterface(expectedServiceInterface);
+ if (implementsSI == false) {
+ String message = format(
+ "Found module factory does not expose expected service interface. "
+ + "Module name is %s : %s, expected service interface %s, dependent module ON %s , "
+ + "attribute %s",
+ foundFactory.getImplementationName(), foundFactory,
+ expectedServiceInterface, dependentModuleReadOnlyON,
+ jmxAttribute);
+ throw new JmxAttributeValidationException(message, jmxAttribute);
+ }
+ synchronized (this) {
+ dependencies.add(moduleIdentifier);
+ }
+ }
+
+ @Override
+ public void validateDependency(
+ Class<? extends AbstractServiceInterface> expectedServiceInterface,
+ ObjectName objectName, String attributeNameForErrorReporting) {
+ validateDependency(expectedServiceInterface, objectName,
+ new JmxAttribute(attributeNameForErrorReporting));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public <T> T resolveInstance(Class<T> expectedType, ObjectName dependentON,
+ JmxAttribute jmxAttribute) {
+ if (expectedType == null || dependentON == null || jmxAttribute == null) {
+ throw new IllegalArgumentException(format(
+ "Null parameters not allowed, got {} {} {}", expectedType,
+ dependentON, jmxAttribute));
+ }
+
+ transactionStatus.checkCommitStarted();
+ transactionStatus.checkNotCommitted();
+
+ ModuleIdentifier dependentModuleIdentifier = ObjectNameUtil.fromON(
+ dependentON, ObjectNameUtil.TYPE_MODULE);
+ Module module = modulesHolder.findModule(dependentModuleIdentifier,
+ jmxAttribute);
+ synchronized (this) {
+ dependencies.add(dependentModuleIdentifier);
+ }
+ AutoCloseable instance = module.getInstance();
+ if (instance == null) {
+ String message = format(
+ "Error while %s resolving instance %s. getInstance() returned null. "
+ + "Expected type %s , attribute %s", name,
+ dependentModuleIdentifier, expectedType, jmxAttribute);
+ throw new JmxAttributeValidationException(message, jmxAttribute);
+ }
+ try {
+ T result = expectedType.cast(instance);
+ return result;
+ } catch (ClassCastException e) {
+ String message = format(
+ "Instance cannot be cast to expected type. Instance class is %s , "
+ + "expected type %s , attribute %s",
+ instance.getClass(), expectedType, jmxAttribute);
+ throw new JmxAttributeValidationException(message, e, jmxAttribute);
+ }
+ }
+
+ @Deprecated
+ @Override
+ public <T> T resolveInstance(Class<T> expectedType, ObjectName objectName) {
+ return resolveInstance(expectedType, objectName, new JmxAttribute(
+ "unknown attribute"));
+ }
+
+ @Override
+ public int compareTo(DependencyResolverImpl o) {
+ transactionStatus.checkCommitted();
+ return Integer.compare(getMaxDependencyDepth(),
+ o.getMaxDependencyDepth());
+ }
+
+ private Integer maxDependencyDepth;
+
+ int getMaxDependencyDepth() {
+ if (maxDependencyDepth == null) {
+ throw new IllegalStateException("Dependency depth was not computed");
+ }
+ return maxDependencyDepth;
+ }
+
+ public void countMaxDependencyDepth(DependencyResolverManager manager) {
+ transactionStatus.checkCommitted();
+ if (maxDependencyDepth == null) {
+ maxDependencyDepth = getMaxDepth(this, manager,
+ new LinkedHashSet<ModuleIdentifier>());
+ }
+ }
+
+ private static int getMaxDepth(DependencyResolverImpl impl,
+ DependencyResolverManager manager,
+ LinkedHashSet<ModuleIdentifier> chainForDetectingCycles) {
+ int maxDepth = 0;
+ LinkedHashSet<ModuleIdentifier> chainForDetectingCycles2 = new LinkedHashSet<>(
+ chainForDetectingCycles);
+ chainForDetectingCycles2.add(impl.getName());
+ for (ModuleIdentifier dependencyName : impl.dependencies) {
+ DependencyResolverImpl dependentDRI = manager
+ .getOrCreate(dependencyName);
+ if (chainForDetectingCycles2.contains(dependencyName)) {
+ throw new IllegalStateException(format(
+ "Cycle detected, {} contains {}",
+ chainForDetectingCycles2, dependencyName));
+ }
+ int subDepth;
+ if (dependentDRI.maxDependencyDepth != null) {
+ subDepth = dependentDRI.maxDependencyDepth;
+ } else {
+ subDepth = getMaxDepth(dependentDRI, manager,
+ chainForDetectingCycles2);
+ dependentDRI.maxDependencyDepth = subDepth;
+ }
+ if (subDepth + 1 > maxDepth) {
+ maxDepth = subDepth + 1;
+ }
+ }
+ impl.maxDependencyDepth = maxDepth;
+ return maxDepth;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl.dependencyresolver;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.concurrent.GuardedBy;
+import javax.management.InstanceAlreadyExistsException;
+
+import org.opendaylight.controller.config.api.JmxAttribute;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.manager.impl.CommitInfo;
+import org.opendaylight.controller.config.manager.impl.ModuleInternalTransactionalInfo;
+import org.opendaylight.controller.config.manager.impl.TransactionStatus;
+import org.opendaylight.controller.config.spi.Module;
+import org.opendaylight.controller.config.spi.ModuleFactory;
+
+/**
+ * Holds information about modules being created and destroyed within this
+ * transaction. Observes usage of DependencyResolver within modules to figure
+ * out dependency tree.
+ */
+public class DependencyResolverManager implements TransactionHolder {
+ @GuardedBy("this")
+ private final Map<ModuleIdentifier, DependencyResolverImpl> moduleIdentifiersToDependencyResolverMap = new HashMap<>();
+ private final ModulesHolder modulesHolder;
+ private final TransactionStatus transactionStatus;
+
+ public DependencyResolverManager(String transactionName,
+ TransactionStatus transactionStatus) {
+ this.modulesHolder = new ModulesHolder(transactionName);
+ this.transactionStatus = transactionStatus;
+ }
+
+ public synchronized DependencyResolverImpl getOrCreate(ModuleIdentifier name) {
+ DependencyResolverImpl dependencyResolver = moduleIdentifiersToDependencyResolverMap
+ .get(name);
+ if (dependencyResolver == null) {
+ transactionStatus.checkNotCommitted();
+ dependencyResolver = new DependencyResolverImpl(name,
+ transactionStatus, modulesHolder);
+ moduleIdentifiersToDependencyResolverMap.put(name,
+ dependencyResolver);
+ }
+ return dependencyResolver;
+ }
+
+ /**
+ * Get all dependency resolvers, including those that belong to destroyed
+ * things?
+ */
+ private List<DependencyResolverImpl> getAllSorted() {
+ transactionStatus.checkCommitted();
+ List<DependencyResolverImpl> sorted = new ArrayList<>(
+ moduleIdentifiersToDependencyResolverMap.values());
+ for (DependencyResolverImpl dri : sorted) {
+ dri.countMaxDependencyDepth(this);
+ }
+ Collections.sort(sorted);
+ return sorted;
+ }
+
+ public List<ModuleIdentifier> getSortedModuleIdentifiers() {
+ List<ModuleIdentifier> result = new ArrayList<>(
+ moduleIdentifiersToDependencyResolverMap.size());
+ for (DependencyResolverImpl dri : getAllSorted()) {
+ ModuleIdentifier driName = dri.getName();
+ result.add(driName);
+ }
+ return result;
+ }
+
+ @Override
+ public ModuleInternalTransactionalInfo destroyModule(
+ ModuleIdentifier moduleIdentifier) {
+ transactionStatus.checkNotCommitted();
+ ModuleInternalTransactionalInfo found = modulesHolder
+ .destroyModule(moduleIdentifier);
+ moduleIdentifiersToDependencyResolverMap.remove(moduleIdentifier);
+ return found;
+ }
+
+ // protect write access
+ @Override
+ public void put(
+ ModuleInternalTransactionalInfo moduleInternalTransactionalInfo) {
+ transactionStatus.checkNotCommitted();
+ modulesHolder.put(moduleInternalTransactionalInfo);
+ }
+
+ // wrapped methods:
+
+ @Override
+ public CommitInfo toCommitInfo() {
+ return modulesHolder.toCommitInfo();
+ }
+
+ @Override
+ public Module findModule(ModuleIdentifier moduleIdentifier,
+ JmxAttribute jmxAttributeForReporting) {
+ return modulesHolder.findModule(moduleIdentifier,
+ jmxAttributeForReporting);
+ }
+
+ @Override
+ public ModuleFactory findModuleFactory(ModuleIdentifier moduleIdentifier,
+ JmxAttribute jmxAttributeForReporting) {
+ return modulesHolder.findModuleFactory(moduleIdentifier,
+ jmxAttributeForReporting);
+ }
+
+ @Override
+ public Map<ModuleIdentifier, Module> getAllModules() {
+ return modulesHolder.getAllModules();
+ }
+
+ @Override
+ public void assertNotExists(ModuleIdentifier moduleIdentifier)
+ throws InstanceAlreadyExistsException {
+ modulesHolder.assertNotExists(moduleIdentifier);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl.dependencyresolver;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.concurrent.GuardedBy;
+import javax.management.InstanceAlreadyExistsException;
+
+import org.opendaylight.controller.config.api.JmxAttribute;
+import org.opendaylight.controller.config.api.JmxAttributeValidationException;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.manager.impl.CommitInfo;
+import org.opendaylight.controller.config.manager.impl.DestroyedModule;
+import org.opendaylight.controller.config.manager.impl.ModuleInternalTransactionalInfo;
+import org.opendaylight.controller.config.spi.Module;
+import org.opendaylight.controller.config.spi.ModuleFactory;
+
+/**
+ * Represents modules to be committed.
+ */
+class ModulesHolder implements TransactionHolder {
+ private final String transactionName;
+ @GuardedBy("this")
+ private final Map<ModuleIdentifier, ModuleInternalTransactionalInfo> commitMap = new HashMap<>();
+
+ @GuardedBy("this")
+ private final Set<ModuleInternalTransactionalInfo> unorderedDestroyedFromPreviousTransactions = new HashSet<>();
+
+ ModulesHolder(String transactionName) {
+ this.transactionName = transactionName;
+ }
+
+ @Override
+ public CommitInfo toCommitInfo() {
+ List<DestroyedModule> orderedDestroyedFromPreviousTransactions = new ArrayList<>(
+ unorderedDestroyedFromPreviousTransactions.size());
+ for (ModuleInternalTransactionalInfo toBeDestroyed : unorderedDestroyedFromPreviousTransactions) {
+ orderedDestroyedFromPreviousTransactions.add(toBeDestroyed
+ .toDestroyedModule());
+ }
+ Collections.sort(orderedDestroyedFromPreviousTransactions);
+ return new CommitInfo(orderedDestroyedFromPreviousTransactions,
+ commitMap);
+ }
+
+ private ModuleInternalTransactionalInfo findModuleInternalTransactionalInfo(
+ ModuleIdentifier moduleIdentifier,
+ JmxAttribute jmxAttributeForReporting) {
+ ModuleInternalTransactionalInfo moduleInternalTransactionalInfo = commitMap
+ .get(moduleIdentifier);
+ JmxAttributeValidationException.checkNotNull(
+ moduleInternalTransactionalInfo, "Module " + moduleIdentifier
+ + "" + " not found in transaction " + transactionName,
+ jmxAttributeForReporting);
+ return moduleInternalTransactionalInfo;
+ }
+
+ @Override
+ public Module findModule(ModuleIdentifier moduleIdentifier,
+ JmxAttribute jmxAttributeForReporting) {
+ return findModuleInternalTransactionalInfo(moduleIdentifier,
+ jmxAttributeForReporting).getModule();
+ }
+
+ @Override
+ public ModuleFactory findModuleFactory(ModuleIdentifier moduleIdentifier,
+ JmxAttribute jmxAttributeForReporting) {
+ return findModuleInternalTransactionalInfo(moduleIdentifier,
+ jmxAttributeForReporting).getModuleFactory();
+ }
+
+ @Override
+ public Map<ModuleIdentifier, Module> getAllModules() {
+ Map<ModuleIdentifier, Module> result = new HashMap<>();
+ for (ModuleInternalTransactionalInfo entry : commitMap.values()) {
+ ModuleIdentifier name = entry.getName();
+ result.put(name, entry.getModule());
+ }
+ return result;
+ }
+
+ @Override
+ public void put(
+ ModuleInternalTransactionalInfo moduleInternalTransactionalInfo) {
+ commitMap.put(moduleInternalTransactionalInfo.getName(),
+ moduleInternalTransactionalInfo);
+ }
+
+ @Override
+ public ModuleInternalTransactionalInfo destroyModule(
+ ModuleIdentifier moduleIdentifier) {
+ ModuleInternalTransactionalInfo found = commitMap
+ .remove(moduleIdentifier);
+ if (found == null)
+ throw new IllegalStateException("Not found:" + moduleIdentifier);
+ if (found.hasOldModule()) {
+ unorderedDestroyedFromPreviousTransactions.add(found);
+ }
+ return found;
+ }
+
+ @Override
+ public void assertNotExists(ModuleIdentifier moduleIdentifier)
+ throws InstanceAlreadyExistsException {
+ if (commitMap.containsKey(moduleIdentifier)) {
+ throw new InstanceAlreadyExistsException(
+ "There is an instance registered with name "
+ + moduleIdentifier);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl.dependencyresolver;
+
+import java.util.Map;
+
+import javax.management.InstanceAlreadyExistsException;
+
+import org.opendaylight.controller.config.api.JmxAttribute;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.manager.impl.CommitInfo;
+import org.opendaylight.controller.config.manager.impl.ModuleInternalTransactionalInfo;
+import org.opendaylight.controller.config.spi.Module;
+import org.opendaylight.controller.config.spi.ModuleFactory;
+
+interface TransactionHolder {
+ CommitInfo toCommitInfo();
+
+ Module findModule(ModuleIdentifier moduleIdentifier,
+ JmxAttribute jmxAttributeForReporting);
+
+ ModuleFactory findModuleFactory(ModuleIdentifier moduleIdentifier,
+ JmxAttribute jmxAttributeForReporting);
+
+ Map<ModuleIdentifier, Module> getAllModules();
+
+ void put(ModuleInternalTransactionalInfo moduleInternalTransactionalInfo);
+
+ ModuleInternalTransactionalInfo destroyModule(
+ ModuleIdentifier moduleIdentifier);
+
+ void assertNotExists(ModuleIdentifier moduleIdentifier)
+ throws InstanceAlreadyExistsException;
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl.dynamicmbean;
+
+import static java.lang.String.format;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.management.Attribute;
+import javax.management.AttributeList;
+import javax.management.AttributeNotFoundException;
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.InstanceNotFoundException;
+import javax.management.IntrospectionException;
+import javax.management.ListenerNotFoundException;
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanConstructorInfo;
+import javax.management.MBeanException;
+import javax.management.MBeanInfo;
+import javax.management.MBeanNotificationInfo;
+import javax.management.MBeanOperationInfo;
+import javax.management.MBeanRegistrationException;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerDelegate;
+import javax.management.MBeanServerNotification;
+import javax.management.NotCompliantMBeanException;
+import javax.management.Notification;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.management.ReflectionException;
+
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.annotations.Description;
+import org.opendaylight.controller.config.api.annotations.RequireInterface;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.manager.impl.util.InterfacesHelper;
+import org.opendaylight.controller.config.spi.Module;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Contains common code for readable/rw dynamic mbean wrappers. Routes all
+ * requests (getAttribute, setAttribute, invoke) into the actual instance, but
+ * provides additional functionality - namely it disallows setting attribute on
+ * a read only wrapper.
+ *
+ */
+abstract class AbstractDynamicWrapper implements DynamicMBeanModuleWrapper {
+ private static final Logger logger = LoggerFactory
+ .getLogger(AbstractDynamicWrapper.class);
+
+ protected final boolean writable;
+ protected final Module module;
+
+ private final MBeanInfo mbeanInfo;
+ protected final ObjectName objectNameInternal;
+ protected final Map<String, AttributeHolder> attributeHolderMap;
+ protected final ModuleIdentifier moduleIdentifier;
+ protected final MBeanServer internalServer;
+
+ public AbstractDynamicWrapper(Module module, boolean writable,
+ ModuleIdentifier moduleIdentifier,
+ ObjectName thisWrapperObjectName, MBeanOperationInfo[] dOperations,
+ MBeanServer internalServer, MBeanServer configMBeanServer) {
+
+ this.writable = writable;
+ this.module = module;
+ this.moduleIdentifier = moduleIdentifier;
+ this.internalServer = internalServer;
+ this.objectNameInternal = thisWrapperObjectName;
+ // register the actual instance into an mbean server.
+ registerActualModule(module, thisWrapperObjectName, objectNameInternal,
+ internalServer, configMBeanServer);
+ Set<Class<?>> jmxInterfaces = InterfacesHelper.getMXInterfaces(module
+ .getClass());
+ this.attributeHolderMap = buildMBeanInfo(module, writable,
+ moduleIdentifier, jmxInterfaces, internalServer,
+ objectNameInternal);
+ this.mbeanInfo = generateMBeanInfo(module.getClass().getName(), module,
+ attributeHolderMap, dOperations, jmxInterfaces);
+ }
+
+ /**
+ * Register module into an internal mbean server, attach listener to the
+ * platform mbean server. Wait until this wrapper gets unregistered, in that
+ * case unregister the module and remove listener.
+ */
+ private final NotificationListener registerActualModule(Module module,
+ final ObjectName thisWrapperObjectName,
+ final ObjectName objectNameInternal,
+ final MBeanServer internalServer,
+ final MBeanServer configMBeanServer) {
+
+ try {
+ internalServer.registerMBean(module, objectNameInternal);
+ } catch (InstanceAlreadyExistsException | MBeanRegistrationException
+ | NotCompliantMBeanException | IllegalStateException e) {
+ throw new IllegalStateException(
+ "Error occured during mbean registration ", e);
+ }
+
+ NotificationListener listener = new NotificationListener() {
+ @Override
+ public void handleNotification(Notification n, Object handback) {
+ if (n instanceof MBeanServerNotification
+ && n.getType()
+ .equals(MBeanServerNotification.UNREGISTRATION_NOTIFICATION)) {
+ if (((MBeanServerNotification) n).getMBeanName().equals(
+ thisWrapperObjectName)) {
+ try {
+ internalServer.unregisterMBean(objectNameInternal);
+ configMBeanServer.removeNotificationListener(
+ MBeanServerDelegate.DELEGATE_NAME, this);
+ } catch (MBeanRegistrationException
+ | ListenerNotFoundException
+ | InstanceNotFoundException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+ }
+ };
+ try {
+ configMBeanServer.addNotificationListener(
+ MBeanServerDelegate.DELEGATE_NAME, listener, null, null);
+ } catch (InstanceNotFoundException e) {
+ throw new RuntimeException("Could not add notification listener", e);
+ }
+ return listener;
+ }
+
+ private static MBeanInfo generateMBeanInfo(String className, Module module,
+ Map<String, AttributeHolder> attributeHolderMap,
+ MBeanOperationInfo[] dOperations, Set<Class<?>> jmxInterfaces) {
+
+ String dDescription = findDescription(module.getClass(), jmxInterfaces);
+ MBeanConstructorInfo[] dConstructors = new MBeanConstructorInfo[0];
+ List<MBeanAttributeInfo> attributes = new ArrayList<>(
+ attributeHolderMap.size());
+ for (AttributeHolder attributeHolder : attributeHolderMap.values()) {
+ attributes.add(attributeHolder.toMBeanAttributeInfo());
+ }
+ return new MBeanInfo(className, dDescription,
+ attributes.toArray(new MBeanAttributeInfo[0]), dConstructors,
+ dOperations, new MBeanNotificationInfo[0]);
+ }
+
+ static String findDescription(Class<?> clazz, Set<Class<?>> jmxInterfaces) {
+ List<Description> descriptions = AnnotationsHelper
+ .findClassAnnotationInSuperClassesAndIfcs(clazz, Description.class, jmxInterfaces);
+ return AnnotationsHelper.aggregateDescriptions(descriptions);
+ }
+
+ protected static MBeanOperationInfo[] getEmptyOperations() {
+ return new MBeanOperationInfo[0];
+ }
+
+ // inspect all exported interfaces ending with MXBean, extract getters &
+ // setters into attribute holder
+ private static Map<String, AttributeHolder> buildMBeanInfo(Module module,
+ boolean writable, ModuleIdentifier moduleIdentifier,
+ Set<Class<?>> jmxInterfaces, MBeanServer internalServer,
+ ObjectName internalObjectName) {
+
+ // internal variables for describing MBean elements
+ Set<Method> methods = new HashSet<>();
+
+ for (Class<?> exportedClass : jmxInterfaces) {
+ Method[] ifcMethods = exportedClass.getMethods();
+ methods.addAll(Arrays.asList(ifcMethods));
+ }
+ // TODO: fix reflection, not used
+ MBeanInfo internalInfo;
+ try {
+ internalInfo = internalServer.getMBeanInfo(internalObjectName);
+ } catch (InstanceNotFoundException | ReflectionException
+ | IntrospectionException e) {
+ throw new RuntimeException("MBean info not found", e);
+ }
+
+ Map<String, MBeanAttributeInfo> attributeMap = new HashMap<>();
+ for (MBeanAttributeInfo a : internalInfo.getAttributes()) {
+ attributeMap.put(a.getName(), a);
+ }
+ Map<String, AttributeHolder> attributeHolderMap = new HashMap<>();
+ for (Method method : methods) {
+
+ if (method.getParameterTypes().length == 1
+ && method.getName().startsWith("set")) {
+ Method setter;
+ String attribName = method.getName().substring(3);
+ try {
+ setter = module.getClass().getMethod(method.getName(),
+ method.getParameterTypes());
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException("No such method on "
+ + moduleIdentifier, e);
+ }
+ RequireInterface ifc = AttributeHolder
+ .findRequireInterfaceAnnotation(setter, jmxInterfaces);
+ String description = null;
+ if (ifc != null) {
+ description = AttributeHolder.findDescription(setter,
+ jmxInterfaces);
+ }
+ AttributeHolder attributeHolder = new AttributeHolder(
+ attribName, module, attributeMap.get(attribName)
+ .getType(), writable, ifc, description);
+ attributeHolderMap.put(attribName, attributeHolder);
+ }
+ }
+ return attributeHolderMap;
+ }
+
+ // DynamicMBean methods
+
+ @Override
+ public MBeanInfo getMBeanInfo() {
+ return mbeanInfo;
+ }
+
+ @Override
+ public Object getAttribute(String attributeName)
+ throws AttributeNotFoundException, MBeanException,
+ ReflectionException {
+ if (attributeName.equals("MBeanInfo")) {
+ return getMBeanInfo();
+ }
+
+ Object obj = null;
+ try {
+ obj = internalServer
+ .getAttribute(objectNameInternal, attributeName);
+ } catch (InstanceNotFoundException e) {
+ new MBeanException(e);
+ }
+ if (obj instanceof ObjectName) {
+ AttributeHolder attributeHolder = attributeHolderMap
+ .get(attributeName);
+ if (attributeHolder.getRequireInterfaceOrNull() != null) {
+ obj = fixObjectName((ObjectName) obj);
+ }
+ return obj;
+ }
+ return obj;
+
+ }
+
+ protected ObjectName fixObjectName(ObjectName on) {
+ if (!ObjectNameUtil.ON_DOMAIN.equals(on.getDomain()))
+ throw new IllegalArgumentException("Wrong domain, expected "
+ + ObjectNameUtil.ON_DOMAIN + " setter on " + on);
+ // if on contains transaction name, remove it
+ String transactionName = ObjectNameUtil.getTransactionName(on);
+ if (transactionName != null)
+ return ObjectNameUtil.withoutTransactionName(on);
+ else
+ return on;
+ }
+
+ @Override
+ public AttributeList getAttributes(String[] attributes) {
+ AttributeList result = new AttributeList();
+ for (String attributeName : attributes) {
+ try {
+ Object value = getAttribute(attributeName);
+ result.add(new Attribute(attributeName, value));
+
+ } catch (Exception e) {
+ logger.debug("Getting attribute {} failed", attributeName, e);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public Object invoke(String actionName, Object[] params, String[] signature)
+ throws MBeanException, ReflectionException {
+ if ("getAttribute".equals(actionName) && params.length == 1
+ && signature[0].equals(String.class.getName())) {
+ try {
+ return getAttribute((String) params[0]);
+ } catch (AttributeNotFoundException e) {
+ throw new MBeanException(e, "Attribute not found on "
+ + moduleIdentifier);
+ }
+ } else if ("getAttributes".equals(actionName) && params.length == 1
+ && signature[0].equals(String[].class.getName())) {
+ return getAttributes((String[]) params[0]);
+ } else if ("setAttributes".equals(actionName) && params.length == 1
+ && signature[0].equals(AttributeList.class.getName())) {
+ return setAttributes((AttributeList) params[0]);
+ } else {
+ logger.debug("Operation not found {} ", actionName);
+ throw new UnsupportedOperationException(
+ format("Operation not found on %s. Method invoke is only supported for getInstance and getAttribute(s) "
+ + "method, got actionName %s, params %s, signature %s ",
+ moduleIdentifier, actionName, params, signature));
+ }
+ }
+
+ @Override
+ public final int hashCode() {
+ return module.hashCode();
+ }
+
+ @Override
+ public final boolean equals(Object other) {
+ return module.equals(other);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl.dynamicmbean;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import org.opendaylight.controller.config.api.annotations.Description;
+
+class AnnotationsHelper {
+
+ /**
+ * Look for annotation specified by annotationType on method. First observe
+ * method's class, then its super classes, then all provided interfaces.
+ * Used for finding @Description and @RequireInterface
+ *
+ * @param <T>
+ * generic type of annotation
+ * @return list of found annotations
+ */
+ static <T extends Annotation> List<T> findMethodAnnotationInSuperClassesAndIfcs(
+ final Method setter, Class<T> annotationType,
+ Set<Class<?>> inspectedInterfaces) {
+ List<T> result = new ArrayList<T>();
+ Class<?> inspectedClass = setter.getDeclaringClass();
+ do {
+ try {
+ Method foundSetter = inspectedClass.getMethod(setter.getName(),
+ setter.getParameterTypes());
+ T annotation = foundSetter.getAnnotation(annotationType);
+ if (annotation != null) {
+ result.add(annotation);
+ }
+ // we need to go deeper
+ inspectedClass = inspectedClass.getSuperclass();
+ } catch (NoSuchMethodException e) {
+ inspectedClass = Object.class; // no need to go further
+ }
+ } while (inspectedClass.equals(Object.class) == false);
+ // inspect interfaces
+ for (Class<?> ifc : inspectedInterfaces) {
+ if (ifc.isInterface() == false) {
+ throw new IllegalArgumentException(ifc + " is not an interface");
+ }
+ try {
+ Method foundSetter = ifc.getMethod(setter.getName(),
+ setter.getParameterTypes());
+ T annotation = foundSetter.getAnnotation(annotationType);
+ if (annotation != null) {
+ result.add(annotation);
+ }
+ } catch (NoSuchMethodException e) {
+
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Look for annotation specified by annotationType on type. First observe
+ * class clazz, then its super classes, then all exported interfaces with
+ * their super types. Used for finding @Description of modules.
+ *
+ * @return list of found annotations
+ */
+ static <T extends Annotation> List<T> findClassAnnotationInSuperClassesAndIfcs(
+ Class<?> clazz, Class<T> annotationType, Set<Class<?>> interfaces) {
+ List<T> result = new ArrayList<T>();
+ Class<?> declaringClass = clazz;
+ do {
+ T annotation = declaringClass.getAnnotation(annotationType);
+ if (annotation != null) {
+ result.add(annotation);
+ }
+ declaringClass = declaringClass.getSuperclass();
+ } while (declaringClass.equals(Object.class) == false);
+ // inspect interfaces
+ for (Class<?> ifc : interfaces) {
+ if (ifc.isInterface() == false) {
+ throw new IllegalArgumentException(ifc + " is not an interface");
+ }
+ T annotation = ifc.getAnnotation(annotationType);
+ if (annotation != null) {
+ result.add(annotation);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @return empty string if no annotation is found, or list of descriptions
+ * separated by newline
+ */
+ static String aggregateDescriptions(List<Description> descriptions) {
+ StringBuilder builder = new StringBuilder();
+ for (Description d : descriptions) {
+ if (builder.length() != 0) {
+ builder.append("\n");
+ }
+ builder.append(d.value());
+
+ }
+ return builder.toString();
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl.dynamicmbean;
+
+import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+import javax.management.MBeanAttributeInfo;
+import javax.management.ObjectName;
+
+import org.opendaylight.controller.config.api.annotations.Description;
+import org.opendaylight.controller.config.api.annotations.RequireInterface;
+
+@Immutable
+class AttributeHolder {
+
+ private final String name;
+ private final String description;
+ private final Object object;
+ private final boolean writable;
+
+ @Nullable
+ private final RequireInterface requireInterfaceAnnotation;
+ private final String attributeType;
+
+ public AttributeHolder(String name, Object object, String returnType,
+ boolean writable,
+ @Nullable RequireInterface requireInterfaceAnnotation,
+ String description) {
+ if (name == null) {
+ throw new NullPointerException();
+ }
+ this.name = name;
+ if (object == null) {
+ throw new NullPointerException();
+ }
+ this.object = object;
+ this.writable = writable;
+ this.requireInterfaceAnnotation = requireInterfaceAnnotation;
+ this.attributeType = returnType;
+ this.description = description;
+ }
+
+ public MBeanAttributeInfo toMBeanAttributeInfo() {
+ MBeanAttributeInfo info = new MBeanAttributeInfo(name, attributeType,
+ description, true, true, false);
+ return info;
+ }
+
+ /**
+ * @return annotation if setter sets ObjectName or ObjectName[], and is
+ * annotated. Return null otherwise.
+ */
+ RequireInterface getRequireInterfaceOrNull() {
+ return requireInterfaceAnnotation;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Object getObject() {
+ return object;
+ }
+
+ public String getAttributeType() {
+ return attributeType;
+ }
+
+ public boolean isWritable() {
+ return writable;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * Find @Description annotations in method class and all its exported
+ * interfaces.
+ *
+ * @param setter
+ * @param jmxInterfaces
+ * @return empty string if no annotation is found, or list of descriptions
+ * separated by newline
+ */
+ static String findDescription(Method setter, Set<Class<?>> jmxInterfaces) {
+ List<Description> descriptions = AnnotationsHelper
+ .findMethodAnnotationInSuperClassesAndIfcs(setter, Description.class, jmxInterfaces);
+ return AnnotationsHelper.aggregateDescriptions(descriptions);
+ }
+
+ /**
+ * Find @RequireInterface annotation by searching method class and all
+ * exported interfaces.
+ *
+ * @param setter
+ * @param inspectedInterfaces
+ * @throws IllegalStateException
+ * if more than one value is specified by found annotations
+ * @throws IllegalArgumentException
+ * if set of exported interfaces contains non interface type
+ * @return null if no annotation is found, otherwise return the annotation
+ */
+ static RequireInterface findRequireInterfaceAnnotation(final Method setter,
+ Set<Class<?>> inspectedInterfaces) {
+
+ // only allow setX(ObjectName y) or setX(ObjectName[] y) to continue
+ if (setter.getParameterTypes().length != 1
+ || (setter.getParameterTypes()[0].equals(ObjectName.class) == false && setter
+ .getParameterTypes()[0].equals(ObjectName[].class) == false)) {
+ return null;
+ }
+
+ List<RequireInterface> foundRequireInterfaces = AnnotationsHelper
+ .findMethodAnnotationInSuperClassesAndIfcs(setter, RequireInterface.class, inspectedInterfaces);
+ // make sure the list if not empty contains always annotation with same
+ // value
+ Set<Class<?>> foundValues = new HashSet<Class<?>>();
+ for (RequireInterface ri : foundRequireInterfaces) {
+ foundValues.add(ri.value());
+ }
+ if (foundValues.size() == 0) {
+ return null;
+ } else if (foundValues.size() > 1) {
+ throw new IllegalStateException("Error finding @RequireInterface. "
+ + "More than one value specified as required interface "
+ + foundValues + " of " + setter + " of "
+ + setter.getDeclaringClass());
+ } else {
+ return foundRequireInterfaces.get(0);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl.dynamicmbean;
+
+import javax.management.DynamicMBean;
+
+/**
+ * Each {@link org.opendaylight.controller.config.spi.Module} in JMX registry
+ * will be wrapped in this class.
+ */
+public interface DynamicMBeanModuleWrapper extends DynamicMBean {
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl.dynamicmbean;
+
+import javax.management.Attribute;
+import javax.management.AttributeList;
+import javax.management.AttributeNotFoundException;
+import javax.management.InvalidAttributeValueException;
+import javax.management.MBeanException;
+import javax.management.MBeanServer;
+import javax.management.ReflectionException;
+
+import org.opendaylight.controller.config.api.DynamicMBeanWithInstance;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.spi.Module;
+
+/**
+ * Wraps {@link org.opendaylight.controller.config.spi.Module} in a
+ * {@link DynamicMBeanWithInstance}. Setting attributes is disabled.
+ */
+public class DynamicReadableWrapper extends AbstractDynamicWrapper implements
+ DynamicMBeanWithInstance {
+ private final AutoCloseable instance;
+
+ /**
+ * @param module
+ * @param instance
+ * for recreating Module.
+ *
+ */
+ public DynamicReadableWrapper(Module module, AutoCloseable instance,
+ ModuleIdentifier moduleIdentifier, MBeanServer internalServer,
+ MBeanServer configMBeanServer) {
+ super(module, false, moduleIdentifier, ObjectNameUtil
+ .createReadOnlyModuleON(moduleIdentifier),
+ getEmptyOperations(), internalServer, configMBeanServer);
+ this.instance = instance;
+ }
+
+ @Override
+ public Module getModule() {
+ return module;
+ }
+
+ @Override
+ public AutoCloseable getInstance() {
+ return instance;
+ }
+
+ @Override
+ public Object invoke(String actionName, Object[] params, String[] signature)
+ throws MBeanException, ReflectionException {
+ if ("getInstance".equals(actionName)
+ && (params == null || params.length == 0)
+ && (signature == null || signature.length == 0)) {
+ return getInstance();
+ }
+ return super.invoke(actionName, params, signature);
+ }
+
+ @Override
+ public Object getAttribute(String attributeName)
+ throws AttributeNotFoundException, MBeanException,
+ ReflectionException {
+ if (attributeName.equals("getInstance")) {
+ return getInstance();
+ }
+ return super.getAttribute(attributeName);
+ }
+
+ @Override
+ public void setAttribute(Attribute attribute)
+ throws AttributeNotFoundException, InvalidAttributeValueException,
+ MBeanException, ReflectionException {
+ throw new UnsupportedOperationException(
+ "setAttributes is not supported on " + moduleIdentifier);
+ }
+
+ @Override
+ public AttributeList setAttributes(AttributeList attributes) {
+ throw new UnsupportedOperationException(
+ "setAttributes is not supported on " + moduleIdentifier);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl.dynamicmbean;
+
+import java.lang.reflect.Method;
+
+import javax.annotation.concurrent.ThreadSafe;
+import javax.management.Attribute;
+import javax.management.AttributeList;
+import javax.management.AttributeNotFoundException;
+import javax.management.DynamicMBean;
+import javax.management.InstanceNotFoundException;
+import javax.management.InvalidAttributeValueException;
+import javax.management.MBeanException;
+import javax.management.MBeanOperationInfo;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import javax.management.ReflectionException;
+
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.ValidationException;
+import org.opendaylight.controller.config.api.annotations.RequireInterface;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.manager.impl.TransactionIdentifier;
+import org.opendaylight.controller.config.spi.Module;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Wraps {@link org.opendaylight.controller.config.spi.Module} instance in a
+ * {@link DynamicMBean} interface. Inspects dependency attributes, identified by
+ * ObjectName getter/setter and {@link RequireInterface} annotation. Used to
+ * simplify client calls - to set a dependency, only instance name is needed.
+ * This class creates new writable String attribute for each dependency with
+ * 'Name' suffix backed by the actual ObjectName attribute.
+ * <p>
+ * Thread safety - setting attributes is synchronized on 'this'. Synchronization
+ * of {@link org.opendaylight.controller.config.spi.Module#validate()} and
+ * {@link org.opendaylight.controller.config.spi.Module#getInstance()} is also
+ * guaranteed by
+ * {@link org.opendaylight.controller.config.manager.impl.ConfigTransactionControllerInternal}
+ * so the actual {@link org.opendaylight.controller.config.spi.Module} needs not
+ * to be thread safe.
+ * </p>
+ */
+@ThreadSafe
+public class DynamicWritableWrapper extends AbstractDynamicWrapper {
+ private static final Logger logger = LoggerFactory
+ .getLogger(DynamicWritableWrapper.class);
+
+ private final ReadOnlyAtomicBoolean configBeanModificationDisabled;
+
+ public DynamicWritableWrapper(Module module,
+ ModuleIdentifier moduleIdentifier,
+ TransactionIdentifier transactionIdentifier,
+ ReadOnlyAtomicBoolean configBeanModificationDisabled,
+ MBeanServer internalServer, MBeanServer configMBeanServer) {
+ super(module, true, moduleIdentifier, ObjectNameUtil
+ .createTransactionModuleON(transactionIdentifier.getName(), moduleIdentifier), getOperations(moduleIdentifier),
+ internalServer, configMBeanServer);
+ this.configBeanModificationDisabled = configBeanModificationDisabled;
+ }
+
+ private static MBeanOperationInfo[] getOperations(
+ ModuleIdentifier moduleIdentifier) {
+ Method validationMethod;
+ try {
+ validationMethod = DynamicWritableWrapper.class.getMethod(
+ "validate", new Class<?>[0]);
+ } catch (NoSuchMethodException e) {
+ throw new IllegalStateException("No such method exception on "
+ + moduleIdentifier, e);
+ }
+ return new MBeanOperationInfo[] { new MBeanOperationInfo("Validation",
+ validationMethod) };
+ }
+
+ @Override
+ public synchronized void setAttribute(Attribute attribute)
+ throws AttributeNotFoundException, InvalidAttributeValueException,
+ MBeanException, ReflectionException {
+ if (configBeanModificationDisabled.get() == true)
+ throw new IllegalStateException("Operation is not allowed now");
+
+ if (attribute.getName().equals("Attribute")) {
+ setAttribute((Attribute) attribute.getValue());
+ return;
+ }
+
+ try {
+ if (attribute.getValue() instanceof ObjectName) {
+ AttributeHolder attributeHolder = attributeHolderMap
+ .get(attribute.getName());
+ if (attributeHolder.getRequireInterfaceOrNull() != null) {
+ attribute = new Attribute(attribute.getName(),
+ fixObjectName((ObjectName) attribute.getValue()));
+ } else {
+ attribute = new Attribute(attribute.getName(),
+ attribute.getValue());
+ }
+ }
+ internalServer.setAttribute(objectNameInternal, attribute);
+ } catch (InstanceNotFoundException e) {
+ throw new MBeanException(e);
+ }
+
+ }
+
+ @Override
+ public AttributeList setAttributes(AttributeList attributes) {
+ AttributeList result = new AttributeList();
+ for (Object attributeObject : attributes) {
+ Attribute attribute = (Attribute) attributeObject;
+ try {
+ setAttribute(attribute);
+ result.add(attribute);
+ } catch (Exception e) {
+ logger.warn("Setting attribute {} failed on {}",
+ attribute.getName(), moduleIdentifier, e);
+ throw new IllegalArgumentException(
+ "Setting attribute failed - " + attribute.getName()
+ + " on " + moduleIdentifier, e);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public Object invoke(String actionName, Object[] params, String[] signature)
+ throws MBeanException, ReflectionException {
+ if ("validate".equals(actionName)
+ && (params == null || params.length == 0)
+ && (signature == null || signature.length == 0)) {
+ try {
+ validate();
+ } catch (Exception e) {
+ throw ValidationException.createForSingleException(
+ moduleIdentifier, e);
+ }
+ return Void.TYPE;
+ }
+ return super.invoke(actionName, params, signature);
+ }
+
+ public void validate() {
+ module.validate();
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl.dynamicmbean;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public interface ReadOnlyAtomicBoolean {
+ boolean get();
+
+ public static class ReadOnlyAtomicBooleanImpl implements
+ ReadOnlyAtomicBoolean {
+ private final AtomicBoolean atomicBoolean;
+
+ public ReadOnlyAtomicBooleanImpl(AtomicBoolean atomicBoolean) {
+ super();
+ this.atomicBoolean = atomicBoolean;
+ }
+
+ @Override
+ public boolean get() {
+ return atomicBoolean.get();
+ }
+
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl.factoriesresolver;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.opendaylight.controller.config.spi.ModuleFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Hold sorted ConfigMBeanFactories by their module names. Check that module
+ * names are globally unique.
+ */
+public class HierarchicalConfigMBeanFactoriesHolder {
+ private static final Logger logger = LoggerFactory
+ .getLogger(HierarchicalConfigMBeanFactoriesHolder.class);
+
+ private final Map<String, ModuleFactory> moduleNamesToConfigBeanFactories;
+ private final Set<String> moduleNames;
+
+ /**
+ * Create instance.
+ *
+ * @throws IllegalArgumentException
+ * if unique constraint on module names is violated
+ */
+ public HierarchicalConfigMBeanFactoriesHolder(
+ List<? extends ModuleFactory> list) {
+ Map<String, ModuleFactory> moduleNamesToConfigBeanFactories = new HashMap<>();
+ StringBuffer errors = new StringBuffer();
+ for (ModuleFactory factory : list) {
+ String moduleName = factory.getImplementationName();
+ if (moduleName == null || moduleName.isEmpty()) {
+ throw new IllegalStateException(
+ "Invalid implementation name for " + factory);
+ }
+ logger.debug("Reading factory {} {}", moduleName, factory);
+ String error = null;
+ ModuleFactory conflicting = moduleNamesToConfigBeanFactories
+ .get(moduleName);
+ if (conflicting != null) {
+ error = String
+ .format("Module name is not unique. Found two conflicting factories with same name '%s': " +
+ "\n\t%s\n\t%s\n", moduleName, conflicting, factory);
+
+ }
+
+ if (error == null) {
+ moduleNamesToConfigBeanFactories.put(moduleName, factory);
+ } else {
+ errors.append(error);
+ }
+
+ }
+ if (errors.length() > 0) {
+ throw new IllegalArgumentException(errors.toString());
+ }
+ this.moduleNamesToConfigBeanFactories = Collections
+ .unmodifiableMap(moduleNamesToConfigBeanFactories);
+ moduleNames = Collections.unmodifiableSet(new TreeSet<>(
+ moduleNamesToConfigBeanFactories.keySet()));
+ }
+
+ /**
+ * Get ModuleFactory by their name.
+ *
+ * @throws IllegalArgumentException
+ * if factory is not found
+ */
+ public ModuleFactory findByModuleName(String moduleName) {
+ ModuleFactory result = moduleNamesToConfigBeanFactories.get(moduleName);
+ if (result == null) {
+ throw new IllegalArgumentException(
+ "ModuleFactory not found with module name: " + moduleName);
+ }
+ return result;
+ }
+
+ public Set<String> getModuleNames() {
+ return moduleNames;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl.factoriesresolver;
+
+import java.util.List;
+
+import org.opendaylight.controller.config.spi.ModuleFactory;
+
+/**
+ * {@link org.opendaylight.controller.config.manager.impl.ConfigTransactionControllerImpl}
+ * receives list of factories using this interface. For testing, this could be
+ * implemented as hard coded list of objects, for OSGi this would look for all
+ * services in OSGi Service Registry are registered under
+ * {@link org.opendaylight.controller.config.spi.ModuleFactory} name.
+ */
+public interface ModuleFactoriesResolver {
+
+ List<? extends ModuleFactory> getAllFactories();
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl.jmx;
+
+import java.util.Set;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import javax.management.QueryExp;
+
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+
+public class BaseJMXRegistrator implements AutoCloseable {
+
+ private final InternalJMXRegistrator internalJMXRegistrator;
+
+ public BaseJMXRegistrator(MBeanServer configMBeanServer) {
+ internalJMXRegistrator = new InternalJMXRegistrator(configMBeanServer);
+ }
+
+ public BaseJMXRegistrator(InternalJMXRegistrator internalJMXRegistrator) {
+ this.internalJMXRegistrator = internalJMXRegistrator;
+ }
+
+ public TransactionJMXRegistrator createTransactionJMXRegistrator(
+ String transactionName) {
+ return new TransactionJMXRegistrator(
+ internalJMXRegistrator.createChild(), transactionName);
+ }
+
+ public ModuleJMXRegistrator createModuleJMXRegistrator() {
+ return new ModuleJMXRegistrator(internalJMXRegistrator.createChild());
+ }
+
+ public RootRuntimeBeanRegistratorImpl createRuntimeBeanRegistrator(
+ ModuleIdentifier moduleIdentifier) {
+ return new RootRuntimeBeanRegistratorImpl(internalJMXRegistrator,
+ moduleIdentifier);
+ }
+
+ public Set<ObjectName> queryNames(ObjectName name, QueryExp query) {
+ return internalJMXRegistrator.queryNames(name, query);
+ }
+
+ public Set<ObjectName> getRegisteredObjectNames() {
+ return internalJMXRegistrator.getRegisteredObjectNames();
+ }
+
+ @Override
+ public void close() {
+ internalJMXRegistrator.close();
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl.jmx;
+
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.MBeanServer;
+
+import org.opendaylight.controller.config.api.jmx.ConfigRegistryMXBean;
+import org.opendaylight.controller.config.manager.impl.ConfigRegistryImplMXBean;
+
+/**
+ * This registrator is used only to register Config Registry to JMX.
+ *
+ */
+public class ConfigRegistryJMXRegistrator implements AutoCloseable {
+ private final InternalJMXRegistrator internalJMXRegistrator;
+
+ public ConfigRegistryJMXRegistrator(MBeanServer configMBeanServer) {
+ internalJMXRegistrator = new InternalJMXRegistrator(configMBeanServer);
+ }
+
+ public AutoCloseable registerToJMX(ConfigRegistryImplMXBean configRegistry)
+ throws InstanceAlreadyExistsException {
+ return internalJMXRegistrator.registerMBean(configRegistry,
+ ConfigRegistryMXBean.OBJECT_NAME);
+ }
+
+ @Override
+ public void close() {
+ internalJMXRegistrator.close();
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl.jmx;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.ObjectName;
+
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.api.runtime.HierarchicalRuntimeBeanRegistration;
+import org.opendaylight.controller.config.api.runtime.RuntimeBean;
+
+public class HierarchicalRuntimeBeanRegistrationImpl implements
+ HierarchicalRuntimeBeanRegistration {
+ private final ModuleIdentifier moduleIdentifier;
+ private final InternalJMXRegistrator internalJMXRegistrator;
+ private final Map<String, String> properties;
+
+ public HierarchicalRuntimeBeanRegistrationImpl(
+ ModuleIdentifier moduleIdentifier,
+ InternalJMXRegistrator internalJMXRegistrator,
+ Map<String, String> properties) {
+ this.moduleIdentifier = moduleIdentifier;
+ this.internalJMXRegistrator = internalJMXRegistrator;
+ this.properties = properties;
+ }
+
+ @Override
+ public ObjectName getObjectName() {
+ return ObjectNameUtil.createRuntimeBeanName(
+ moduleIdentifier.getFactoryName(),
+ moduleIdentifier.getInstanceName(), properties);
+ }
+
+ @Override
+ public HierarchicalRuntimeBeanRegistrationImpl register(String key,
+ String value, RuntimeBean mxBean) {
+ Map<String, String> currentProperties = new HashMap<>(properties);
+ currentProperties.put(key, value);
+ ObjectName on = ObjectNameUtil.createRuntimeBeanName(
+ moduleIdentifier.getFactoryName(),
+ moduleIdentifier.getInstanceName(), currentProperties);
+ InternalJMXRegistrator child = internalJMXRegistrator.createChild();
+ try {
+ child.registerMBean(mxBean, on);
+ } catch (InstanceAlreadyExistsException e) {
+ throw RootRuntimeBeanRegistratorImpl.sanitize(e, moduleIdentifier,
+ on);
+ }
+ return new HierarchicalRuntimeBeanRegistrationImpl(moduleIdentifier,
+ child, currentProperties);
+ }
+
+ @Override
+ public void close() {
+ internalJMXRegistrator.close();
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl.jmx;
+
+import java.io.Closeable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.concurrent.GuardedBy;
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.JMX;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import javax.management.QueryExp;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class InternalJMXRegistrator implements Closeable {
+ private static final Logger logger = LoggerFactory
+ .getLogger(InternalJMXRegistrator.class);
+ private final MBeanServer configMBeanServer;
+
+ public InternalJMXRegistrator(MBeanServer configMBeanServer) {
+ this.configMBeanServer = configMBeanServer;
+ }
+
+ static class InternalJMXRegistration implements AutoCloseable {
+ private final InternalJMXRegistrator internalJMXRegistrator;
+ private final ObjectName on;
+
+ InternalJMXRegistration(InternalJMXRegistrator internalJMXRegistrator,
+ ObjectName on) {
+ this.internalJMXRegistrator = internalJMXRegistrator;
+ this.on = on;
+ }
+
+ @Override
+ public void close() {
+ internalJMXRegistrator.unregisterMBean(on);
+ }
+ }
+
+ @GuardedBy("this")
+ private final Set<ObjectName> registeredObjectNames = new HashSet<>();
+ private final List<InternalJMXRegistrator> children = new ArrayList<>();
+
+ public synchronized InternalJMXRegistration registerMBean(Object object,
+ ObjectName on) throws InstanceAlreadyExistsException {
+ try {
+ configMBeanServer.registerMBean(object, on);
+ } catch (InstanceAlreadyExistsException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ registeredObjectNames.add(on);
+ return new InternalJMXRegistration(this, on);
+ }
+
+ private synchronized void unregisterMBean(ObjectName on) {
+ // first check that on was registered using this instance
+ boolean removed = registeredObjectNames.remove(on);
+ if (!removed)
+ throw new IllegalStateException(
+ "Cannot unregister - ObjectName not found in 'registeredObjectNames': "
+ + on);
+ try {
+ configMBeanServer.unregisterMBean(on);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public InternalJMXRegistrator createChild() {
+ InternalJMXRegistrator child = new InternalJMXRegistrator(
+ configMBeanServer);
+ children.add(child);
+ return child;
+ }
+
+ /**
+ * Allow close to be called multiple times.
+ */
+ @Override
+ public synchronized void close() {
+ // close children
+ for (InternalJMXRegistrator child : children) {
+ child.close();
+ }
+ // close registered ONs
+ for (ObjectName on : registeredObjectNames) {
+ try {
+ configMBeanServer.unregisterMBean(on);
+ } catch (Exception e) {
+ logger.warn("Ignoring error while unregistering {}", on, e);
+ }
+ }
+ registeredObjectNames.clear();
+ }
+
+ public <T> T newMBeanProxy(ObjectName objectName, Class<T> interfaceClass) {
+ return JMX.newMBeanProxy(configMBeanServer, objectName, interfaceClass);
+ }
+
+ public <T> T newMBeanProxy(ObjectName objectName, Class<T> interfaceClass,
+ boolean notificationBroadcaster) {
+ return JMX.newMBeanProxy(configMBeanServer, objectName, interfaceClass,
+ notificationBroadcaster);
+ }
+
+ public <T> T newMXBeanProxy(ObjectName objectName, Class<T> interfaceClass) {
+ return JMX
+ .newMXBeanProxy(configMBeanServer, objectName, interfaceClass);
+ }
+
+ public <T> T newMXBeanProxy(ObjectName objectName, Class<T> interfaceClass,
+ boolean notificationBroadcaster) {
+ return JMX.newMXBeanProxy(configMBeanServer, objectName,
+ interfaceClass, notificationBroadcaster);
+ }
+
+ public Set<ObjectName> getRegisteredObjectNames() {
+ return Collections.unmodifiableSet(registeredObjectNames);
+ }
+
+ public Set<ObjectName> queryNames(ObjectName name, QueryExp query) {
+ Set<ObjectName> result = configMBeanServer.queryNames(name, query);
+ // keep only those that were registered using this instance
+ return getSameNames(result);
+ }
+
+ private Set<ObjectName> getSameNames(Set<ObjectName> superSet) {
+ Set<ObjectName> result = new HashSet<>(superSet);
+ result.retainAll(registeredObjectNames);
+ for (InternalJMXRegistrator child : children) {
+ result.addAll(child.getSameNames(superSet));
+ }
+ return result;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl.jmx;
+
+import java.io.Closeable;
+
+import javax.annotation.concurrent.ThreadSafe;
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.ObjectName;
+
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.manager.impl.jmx.InternalJMXRegistrator.InternalJMXRegistration;
+
+/**
+ * This subclass is used for registering readable module into JMX, it is also
+ * used as underlying provider in {@link RuntimeBeanRegistratorImpl}. Closing
+ * the instance thus unregisters all JMX beans related to the module excluding
+ * currently open transactions.
+ */
+@ThreadSafe
+public class ModuleJMXRegistrator implements Closeable {
+ private final InternalJMXRegistrator childJMXRegistrator;
+
+ public ModuleJMXRegistrator(InternalJMXRegistrator internalJMXRegistrator) {
+ this.childJMXRegistrator = internalJMXRegistrator.createChild();
+ }
+
+ static class ModuleJMXRegistration implements AutoCloseable {
+ private final InternalJMXRegistration internalJMXRegistration;
+
+ ModuleJMXRegistration(InternalJMXRegistration registration) {
+ this.internalJMXRegistration = registration;
+ }
+
+ @Override
+ public void close() {
+ internalJMXRegistration.close();
+ }
+ }
+
+ public ModuleJMXRegistration registerMBean(Object object, ObjectName on)
+ throws InstanceAlreadyExistsException {
+ ObjectNameUtil.checkType(on, ObjectNameUtil.TYPE_MODULE);
+ if (ObjectNameUtil.getTransactionName(on) != null)
+ throw new IllegalArgumentException(
+ "Transaction name not expected in " + on);
+ return new ModuleJMXRegistration(childJMXRegistrator.registerMBean(
+ object, on));
+ }
+
+ @Override
+ public void close() {
+ childJMXRegistrator.close();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl.jmx;
+
+import java.util.Collections;
+
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.ObjectName;
+
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.api.runtime.RootRuntimeBeanRegistrator;
+import org.opendaylight.controller.config.api.runtime.RuntimeBean;
+
+public class RootRuntimeBeanRegistratorImpl implements
+ RootRuntimeBeanRegistrator {
+ private final InternalJMXRegistrator internalJMXRegistrator;
+ private final ModuleIdentifier moduleIdentifier;
+ private final ObjectName defaultRuntimeON;
+
+ public RootRuntimeBeanRegistratorImpl(
+ InternalJMXRegistrator internalJMXRegistrator,
+ ModuleIdentifier moduleIdentifier) {
+ this.internalJMXRegistrator = internalJMXRegistrator;
+ this.moduleIdentifier = moduleIdentifier;
+ defaultRuntimeON = ObjectNameUtil.createRuntimeBeanName(
+ moduleIdentifier.getFactoryName(),
+ moduleIdentifier.getInstanceName(),
+ Collections.<String, String> emptyMap());
+ }
+
+ @Override
+ public HierarchicalRuntimeBeanRegistrationImpl registerRoot(
+ RuntimeBean mxBean) {
+ try {
+ internalJMXRegistrator.registerMBean(mxBean, defaultRuntimeON);
+ } catch (InstanceAlreadyExistsException e) {
+ throw sanitize(e, moduleIdentifier, defaultRuntimeON);
+ }
+ return new HierarchicalRuntimeBeanRegistrationImpl(moduleIdentifier,
+ internalJMXRegistrator, Collections.<String, String> emptyMap());
+ }
+
+ @Override
+ public void close() {
+ internalJMXRegistrator.close();
+ }
+
+ static IllegalStateException sanitize(InstanceAlreadyExistsException e,
+ ModuleIdentifier moduleIdentifier, ObjectName on) {
+ throw new IllegalStateException("Could not register runtime bean in "
+ + moduleIdentifier + " under name " + on, e);
+
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl.jmx;
+
+import java.io.Closeable;
+import java.util.Set;
+
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.ObjectName;
+import javax.management.QueryExp;
+
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.manager.impl.jmx.InternalJMXRegistrator.InternalJMXRegistration;
+
+/**
+ * Contains constraints on passed {@link ObjectName} parameters. Only allow (un)
+ * registration of ObjectNames that have expected transaction name.
+ */
+public class TransactionJMXRegistrator implements Closeable {
+ private final InternalJMXRegistrator childJMXRegistrator;
+ private final String transactionName;
+
+ TransactionJMXRegistrator(InternalJMXRegistrator internalJMXRegistrator,
+ String transactionName) {
+ this.childJMXRegistrator = internalJMXRegistrator.createChild();
+ this.transactionName = transactionName;
+ }
+
+ public static class TransactionJMXRegistration implements AutoCloseable {
+ private final InternalJMXRegistration registration;
+
+ TransactionJMXRegistration(InternalJMXRegistration registration) {
+ this.registration = registration;
+ }
+
+ @Override
+ public void close() {
+ registration.close();
+ }
+ }
+
+ public TransactionJMXRegistration registerMBean(Object object, ObjectName on)
+ throws InstanceAlreadyExistsException {
+ if (!transactionName.equals(ObjectNameUtil.getTransactionName(on)))
+ throw new IllegalArgumentException(
+ "Transaction name mismatch between expected "
+ + transactionName + " " + "and " + on);
+ ObjectNameUtil.checkType(on, ObjectNameUtil.TYPE_CONFIG_TRANSACTION);
+ return new TransactionJMXRegistration(
+ childJMXRegistrator.registerMBean(object, on));
+ }
+
+ public Set<ObjectName> queryNames(ObjectName name, QueryExp query) {
+ return childJMXRegistrator.queryNames(name, query);
+ }
+
+ public TransactionModuleJMXRegistrator createTransactionModuleJMXRegistrator() {
+ return new TransactionModuleJMXRegistrator(childJMXRegistrator,
+ transactionName);
+ }
+
+ @Override
+ public void close() {
+ childJMXRegistrator.close();
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl.jmx;
+
+import java.io.Closeable;
+import java.util.Set;
+
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.ObjectName;
+import javax.management.QueryExp;
+
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.manager.impl.jmx.InternalJMXRegistrator.InternalJMXRegistration;
+
+public class TransactionModuleJMXRegistrator implements Closeable {
+ private final InternalJMXRegistrator childJMXRegistrator;
+ private final String transactionName;
+
+ public TransactionModuleJMXRegistrator(
+ InternalJMXRegistrator internalJMXRegistrator,
+ String transactionName) {
+ this.childJMXRegistrator = internalJMXRegistrator.createChild();
+ this.transactionName = transactionName;
+ }
+
+ public static class TransactionModuleJMXRegistration implements
+ AutoCloseable {
+ private final InternalJMXRegistration registration;
+
+ TransactionModuleJMXRegistration(InternalJMXRegistration registration) {
+ this.registration = registration;
+ }
+
+ @Override
+ public void close() {
+ registration.close();
+ }
+ }
+
+ public TransactionModuleJMXRegistration registerMBean(Object object,
+ ObjectName on) throws InstanceAlreadyExistsException {
+ if (!transactionName.equals(ObjectNameUtil.getTransactionName(on)))
+ throw new IllegalArgumentException(
+ "Transaction name mismatch between expected "
+ + transactionName + " " + "and " + on);
+ ObjectNameUtil.checkType(on, ObjectNameUtil.TYPE_MODULE);
+ return new TransactionModuleJMXRegistration(
+ childJMXRegistrator.registerMBean(object, on));
+ }
+
+ public Set<ObjectName> queryNames(ObjectName name, QueryExp query) {
+ return childJMXRegistrator.queryNames(name, query);
+ }
+
+ @Override
+ public void close() {
+ childJMXRegistrator.close();
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl.osgi;
+
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Set;
+
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.opendaylight.controller.config.api.annotations.ServiceInterfaceAnnotation;
+import org.opendaylight.controller.config.manager.impl.util.InterfacesHelper;
+import org.opendaylight.controller.config.spi.Module;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * Registers instantiated beans as OSGi services and unregisters these services
+ * if beans are destroyed.
+ */
+public class BeanToOsgiServiceManager {
+ // name of properties submitted to osgi
+ static final String INSTANCE_NAME_OSGI_PROP = "instanceName";
+ static final String IMPLEMENTATION_NAME_OSGI_PROP = "implementationName";
+
+ private final BundleContext bundleContext;
+
+ public BeanToOsgiServiceManager(BundleContext context) {
+ this.bundleContext = context;
+ }
+
+ /**
+ * To be called for every created, reconfigured and recreated config bean.
+ * It is expected that before using this method OSGi service registry will
+ * be cleaned from previous registrations.
+ */
+ public OsgiRegistration registerToOsgi(
+ Class<? extends Module> configBeanClass, AutoCloseable instance,
+ ModuleIdentifier moduleIdentifier) {
+ try {
+ final Set<Class<?>> configuresInterfaces = InterfacesHelper
+ .getOsgiRegistrationTypes(configBeanClass);
+ checkInstanceImplementing(instance, configuresInterfaces);
+
+ // bundleContext.registerService blows up with empty 'clazzes'
+ if (configuresInterfaces.isEmpty() == false) {
+ final Dictionary<String, ?> propertiesForOsgi = getPropertiesForOsgi(moduleIdentifier);
+ final ServiceRegistration<?> serviceRegistration = bundleContext
+ .registerService(classesToNames(configuresInterfaces), instance, propertiesForOsgi);
+ return new OsgiRegistration(serviceRegistration);
+ } else {
+ return new OsgiRegistration();
+ }
+ } catch (IllegalStateException e) {
+ throw new IllegalStateException(
+ "Error while registering instance into OSGi Service Registry: "
+ + moduleIdentifier, e);
+ }
+ }
+
+ private static String[] classesToNames(Set<Class<?>> cfgs) {
+ String[] result = new String[cfgs.size()];
+ int i = 0;
+ for (Class<?> cfg : cfgs) {
+ result[i] = cfg.getName();
+ i++;
+ }
+ return result;
+ }
+
+ private void checkInstanceImplementing(AutoCloseable instance,
+ Set<Class<?>> configures) {
+ Set<Class<?>> missing = new HashSet<>();
+ for (Class<?> requiredIfc : configures) {
+ if (requiredIfc.isInstance(instance) == false) {
+ missing.add(requiredIfc);
+ }
+ }
+ if (missing.isEmpty() == false) {
+ throw new IllegalStateException(
+ instance.getClass()
+ + " does not implement following interfaces as announced by "
+ + ServiceInterfaceAnnotation.class.getName()
+ + " annotation :" + missing);
+ }
+ }
+
+ private static Dictionary<String, ?> getPropertiesForOsgi(
+ ModuleIdentifier moduleIdentifier) {
+ Hashtable<String, String> table = new Hashtable<>();
+ table.put(IMPLEMENTATION_NAME_OSGI_PROP,
+ moduleIdentifier.getFactoryName());
+ table.put(INSTANCE_NAME_OSGI_PROP, moduleIdentifier.getInstanceName());
+ return table;
+ }
+
+ public static class OsgiRegistration implements AutoCloseable {
+ private final ServiceRegistration<?> serviceRegistration;
+
+ public OsgiRegistration(ServiceRegistration<?> serviceRegistration) {
+ this.serviceRegistration = serviceRegistration;
+ }
+
+ public OsgiRegistration() {
+ this.serviceRegistration = null;
+ }
+
+ @Override
+ public void close() {
+ if (serviceRegistration != null) {
+ serviceRegistration.unregister();
+ }
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl.osgi;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.opendaylight.controller.config.manager.impl.factoriesresolver.ModuleFactoriesResolver;
+import org.opendaylight.controller.config.spi.ModuleFactory;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * Retrieves list of currently registered Module Factories using bundlecontext.
+ */
+public class BundleContextBackedModuleFactoriesResolver implements
+ ModuleFactoriesResolver {
+ private final BundleContext bundleContext;
+
+ public BundleContextBackedModuleFactoriesResolver(
+ BundleContext bundleContext) {
+ this.bundleContext = bundleContext;
+ }
+
+ @Override
+ public List<? extends ModuleFactory> getAllFactories() {
+ Collection<ServiceReference<ModuleFactory>> serviceReferences;
+ try {
+ serviceReferences = bundleContext.getServiceReferences(
+ ModuleFactory.class, null);
+ } catch (InvalidSyntaxException e) {
+ throw new IllegalStateException(e);
+ }
+ List<ModuleFactory> result = new ArrayList<>(serviceReferences.size());
+ for (ServiceReference<ModuleFactory> serviceReference : serviceReferences) {
+ ModuleFactory service = bundleContext.getService(serviceReference);
+ // null if the service is not registered, the service object
+ // returned by a ServiceFactory does not
+ // implement the classes under which it was registered or the
+ // ServiceFactory threw an exception.
+ if (service != null) {
+ result.add(service);
+ }
+ }
+ return result;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl.osgi;
+
+import java.lang.management.ManagementFactory;
+
+import javax.management.MBeanServer;
+
+import org.opendaylight.controller.config.manager.impl.ConfigRegistryImpl;
+import org.opendaylight.controller.config.manager.impl.jmx.ConfigRegistryJMXRegistrator;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ConfigManagerActivator implements BundleActivator {
+ private static final Logger logger = LoggerFactory
+ .getLogger(ConfigManagerActivator.class);
+
+ private ExtenderBundleTracker extenderBundleTracker;
+ private ConfigRegistryImpl configRegistry;
+ private ConfigRegistryJMXRegistrator configRegistryJMXRegistrator;
+
+ @Override
+ public void start(BundleContext context) throws Exception {
+ extenderBundleTracker = new ExtenderBundleTracker(context);
+ extenderBundleTracker.open();
+ BundleContextBackedModuleFactoriesResolver bundleContextBackedModuleFactoriesResolver = new BundleContextBackedModuleFactoriesResolver(
+ context);
+
+ MBeanServer configMBeanServer = ManagementFactory
+ .getPlatformMBeanServer();
+ configRegistry = new ConfigRegistryImpl(
+ bundleContextBackedModuleFactoriesResolver, context,
+ configMBeanServer);
+ // register config registry to jmx
+
+ configRegistryJMXRegistrator = new ConfigRegistryJMXRegistrator(
+ configMBeanServer);
+ configRegistryJMXRegistrator.registerToJMX(configRegistry);
+ }
+
+ @Override
+ public void stop(BundleContext context) throws Exception {
+ try {
+ configRegistry.close();
+ } catch (Exception e) {
+ logger.warn("Exception while closing config registry", e);
+ }
+ try {
+ extenderBundleTracker.close();
+ } catch (Exception e) {
+ logger.warn("Exception while closing extender", e);
+ }
+ try {
+ configRegistryJMXRegistrator.close();
+ } catch (Exception e) {
+ logger.warn(
+ "Exception while closing config registry jmx registrator",
+ e);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl.osgi;
+
+import static java.lang.String.format;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.util.List;
+
+import org.apache.commons.io.IOUtils;
+import org.opendaylight.controller.config.spi.ModuleFactory;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.util.tracker.BundleTracker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * OSGi extender that listens for bundle activation events. Reads file
+ * META-INF/services/org.opendaylight.controller.config.spi.ModuleFactory, each
+ * line should contain an implementation of ModuleFactory interface. Creates new
+ * instance with default constructor and registers it into OSGi service
+ * registry. There is no need for listening for implementing removedBundle as
+ * the services are unregistered automatically. Code based on
+ * http://www.toedter.com/blog/?p=236
+ */
+
+public class ExtenderBundleTracker extends BundleTracker<Object> {
+
+ private static final Logger logger = LoggerFactory
+ .getLogger(ExtenderBundleTracker.class);
+
+ public ExtenderBundleTracker(BundleContext context) {
+ super(context, Bundle.ACTIVE, null);
+ logger.trace("Registered as extender with context {}", context);
+ }
+
+ @Override
+ public Object addingBundle(Bundle bundle, BundleEvent event) {
+ URL resource = bundle.getEntry("META-INF/services/"
+ + ModuleFactory.class.getName());
+ logger.trace(
+ "Got addingBundle event of bundle {}, resource {}, event {}",
+ bundle, resource, event);
+ if (resource != null) {
+ try (InputStream inputStream = resource.openStream()) {
+ List<String> lines = IOUtils.readLines(inputStream);
+ for (String factoryClassName : lines) {
+ registerFactory(factoryClassName, bundle);
+ }
+ } catch (Exception e) {
+ logger.error("Error while reading {}, stopping bundle {}",
+ resource, bundle, e);
+ stopBundleQuietly(bundle);
+ throw new RuntimeException(e);
+ }
+
+ }
+ return bundle;
+ }
+
+ private static void stopBundleQuietly(Bundle bundle) {
+ try {
+ bundle.stop();
+ } catch (BundleException e2) {
+ logger.warn(
+ "Ignoring fact that bundle.stop failed on {}, reason {}",
+ bundle, e2.toString());
+ }
+ }
+
+ // TODO:test
+ private static ServiceRegistration<?> registerFactory(
+ String factoryClassName, Bundle bundle) {
+ String errorMessage;
+ try {
+ Class<?> clazz = bundle.loadClass(factoryClassName);
+ if (ModuleFactory.class.isAssignableFrom(clazz)) {
+ try {
+ logger.debug("Registering {} in bundle {}",
+ clazz.getName(), bundle);
+ return bundle.getBundleContext().registerService(
+ ModuleFactory.class.getName(), clazz.newInstance(),
+ null);
+ } catch (InstantiationException e) {
+ errorMessage = logMessage(
+ "Could not instantiate {} in bundle {}, reason {}",
+ factoryClassName, bundle, e);
+ } catch (IllegalAccessException e) {
+ errorMessage = logMessage(
+ "Illegal access during instatiation of class {} in bundle {}, reason {}",
+ factoryClassName, bundle, e);
+ }
+ } else {
+ errorMessage = logMessage(
+ "Class {} does not implement {} in bundle {}", clazz,
+ ModuleFactory.class, bundle);
+ }
+ } catch (ClassNotFoundException e) {
+ errorMessage = logMessage(
+ "Could not find class {} in bunde {}, reason {}",
+ factoryClassName, bundle, e);
+ }
+ throw new IllegalStateException(errorMessage);
+ }
+
+ public static String logMessage(String slfMessage, Object... params) {
+ logger.info(slfMessage, params);
+ String formatMessage = slfMessage.replaceAll("\\{\\}", "%s");
+ return format(formatMessage, params);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl.util;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import javax.management.JMX;
+
+import org.opendaylight.controller.config.api.annotations.AbstractServiceInterface;
+import org.opendaylight.controller.config.api.annotations.ServiceInterfaceAnnotation;
+import org.opendaylight.controller.config.spi.Module;
+
+public class InterfacesHelper {
+
+ public static Set<Class<?>> getAllInterfaces(Class<?> clazz) {
+ if (clazz.isInterface()) {
+ throw new IllegalArgumentException(clazz
+ + " should not be an interface");
+ }
+ // getInterfaces gets interfaces implemented directly by this class
+ Set<Class<?>> toBeInspected = new HashSet<>();
+ while (clazz.equals(Object.class) == false) {
+ toBeInspected.addAll(Arrays.asList(clazz.getInterfaces()));
+ // get parent class
+ clazz = clazz.getSuperclass();
+ }
+ // each interface can extend other interfaces
+ Set<Class<?>> inspected = new HashSet<>();
+ while (toBeInspected.size() > 0) {
+ Iterator<Class<?>> iterator = toBeInspected.iterator();
+ Class<?> ifc = iterator.next();
+ iterator.remove();
+ toBeInspected.addAll(Arrays.asList(ifc.getInterfaces()));
+ inspected.add(ifc);
+ }
+ return inspected;
+ }
+
+ /**
+ * Get interfaces that this class is derived from that are JMX interfaces.
+ */
+ public static Set<Class<?>> getMXInterfaces(
+ Class<? extends Module> configBeanClass) {
+ Set<Class<?>> allInterfaces = getAllInterfaces(configBeanClass);
+ Set<Class<?>> result = new HashSet<>();
+ for (Class<?> clazz : allInterfaces) {
+ if (JMX.isMXBeanInterface(clazz)) {
+ result.add(clazz);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Get all implemented interfaces that have
+ * {@link org.opendaylight.controller.config.api.annotations.ServiceInterfaceAnnotation}
+ * annotation.
+ */
+ public static Set<Class<?>> getServiceInterfaces(
+ Class<? extends Module> configBeanClass) {
+ Set<Class<?>> allInterfaces = getAllInterfaces(configBeanClass);
+ Set<Class<?>> result = new HashSet<>();
+ for (Class<?> clazz : allInterfaces) {
+ if (AbstractServiceInterface.class.isAssignableFrom(clazz)) {
+ ServiceInterfaceAnnotation annotation = clazz
+ .getAnnotation(ServiceInterfaceAnnotation.class);
+ if (annotation != null) {
+ result.add(clazz);
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Get OSGi registration types under which config bean instance should be
+ * registered. This is specified in
+ * {@link org.opendaylight.controller.config.api.annotations.ServiceInterfaceAnnotation#osgiRegistrationType()}
+ */
+ public static Set<Class<?>> getOsgiRegistrationTypes(
+ Class<? extends Module> configBeanClass) {
+ Set<Class<?>> serviceInterfaces = getServiceInterfaces(configBeanClass);
+ Set<Class<?>> result = new HashSet<>();
+ for (Class<?> clazz : serviceInterfaces) {
+ ServiceInterfaceAnnotation annotation = clazz
+ .getAnnotation(ServiceInterfaceAnnotation.class);
+ result.add(annotation.osgiRegistrationType());
+ }
+ return result;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.manager.impl.util;
+
+import java.util.Set;
+
+import javax.management.InstanceNotFoundException;
+import javax.management.ObjectName;
+
+import org.opendaylight.controller.config.api.LookupRegistry;
+
+public class LookupBeansUtil {
+
+ public static ObjectName lookupConfigBean(LookupRegistry lookupRegistry,
+ String moduleName, String instanceName)
+ throws InstanceNotFoundException {
+ Set<ObjectName> objectNames = lookupRegistry.lookupConfigBeans(
+ moduleName, instanceName);
+ if (objectNames.size() == 0) {
+ throw new InstanceNotFoundException("No instance found");
+ } else if (objectNames.size() > 1) {
+ throw new InstanceNotFoundException("Too many instances found");
+ }
+ return objectNames.iterator().next();
+ }
+
+}