aboutsummaryrefslogtreecommitdiffstats
path: root/utils/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'utils/src/main')
-rw-r--r--utils/src/main/java/org/onap/policy/common/utils/network/NetworkUtil.java83
-rw-r--r--utils/src/main/java/org/onap/policy/common/utils/properties/BeanConfigurator.java3
-rw-r--r--utils/src/main/java/org/onap/policy/common/utils/properties/PropertyConfiguration.java498
-rw-r--r--utils/src/main/java/org/onap/policy/common/utils/services/ServiceManager.java190
-rw-r--r--utils/src/main/java/org/onap/policy/common/utils/services/ServiceManagerException.java44
5 files changed, 315 insertions, 503 deletions
diff --git a/utils/src/main/java/org/onap/policy/common/utils/network/NetworkUtil.java b/utils/src/main/java/org/onap/policy/common/utils/network/NetworkUtil.java
index ae70ba44..3976c7a2 100644
--- a/utils/src/main/java/org/onap/policy/common/utils/network/NetworkUtil.java
+++ b/utils/src/main/java/org/onap/policy/common/utils/network/NetworkUtil.java
@@ -1,8 +1,8 @@
-/*-
+/*
* ============LICENSE_START=======================================================
* ONAP
* ================================================================================
- * Copyright (C) 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * Copyright (C) 2017-2019 AT&T Intellectual Property. All rights reserved.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,9 +23,13 @@ package org.onap.policy.common.utils.network;
import java.io.IOException;
import java.net.ConnectException;
import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
-
+import java.security.cert.X509Certificate;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -41,11 +45,84 @@ public class NetworkUtil {
*/
public static final String IPv4_WILDCARD_ADDRESS = "0.0.0.0";
+
+ /**
+ * A trust manager that always trusts certificates.
+ */
+ // @formatter:off
+ private static final TrustManager[] ALWAYS_TRUST_MANAGER = new TrustManager[] {
+ new X509TrustManager() {
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return new X509Certificate[0];
+ }
+
+ @Override
+ public void checkClientTrusted(final java.security.cert.X509Certificate[] certs,
+ final String authType) {
+ // always trust
+ }
+
+ @Override
+ public void checkServerTrusted(final java.security.cert.X509Certificate[] certs,
+ final String authType) {
+ // always trust
+ }
+ }
+ };
+ // @formatter:on
+
private NetworkUtil() {
// Empty constructor
}
/**
+ * Allocates an available port on which a server may listen.
+ *
+ * @return an available port
+ * @throws IOException if a socket cannot be created
+ */
+ public static int allocPort() throws IOException {
+ return allocPort((InetSocketAddress) null);
+ }
+
+ /**
+ * Allocates an available port on which a server may listen.
+ *
+ * @param hostName the server's host name
+ * @return an available port
+ * @throws IOException if a socket cannot be created
+ */
+ public static int allocPort(String hostName) throws IOException {
+ return allocPort(new InetSocketAddress(hostName, 0));
+ }
+
+ /**
+ * Allocates an available port on which a server may listen.
+ *
+ * @param hostAddr the server's host address on which to listen
+ * @return an available port
+ * @throws IOException if a socket cannot be created
+ */
+ public static int allocPort(InetSocketAddress hostAddr) throws IOException {
+ try (ServerSocket socket = new ServerSocket()) {
+ socket.bind(hostAddr);
+
+ return socket.getLocalPort();
+ }
+ }
+
+ /**
+ * Gets a trust manager that accepts all certificates.
+ *
+ * @return a trust manager that accepts all certificates
+ */
+ public static TrustManager[] getAlwaysTrustingManager() {
+ return ALWAYS_TRUST_MANAGER;
+ }
+
+ /**
* try to connect to $host:$port $retries times while we are getting connection failures.
*
* @param host host
diff --git a/utils/src/main/java/org/onap/policy/common/utils/properties/BeanConfigurator.java b/utils/src/main/java/org/onap/policy/common/utils/properties/BeanConfigurator.java
index 9d02819a..2ef91911 100644
--- a/utils/src/main/java/org/onap/policy/common/utils/properties/BeanConfigurator.java
+++ b/utils/src/main/java/org/onap/policy/common/utils/properties/BeanConfigurator.java
@@ -526,9 +526,8 @@ public class BeanConfigurator {
* @param methodName name of the method to return
* @return the method to be used to get the field's value
* @throws NoSuchMethodException if the method does not exist
- * @throws SecurityException if the method cannot be accessed
*/
- protected Method getGetter(Field field, String methodName) throws NoSuchMethodException, SecurityException {
+ protected Method getGetter(Field field, String methodName) throws NoSuchMethodException {
return field.getDeclaringClass().getMethod(methodName);
}
diff --git a/utils/src/main/java/org/onap/policy/common/utils/properties/PropertyConfiguration.java b/utils/src/main/java/org/onap/policy/common/utils/properties/PropertyConfiguration.java
deleted file mode 100644
index b41f3bc0..00000000
--- a/utils/src/main/java/org/onap/policy/common/utils/properties/PropertyConfiguration.java
+++ /dev/null
@@ -1,498 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- * ONAP Policy Engine - Common Modules
- * ================================================================================
- * Copyright (C) 2018-2019 AT&T Intellectual Property. All rights reserved.
- * ================================================================================
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ============LICENSE_END=========================================================
- */
-
-package org.onap.policy.common.utils.properties;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.Properties;
-import org.apache.commons.lang3.StringUtils;
-import org.onap.policy.common.utils.properties.exception.PropertyAccessException;
-import org.onap.policy.common.utils.properties.exception.PropertyException;
-import org.onap.policy.common.utils.properties.exception.PropertyInvalidException;
-import org.onap.policy.common.utils.properties.exception.PropertyMissingException;
-
-/**
- * Configuration whose fields are initialized by reading from a set of {@link Properties},
- * as directed by the {@link Property} annotations that appear on fields within the
- * subclass. The values of the fields are set via <i>setXxx()</i> methods. As a result, if
- * a field is annotated and there is no corresponding <i>setXxx()</i> method, then an
- * exception will be thrown.
- *
- * <p>It is possible that an invalid <i>defaultValue</i> is specified via the
- * {@link Property} annotation. This could remain undetected until an optional property is
- * left out of the {@link Properties}. Consequently, this class will always validate a
- * {@link Property}'s default value, if the <i>defaultValue</i> is not empty or if
- * <i>accept</i> includes the "empty" option.
- */
-public class PropertyConfiguration {
-
- /**
- * The "empty" option that may appear within the {@link Property}'s <i>accept</i>
- * attribute.
- */
- public static final String ACCEPT_EMPTY = "empty";
-
- /**
- * Constructs a configuration, without populating any fields; fields should be
- * populated later by invoking {@link #setAllFields(Properties)}.
- */
- public PropertyConfiguration() {
- super();
- }
-
- /**
- * Initializes each "@Property" field with its value, as found in the properties.
- *
- * @param props properties from which to extract the values
- * @throws PropertyException if an error occurs
- */
- public PropertyConfiguration(Properties props) throws PropertyException {
- setAllFields(props);
- }
-
- /**
- * Walks the class hierarchy of "this" object, populating fields defined in each
- * class, using values extracted from the given property set.
- *
- * @param props properties from which to extract the values
- * @throws PropertyException if an error occurs
- */
- public void setAllFields(Properties props) throws PropertyException {
- Class<?> clazz = getClass();
-
- while (clazz != PropertyConfiguration.class) {
- for (Field field : clazz.getDeclaredFields()) {
- setValue(field, props);
- }
-
- clazz = clazz.getSuperclass();
- }
- }
-
- /**
- * Sets a field's value, within an object, based on what's in the properties.
- *
- * @param field field whose value is to be set
- * @param props properties from which to get the value
- * @return {@code true} if the property's value was set, {@code false} otherwise
- * @throws PropertyException if an error occurs
- */
- protected boolean setValue(Field field, Properties props) throws PropertyException {
- Property prop = field.getAnnotation(Property.class);
- if (prop == null) {
- return false;
- }
-
- checkModifiable(field, prop);
-
- Method setter = getSetter(field, prop);
- checkSetter(setter, prop);
-
- if (setValue(setter, field, props, prop)) {
- return true;
- }
-
- throw new PropertyAccessException(prop.name(), field.getName(), "unsupported field type");
- }
-
- /**
- * Sets a field's value from a particular property.
- *
- * @param setter method to be used to set the field's value
- * @param field field whose value is to be set
- * @param props properties from which to get the value
- * @param prop property of interest
- * @return {@code true} if the property's value was set, {@code false} otherwise
- * @throws PropertyException if an error occurs
- */
- protected boolean setValue(Method setter, Field field, Properties props, Property prop) throws PropertyException {
-
- try {
- Object val = getValue(field, props, prop);
- if (val == null) {
- return false;
-
- } else {
- setter.invoke(this, val);
- return true;
- }
-
- } catch (IllegalArgumentException e) {
- throw new PropertyInvalidException(prop.name(), field.getName(), e);
-
- } catch (IllegalAccessException | InvocationTargetException e) {
- throw new PropertyAccessException(prop.name(), setter.getName(), e);
- }
- }
-
- /**
- * Get the setter.
- *
- * @param field field whose value is to be set
- * @param prop property of interest
- * @return the method to be used to set the field's value
- * @throws PropertyAccessException if a "set" method cannot be identified
- */
- private Method getSetter(Field field, Property prop) throws PropertyAccessException {
- String nm = "set" + StringUtils.capitalize(field.getName());
-
- try {
- return this.getClass().getMethod(nm, field.getType());
-
- } catch (NoSuchMethodException | SecurityException e) {
- throw new PropertyAccessException(prop.name(), nm, e);
- }
- }
-
- /**
- * Gets a property value, coercing it to the field's type.
- *
- * @param field field whose value is to be set
- * @param props properties from which to get the value
- * @param prop property of interest
- * @return the value extracted from the property, or {@code null} if the field type is
- * not supported
- * @throws PropertyException if an error occurs
- */
- protected Object getValue(Field field, Properties props, Property prop) throws PropertyException {
-
- Class<?> clazz = field.getType();
- String fieldName = field.getName();
-
- // can still add support for short, float, double, enum
-
- if (clazz == String.class) {
- return getStringValue(fieldName, props, prop);
-
- } else if (clazz == Boolean.class || clazz == boolean.class) {
- return getBooleanValue(fieldName, props, prop);
-
- } else if (clazz == Integer.class || clazz == int.class) {
- return getIntegerValue(fieldName, props, prop);
-
- } else if (clazz == Long.class || clazz == long.class) {
- return getLongValue(fieldName, props, prop);
-
- } else {
- return null;
- }
- }
-
- /**
- * Verifies that the field can be modified, i.e., it's neither <i>static</i>, nor
- * <i>final</i>.
- *
- * @param field field whose value is to be set
- * @param prop property of interest
- * @throws PropertyAccessException if the field is not modifiable
- */
- protected void checkModifiable(Field field, Property prop) throws PropertyAccessException {
- int mod = field.getModifiers();
-
- if (Modifier.isStatic(mod)) {
- throw new PropertyAccessException(prop.name(), field.getName(), "'static' variable cannot be modified");
- }
-
- if (Modifier.isFinal(mod)) {
- throw new PropertyAccessException(prop.name(), field.getName(), "'final' variable cannot be modified");
- }
- }
-
- /**
- * Verifies that the setter method is not <i>static</i>.
- *
- * @param setter method to be checked
- * @param prop property of interest
- * @throws PropertyAccessException if the method is static
- */
- private void checkSetter(Method setter, Property prop) throws PropertyAccessException {
- int mod = setter.getModifiers();
-
- if (Modifier.isStatic(mod)) {
- throw new PropertyAccessException(prop.name(), setter.getName(), "method is 'static'");
- }
- }
-
- /**
- * Gets a property value, coercing it to a String.
- *
- * @param fieldName field whose value is to be set
- * @param props properties from which to get the value
- * @param prop property of interest
- * @return the value extracted from the property
- * @throws PropertyException if an error occurs
- */
- protected String getStringValue(String fieldName, Properties props, Property prop) throws PropertyException {
-
- /*
- * Note: the default value for a String type is always valid, thus no need to
- * check it.
- */
-
- return getPropValue(fieldName, props, prop);
- }
-
- /**
- * Gets a property value, coercing it to a Boolean.
- *
- * @param fieldName field whose value is to be set
- * @param props properties from which to get the value
- * @param prop property of interest
- * @return the value extracted from the property
- * @throws PropertyException if an error occurs
- */
- protected Boolean getBooleanValue(String fieldName, Properties props, Property prop) throws PropertyException {
- // validate the default value
- checkDefaultValue(fieldName, prop, xxx -> makeBoolean(fieldName, prop, prop.defaultValue()));
-
- return makeBoolean(fieldName, prop, getPropValue(fieldName, props, prop));
- }
-
- /**
- * Gets a property value, coercing it to an Integer.
- *
- * @param fieldName field whose value is to be set
- * @param props properties from which to get the value
- * @param prop property of interest
- * @return the value extracted from the property
- * @throws PropertyException if an error occurs
- */
- protected Integer getIntegerValue(String fieldName, Properties props, Property prop) throws PropertyException {
- // validate the default value
- checkDefaultValue(fieldName, prop, xxx -> makeInteger(fieldName, prop, prop.defaultValue()));
-
- return makeInteger(fieldName, prop, getPropValue(fieldName, props, prop));
- }
-
- /**
- * Gets a property value, coercing it to a Long.
- *
- * @param fieldName field whose value is to be set
- * @param props properties from which to get the value
- * @param prop property of interest
- * @return the value extracted from the property
- * @throws PropertyException if an error occurs
- */
- protected Long getLongValue(String fieldName, Properties props, Property prop) throws PropertyException {
- // validate the default value
- checkDefaultValue(fieldName, prop, xxx -> makeLong(fieldName, prop, prop.defaultValue()));
-
- return makeLong(fieldName, prop, getPropValue(fieldName, props, prop));
- }
-
- /**
- * Gets a value from the property set.
- *
- * @param fieldName field whose value is to be set
- * @param props properties from which to get the value
- * @param prop property of interest
- * @return the value extracted from the property, or the <i>defaultValue</i> if the
- * value does not exist
- * @throws PropertyMissingException if the property does not exist and the
- * <i>defaultValue</i> is empty and <i>emptyOk</i> is {@code false}
- */
- protected String getPropValue(String fieldName, Properties props, Property prop) throws PropertyMissingException {
- String propnm = prop.name();
-
- String val = getRawPropertyValue(props, propnm);
- if (val != null && isEmptyOk(prop, val)) {
- return val;
- }
-
- val = prop.defaultValue();
- if (val != null && isEmptyOk(prop, val)) {
- return val;
- }
-
- throw new PropertyMissingException(prop.name(), fieldName);
- }
-
- /**
- * Gets the property value, straight from the property set.
- *
- * @param props properties from which to get the value
- * @param propnm name of the property of interest
- * @return the raw property value
- */
- protected String getRawPropertyValue(Properties props, String propnm) {
- return props.getProperty(propnm);
- }
-
- /**
- * Coerces a String value into a Boolean.
- *
- * @param fieldName field whose value is to be set
- * @param prop property of interest
- * @param value value to be coerced
- * @return the Boolean value represented by the String value
- * @throws PropertyInvalidException if the value does not represent a valid Boolean
- */
- private Boolean makeBoolean(String fieldName, Property prop, String value) throws PropertyInvalidException {
- if ("true".equalsIgnoreCase(value)) {
- return Boolean.TRUE;
-
- } else if ("false".equalsIgnoreCase(value)) {
- return Boolean.FALSE;
-
- } else {
- throw new PropertyInvalidException(prop.name(), fieldName, "expecting 'true' or 'false'");
- }
- }
-
- /**
- * Coerces a String value into an Integer.
- *
- * @param fieldName field whose value is to be set
- * @param prop property of interest
- * @param value value to be coerced
- * @return the Integer value represented by the String value
- * @throws PropertyInvalidException if the value does not represent a valid Integer
- */
- private Integer makeInteger(String fieldName, Property prop, String value) throws PropertyInvalidException {
- try {
- return Integer.valueOf(value);
-
- } catch (NumberFormatException e) {
- throw new PropertyInvalidException(prop.name(), fieldName, e);
- }
- }
-
- /**
- * Coerces a String value into a Long.
- *
- * @param fieldName field whose value is to be set
- * @param prop property of interest
- * @param value value to be coerced
- * @return the Long value represented by the String value
- * @throws PropertyInvalidException if the value does not represent a valid Long
- */
- private Long makeLong(String fieldName, Property prop, String value) throws PropertyInvalidException {
- try {
- return Long.valueOf(value);
-
- } catch (NumberFormatException e) {
- throw new PropertyInvalidException(prop.name(), fieldName, e);
- }
- }
-
- /**
- * Applies a function to check a property's default value. If the function throws an
- * exception about an invalid property, then it's re-thrown as an exception about an
- * invalid <i>defaultValue</i>.
- *
- * @param fieldName name of the field being checked
- * @param prop property of interest
- * @param func function to invoke to check the default value
- */
- private void checkDefaultValue(String fieldName, Property prop, CheckDefaultValueFunction func)
- throws PropertyInvalidException {
-
- if (isEmptyOk(prop, prop.defaultValue())) {
- try {
- func.apply(null);
-
- } catch (PropertyInvalidException ex) {
- throw new PropertyInvalidException(ex.getPropertyName(), fieldName, "defaultValue is invalid", ex);
- }
- }
- }
-
- /**
- * Determines if a value is OK, even if it's empty.
- *
- * @param prop property specifying what's acceptable
- * @param value value to be checked
- * @return {@code true} if the value is not empty or empty is allowed, {@code false}
- * otherwise
- */
- protected boolean isEmptyOk(Property prop, String value) {
- return !value.isEmpty() || isEmptyOk(prop);
- }
-
- /**
- * Determines if a {@link Property}'s <i>accept</i> attribute includes the "empty"
- * option.
- *
- * @param prop property whose <i>accept</i> attribute is to be examined
- * @return {@code true} if the <i>accept</i> attribute includes "empty"
- */
- protected boolean isEmptyOk(Property prop) {
- for (String option : prop.accept().split(",")) {
- if (ACCEPT_EMPTY.equals(option)) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Functions to check a default value.
- */
- @FunctionalInterface
- private static interface CheckDefaultValueFunction {
-
- /**
- * Checks the default value.
- *
- * @param arg always {@code null}
- * @throws PropertyInvalidException if an error occurs
- */
- public void apply(Void arg) throws PropertyInvalidException;
- }
-
- /**
- * Annotation that declares a variable to be configured via {@link Properties}.
- */
- @Target(ElementType.FIELD)
- @Retention(RetentionPolicy.RUNTIME)
-
- protected static @interface Property {
-
- /**
- * Name of the property.
- *
- * @return the property name
- */
- public String name();
-
- /**
- * Default value, used when the property does not exist.
- *
- * @return the default value
- */
- public String defaultValue() default "";
-
- /**
- * Comma-separated options identifying what's acceptable. The word, "empty",
- * indicates that an empty string, "", is an acceptable value.
- *
- * @return options identifying what's acceptable
- */
- public String accept() default "";
- }
-}
diff --git a/utils/src/main/java/org/onap/policy/common/utils/services/ServiceManager.java b/utils/src/main/java/org/onap/policy/common/utils/services/ServiceManager.java
new file mode 100644
index 00000000..8bf89d56
--- /dev/null
+++ b/utils/src/main/java/org/onap/policy/common/utils/services/ServiceManager.java
@@ -0,0 +1,190 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP PAP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.services;
+
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.LinkedList;
+import org.onap.policy.common.capabilities.Startable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Manages a series of services. The services are started in order, and stopped in reverse
+ * order.
+ */
+public class ServiceManager {
+ private static final Logger logger = LoggerFactory.getLogger(ServiceManager.class);
+
+ /**
+ * Services to be started/stopped.
+ */
+ private final Deque<Service> items = new LinkedList<>();
+
+ /**
+ * {@code True} if the services are currently running, {@code false} otherwise.
+ */
+ private boolean running;
+
+ /**
+ * Adds a pair of service actions to the manager.
+ *
+ * @param stepName name to be logged when the service is started/stopped
+ * @param starter function to start the service
+ * @param stopper function to stop the service
+ * @return this manager
+ */
+ public synchronized ServiceManager addAction(String stepName, RunnableWithEx starter, RunnableWithEx stopper) {
+ if (running) {
+ throw new IllegalStateException("services are already running; cannot add " + stepName);
+ }
+
+ items.add(new Service(stepName, starter, stopper));
+ return this;
+ }
+
+ /**
+ * Adds a service to the manager. The manager will invoke the service's
+ * {@link Startable#start()} and {@link Startable#stop()} methods.
+ *
+ * @param stepName name to be logged when the service is started/stopped
+ * @param service object to be started/stopped
+ * @return this manager
+ */
+ public synchronized ServiceManager addService(String stepName, Startable service) {
+ if (running) {
+ throw new IllegalStateException("services are already running; cannot add " + stepName);
+ }
+
+ items.add(new Service(stepName, () -> service.start(), () -> service.stop()));
+ return this;
+ }
+
+ /**
+ * Starts each service, in order. If a service throws an exception, then the
+ * previously started services are stopped, in reverse order.
+ *
+ * @throws ServiceManagerException if a service fails to start
+ */
+ public synchronized void start() throws ServiceManagerException {
+ if (running) {
+ throw new IllegalStateException("services are already running");
+ }
+
+ // tracks the services that have been started so far
+ Deque<Service> started = new LinkedList<>();
+ Exception ex = null;
+
+ for (Service item : items) {
+ try {
+ logger.info("starting {}", item.stepName);
+ item.starter.run();
+ started.add(item);
+
+ } catch (Exception e) {
+ logger.error("failed to start {}; rewinding steps", item.stepName);
+ ex = e;
+ break;
+ }
+ }
+
+ if (ex == null) {
+ running = true;
+ return;
+ }
+
+ // one of the services failed to start - rewind those we've previously started
+ try {
+ rewind(started);
+
+ } catch (ServiceManagerException e) {
+ logger.error("rewind failed", e);
+ }
+
+ throw new ServiceManagerException(ex);
+ }
+
+ /**
+ * Stops the services, in reverse order from which they were started. Stops all of the
+ * services, even if one of the "stop" functions throws an exception. Assumes that
+ * {@link #start()} has completed successfully.
+ *
+ * @throws ServiceManagerException if a service fails to stop
+ */
+ public synchronized void stop() throws ServiceManagerException {
+ if (!running) {
+ throw new IllegalStateException("services are not running");
+ }
+
+ running = false;
+ rewind(items);
+ }
+
+ /**
+ * Rewinds a list of services, stopping them in reverse order. Stops all of the
+ * services, even if one of the "stop" functions throws an exception.
+ *
+ * @param running services that are running, in the order they were started
+ * @throws ServiceManagerException if a service fails to stop
+ */
+ private void rewind(Deque<Service> running) throws ServiceManagerException {
+ Exception ex = null;
+
+ // stop everything, in reverse order
+ Iterator<Service> it = running.descendingIterator();
+ while (it.hasNext()) {
+ Service item = it.next();
+ try {
+ logger.info("stopping {}", item.stepName);
+ item.stopper.run();
+ } catch (Exception e) {
+ logger.error("failed to stop {}", item.stepName);
+ ex = e;
+
+ // do NOT break or re-throw, as we must stop ALL remaining items
+ }
+ }
+
+ if (ex != null) {
+ throw new ServiceManagerException(ex);
+ }
+ }
+
+ /**
+ * Service information.
+ */
+ private static class Service {
+ private String stepName;
+ private RunnableWithEx starter;
+ private RunnableWithEx stopper;
+
+ public Service(String stepName, RunnableWithEx starter, RunnableWithEx stopper) {
+ this.stepName = stepName;
+ this.starter = starter;
+ this.stopper = stopper;
+ }
+ }
+
+ @FunctionalInterface
+ public static interface RunnableWithEx {
+ public void run() throws Exception;
+ }
+}
diff --git a/utils/src/main/java/org/onap/policy/common/utils/services/ServiceManagerException.java b/utils/src/main/java/org/onap/policy/common/utils/services/ServiceManagerException.java
new file mode 100644
index 00000000..3daa441a
--- /dev/null
+++ b/utils/src/main/java/org/onap/policy/common/utils/services/ServiceManagerException.java
@@ -0,0 +1,44 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP PAP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.services;
+
+/**
+ * Exceptions thrown by the ServiceManager.
+ */
+public class ServiceManagerException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public ServiceManagerException() {
+ super();
+ }
+
+ public ServiceManagerException(String message) {
+ super(message);
+ }
+
+ public ServiceManagerException(Throwable cause) {
+ super(cause);
+ }
+
+ public ServiceManagerException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}