From 1162f4b61e6893c0f44d1f9d5d8abc81a94bed48 Mon Sep 17 00:00:00 2001 From: Jim Hahn Date: Mon, 11 Mar 2019 11:37:53 -0400 Subject: Add ServiceManager class Added ServiceManager class to start a list of services, in order, and stop them in reverse order. Also addressed minor checkstyle issue in TopicSinkClient. Enabled logging from tests. Updated some comments. Updated license date. Added state checks and support for multi-threading. Change-Id: Ie7f053d9884766fe199895691a57eb5a51b1d155 Issue-ID: POLICY-1542 Signed-off-by: Jim Hahn --- .../common/utils/services/ServiceManager.java | 190 +++++++++++++++++++++ .../utils/services/ServiceManagerException.java | 44 +++++ 2 files changed, 234 insertions(+) create mode 100644 utils/src/main/java/org/onap/policy/common/utils/services/ServiceManager.java create mode 100644 utils/src/main/java/org/onap/policy/common/utils/services/ServiceManagerException.java (limited to 'utils/src/main/java') 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 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 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 running) throws ServiceManagerException { + Exception ex = null; + + // stop everything, in reverse order + Iterator 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); + } +} -- cgit 1.2.3-korg