diff options
Diffstat (limited to 'policy-management/src/main/java/org/onap/policy')
20 files changed, 11340 insertions, 0 deletions
diff --git a/policy-management/src/main/java/org/onap/policy/drools/controller/DroolsController.java b/policy-management/src/main/java/org/onap/policy/drools/controller/DroolsController.java new file mode 100644 index 00000000..c4c61a8a --- /dev/null +++ b/policy-management/src/main/java/org/onap/policy/drools/controller/DroolsController.java @@ -0,0 +1,218 @@ +/*- + * ============LICENSE_START======================================================= + * policy-management + * ================================================================================ + * Copyright (C) 2017 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.drools.controller; + +import java.util.List; +import java.util.Map; + +import org.onap.policy.drools.core.PolicyContainer; +import org.onap.policy.drools.event.comm.TopicSink; +import org.onap.policy.drools.properties.Lockable; +import org.onap.policy.drools.properties.Startable; +import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration; + +/** + * Drools Controller is the abstractions that wraps the + * drools layer (policy-core) + */ +public interface DroolsController extends Startable, Lockable { + + /** + * No Group ID identifier + */ + public static final String NO_GROUP_ID = "NO-GROUP-ID"; + + /** + * No Artifact ID identifier + */ + public static final String NO_ARTIFACT_ID = "NO-ARTIFACT-ID"; + + /** + * No version identifier + */ + public static final String NO_VERSION = "NO-VERSION"; + + /** + * get group id + * @return group id + */ + public String getGroupId(); + + /** + * get artifact id + * @return artifact id + */ + public String getArtifactId(); + + /** + * get version + * @return version + */ + public String getVersion(); + + /** + * return the policy session names + * + * @return policy session + */ + public List<String> getSessionNames(); + + /** + * return the policy full session names + * + * @return policy session + */ + public List<String> getCanonicalSessionNames(); + + /** + * offers an event to this controller for processing + * + * @param topic topic associated with the event + * @param event the event + * + * @return true if the operation was successful + */ + public boolean offer(String topic, String event); + + /** + * delivers "event" to "sink" + * + * @param sink destination + * @param event + * @return true if successful, false if a failure has occurred. + * @throws IllegalArgumentException when invalid or insufficient + * properties are provided + * @throws IllegalStateException when the engine is in a state where + * this operation is not permitted (ie. locked or stopped). + * @throws UnsupportedOperationException when the engine cannot deliver due + * to the functionality missing (ie. communication infrastructure + * not supported. + */ + public boolean deliver(TopicSink sink, Object event) + throws IllegalArgumentException, IllegalStateException, + UnsupportedOperationException; + + /** + * + * @return the most recent received events + */ + public Object[] getRecentSourceEvents(); + + /** + * + * @return the most recent delivered events + */ + public String[] getRecentSinkEvents(); + + /** + * @return the underlying policy container + */ + public PolicyContainer getContainer(); + + /** + * Supports this encoder? + * + * @param encodedObject + * @return + */ + public boolean ownsCoder(Class<? extends Object> coderClass, int modelHash) throws IllegalStateException; + + /** + * fetches a class from the model + * + * @param className the class to fetch + * @return the actual class object, or null if not found + */ + public Class<?> fetchModelClass(String className) throws IllegalArgumentException; + + /** + * is this controller Smart? + */ + public boolean isBrained(); + + /** + * update the new version of the maven jar rules file + * + * @param newGroupId - new group id + * @param newArtifactId - new artifact id + * @param newVersion - new version + * @param decoderConfigurations - decoder configurations + * @param encoderConfigurations - encoder configurations + * + * @throws Exception from within drools libraries + * @throws LinkageError from within drools libraries + * @throws ArgumentException bad parameter passed in + */ + public void updateToVersion(String newGroupId, String newArtifactId, String newVersion, + List<TopicCoderFilterConfiguration> decoderConfigurations, + List<TopicCoderFilterConfiguration> encoderConfigurations) + throws IllegalArgumentException, LinkageError, Exception; + + /** + * gets the classnames of facts as well as the current count + * @param sessionName the session name + * @return map of class to count + */ + public Map<String,Integer> factClassNames(String sessionName) throws IllegalArgumentException; + + /** + * gets the count of facts for a given session + * @param sessionName the session name + * @return the fact count + * @throws IllegalArgumentException + */ + public long factCount(String sessionName) throws IllegalArgumentException; + + /** + * gets all the facts of a given class for a given session + * + * @param sessionName the session identifier + * @param className the class type + * @param delete retract from drools the results of the query? + * @return the list of facts returned by the query + */ + public List<Object> facts(String sessionName, String className, boolean delete); + + /** + * gets the facts associated with a query for a give session for a given queried entity + * + * @param sessionName the session + * @param queryName the query identifier + * @param queriedEntity the queried entity + * @param delete retract from drools the results of the query? + * @param queryParams query parameters + * @return list of facts returned by the query + */ + public List<Object> factQuery(String sessionName, String queryName, String queriedEntity, + boolean delete, Object... queryParams); + + /** + * halts and permanently releases all resources + * @throws IllegalStateException + */ + public void halt() throws IllegalStateException; + + /** + * Factory to track and manage drools controllers + */ + public static final DroolsControllerFactory factory = + new IndexedDroolsControllerFactory(); +} diff --git a/policy-management/src/main/java/org/onap/policy/drools/controller/DroolsControllerFactory.java b/policy-management/src/main/java/org/onap/policy/drools/controller/DroolsControllerFactory.java new file mode 100644 index 00000000..c7a63a03 --- /dev/null +++ b/policy-management/src/main/java/org/onap/policy/drools/controller/DroolsControllerFactory.java @@ -0,0 +1,555 @@ +/*- + * ============LICENSE_START======================================================= + * policy-management + * ================================================================================ + * Copyright (C) 2017 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.drools.controller; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Properties; + +import org.onap.policy.drools.controller.internal.MavenDroolsController; +import org.onap.policy.drools.controller.internal.NullDroolsController; +import org.onap.policy.drools.event.comm.Topic; +import org.onap.policy.drools.event.comm.Topic.CommInfrastructure; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.onap.policy.drools.event.comm.TopicSource; +import org.onap.policy.drools.event.comm.TopicSink; +import org.onap.policy.drools.properties.PolicyProperties; +import org.onap.policy.drools.protocol.coders.JsonProtocolFilter; +import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration; +import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration.CustomGsonCoder; +import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration.CustomJacksonCoder; +import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration.PotentialCoderFilter; +import org.onap.policy.drools.utils.Pair; + +/** + * Drools Controller Factory to manage controller creation, destruction, + * and retrieval for management interfaces + */ +public interface DroolsControllerFactory { + + /** + * Constructs a Drools Controller based on properties + * + * @param properties properties containing initialization parameters + * @param eventSources list of event sources + * @param eventSinks list of event sinks + * + * @return the instantiated Drools Controller + * @throws IllegalArgumentException with invalid parameters + * @throws LinkageError Failure to link rules and models in Drools Libraries + * @throws Exception Exception from Drools Libraries + */ + public DroolsController build(Properties properties, + List<? extends TopicSource> eventSources, + List<? extends TopicSink> eventSinks) + throws IllegalArgumentException, LinkageError, Exception; + + /** + * Explicit construction of a Drools Controller + * + * @param groupId maven group id of drools artifact + * @param artifactId maven artifact id of drools artifact + * @param version maven version id of drools artifact + * @param decoderConfigurations list of decoder configurations + * @param encoderConfigurations list of encoder configurations + * + * @return the instantiated Drools Controller + * @throws IllegalArgumentException with invalid parameters + * @throws LinkageError Failure to link rules and models in Drools Libraries + * @throws Exception Exception from Drools Libraries + */ + public DroolsController build(String groupId, + String artifactId, + String version, + List<TopicCoderFilterConfiguration> decoderConfigurations, + List<TopicCoderFilterConfiguration> encoderConfigurations) + throws IllegalArgumentException, LinkageError, Exception; + + /** + * Releases the Drools Controller from operation + * + * @param controller the Drools Controller to shut down + */ + public void shutdown(DroolsController controller); + + /** + * Disables all Drools Controllers from operation + */ + public void shutdown(); + + /** + * Destroys and releases resources for a Drools Controller + * + * @param controller the Drools Controller to destroy + */ + public void destroy(DroolsController controller); + + /** + * Destroys all Drools Controllers + */ + public void destroy(); + + /** + * Gets the Drools Controller associated with the maven group + * and artifact id + * + * @param groupId maven group id of drools artifact + * @param artifactId maven artifact id of drools artifact + * @param version maven version id of drools artifact + * + * @return the Drools Controller + * @throws IllegalArgumentException with invalid parameters + */ + public DroolsController get(String groupId, + String artifactId, + String version) + throws IllegalArgumentException; + + /** + * returns the current inventory of Drools Controllers + * + * @return a list of Drools Controllers + */ + public List<DroolsController> inventory(); +} + +/* ---------------- implementation -----------------*/ + +/** + * Factory of Drools Controllers indexed by the Maven coordinates + */ +class IndexedDroolsControllerFactory implements DroolsControllerFactory { + + /** + * logger + */ + private static Logger logger = LoggerFactory.getLogger(MavenDroolsController.class); + + /** + * Policy Controller Name Index + */ + protected HashMap<String, DroolsController> droolsControllers = + new HashMap<String, DroolsController>(); + + /** + * Null Drools Controller + */ + protected NullDroolsController nullDroolsController = new NullDroolsController(); + + + public IndexedDroolsControllerFactory() { + + /* Add a NULL controller which will always be present in the hash */ + + DroolsController controller = new NullDroolsController(); + String controllerId = controller.getGroupId() + ":" + controller.getArtifactId(); + + synchronized(this) { + droolsControllers.put(controllerId, controller); + } + } + + /** + * {@inheritDoc} + */ + @Override + public DroolsController build(Properties properties, + List<? extends TopicSource> eventSources, + List<? extends TopicSink> eventSinks) + throws IllegalArgumentException, LinkageError, Exception { + + String groupId = properties.getProperty(PolicyProperties.RULES_GROUPID); + if (groupId == null || groupId.isEmpty()) + groupId = DroolsController.NO_GROUP_ID; + + String artifactId = properties.getProperty(PolicyProperties.RULES_ARTIFACTID); + if (artifactId == null || artifactId.isEmpty()) + artifactId = DroolsController.NO_ARTIFACT_ID; + + String version = properties.getProperty(PolicyProperties.RULES_VERSION); + if (version == null || version.isEmpty()) + version = DroolsController.NO_VERSION; + + List<TopicCoderFilterConfiguration> + topics2DecodedClasses2Filters = codersAndFilters(properties, eventSources); + + List<TopicCoderFilterConfiguration> + topics2EncodedClasses2Filters = codersAndFilters(properties, eventSinks); + + return this.build(groupId, artifactId, version, + topics2DecodedClasses2Filters, + topics2EncodedClasses2Filters); + } + + /** + * find out decoder classes and filters + * + * @param properties properties with information about decoders + * @param topicEntities topic sources + * @return list of topics, each with associated decoder classes, each + * with a list of associated filters + * @throws IllegalArgumentException invalid input data + */ + protected List<TopicCoderFilterConfiguration> codersAndFilters + (Properties properties, List<? extends Topic> topicEntities) + throws IllegalArgumentException { + + String PROPERTY_TOPIC_ENTITY_PREFIX; + + List<TopicCoderFilterConfiguration> + topics2DecodedClasses2Filters = + new ArrayList<TopicCoderFilterConfiguration>(); + + if (topicEntities.isEmpty()) + return topics2DecodedClasses2Filters; + + for (Topic topic: topicEntities) { + + /* source or sink ? ueb or dmaap? */ + boolean isSource = (topic instanceof TopicSource); + CommInfrastructure commInfra = topic.getTopicCommInfrastructure(); + if (commInfra == CommInfrastructure.UEB) { + if (isSource) { + PROPERTY_TOPIC_ENTITY_PREFIX = PolicyProperties.PROPERTY_UEB_SOURCE_TOPICS + "."; + } else { + PROPERTY_TOPIC_ENTITY_PREFIX = PolicyProperties.PROPERTY_UEB_SINK_TOPICS + "."; + } + } else if (commInfra == CommInfrastructure.DMAAP) { + if (isSource) { + PROPERTY_TOPIC_ENTITY_PREFIX = PolicyProperties.PROPERTY_DMAAP_SOURCE_TOPICS + "."; + } else { + PROPERTY_TOPIC_ENTITY_PREFIX = PolicyProperties.PROPERTY_DMAAP_SINK_TOPICS + "."; + } + } else if (commInfra == CommInfrastructure.NOOP) { + if (!isSource) + PROPERTY_TOPIC_ENTITY_PREFIX = PolicyProperties.PROPERTY_NOOP_SINK_TOPICS + "."; + else + continue; + } else { + throw new IllegalArgumentException("Invalid Communication Infrastructure: " + commInfra); + } + + // 1. first the topic + + String aTopic = topic.getTopic(); + + // 2. check if there is a custom decoder for this topic that the user prefers to use + // instead of the ones provided in the platform + + String customGson = properties.getProperty + (PROPERTY_TOPIC_ENTITY_PREFIX + + aTopic + + PolicyProperties.PROPERTY_TOPIC_EVENTS_CUSTOM_MODEL_CODER_GSON_SUFFIX); + + CustomGsonCoder customGsonCoder = null; + if (customGson != null && !customGson.isEmpty()) { + try { + customGsonCoder = new CustomGsonCoder(customGson); + } catch (IllegalArgumentException e) { + logger.warn("{}: cannot create custom-gson-coder {} because of {}", + this, customGson, e.getMessage(), e); + } + } + + String customJackson = properties.getProperty + (PROPERTY_TOPIC_ENTITY_PREFIX + + aTopic + + PolicyProperties.PROPERTY_TOPIC_EVENTS_CUSTOM_MODEL_CODER_JACKSON_SUFFIX); + + CustomJacksonCoder customJacksonCoder = null; + if (customJackson != null && !customJackson.isEmpty()) { + try { + customJacksonCoder = new CustomJacksonCoder(customJackson); + } catch (IllegalArgumentException e) { + logger.warn("{}: cannot create custom-jackson-coder {} because of {}", + this, customJackson, e.getMessage(), e); + } + } + + // 3. second the list of classes associated with each topic + + String eventClasses = + properties.getProperty(PROPERTY_TOPIC_ENTITY_PREFIX + aTopic + PolicyProperties.PROPERTY_TOPIC_EVENTS_SUFFIX); + + if (eventClasses == null || eventClasses.isEmpty()) { + // TODO warn + continue; + } + + List<PotentialCoderFilter> classes2Filters = new ArrayList<PotentialCoderFilter>(); + + List<String> aTopicClasses = + new ArrayList<String>(Arrays.asList(eventClasses.split("\\s*,\\s*"))); + + for (String aClass: aTopicClasses) { + + + // 4. third, for each coder class, get the list of field filters + + String filter = properties.getProperty + (PROPERTY_TOPIC_ENTITY_PREFIX + + aTopic + + PolicyProperties.PROPERTY_TOPIC_EVENTS_SUFFIX + + "." + aClass + + PolicyProperties.PROPERTY_TOPIC_EVENTS_FILTER_SUFFIX); + + List<Pair<String,String>> filters = new ArrayList<Pair<String,String>>(); + + if (filter == null || filter.isEmpty()) { + // 4. topic -> class -> with no filters + + JsonProtocolFilter protocolFilter = JsonProtocolFilter.fromRawFilters(filters); + PotentialCoderFilter class2Filters = + new PotentialCoderFilter(aClass, protocolFilter); + classes2Filters.add(class2Filters); + continue; + } + + // There are filters associated with the applicability of + // this class for decoding. + List<String> listOfFilters = + new ArrayList<String>(Arrays.asList(filter.split("\\s*,\\s*"))); + + for (String nameValue: listOfFilters) { + String fieldName; + String regexValue; + + String[] nameValueSplit = nameValue.split("\\s*=\\s*"); + if (nameValueSplit.length <= 0 || nameValueSplit.length > 2) { + // TODO warn + // skip + continue; + } + + if (nameValueSplit.length == 2) { + fieldName = nameValueSplit[0]; + regexValue = nameValueSplit[1]; + } else if (nameValueSplit.length == 1) { + fieldName = nameValueSplit[0]; + regexValue = null; + } else { + // unreachable + continue; + } + + filters.add(new Pair<String,String>(fieldName, regexValue)); + } + + JsonProtocolFilter protocolFilter = JsonProtocolFilter.fromRawFilters(filters); + PotentialCoderFilter class2Filters = + new PotentialCoderFilter(aClass, protocolFilter); + classes2Filters.add(class2Filters); + } + + TopicCoderFilterConfiguration topic2Classes2Filters = + new TopicCoderFilterConfiguration(aTopic,classes2Filters, customGsonCoder, customJacksonCoder); + topics2DecodedClasses2Filters.add(topic2Classes2Filters); + } + + return topics2DecodedClasses2Filters; + } + + /** + * {@inheritDoc} + * @param decoderConfiguration + */ + @Override + public DroolsController build(String newGroupId, + String newArtifactId, + String newVersion, + List<TopicCoderFilterConfiguration> decoderConfigurations, + List<TopicCoderFilterConfiguration> encoderConfigurations) + throws IllegalArgumentException, LinkageError, Exception { + + if (newGroupId == null || newArtifactId == null || newVersion == null || + newGroupId.isEmpty() || newArtifactId.isEmpty() || newVersion.isEmpty()) { + throw new IllegalArgumentException("Missing maven coordinates: " + + newGroupId + ":" + newArtifactId + ":" + + newVersion); + } + + String controllerId = newGroupId + ":" + newArtifactId; + DroolsController controllerCopy = null; + synchronized (this) { + /* + * The Null Drools Controller for no maven coordinates is always here + * so when no coordinates present, this is the return point + * + * assert (controllerCopy instanceof NullDroolsController) + */ + if (droolsControllers.containsKey(controllerId)) { + controllerCopy = droolsControllers.get(controllerId); + if (controllerCopy.getVersion().equalsIgnoreCase(newVersion)) { + return controllerCopy; + } + } + } + + if (controllerCopy != null) { + /* + * a controller keyed by group id + artifact id exists + * but with different version => version upgrade/downgrade + */ + + controllerCopy.updateToVersion(newGroupId, newArtifactId, newVersion, + decoderConfigurations, encoderConfigurations); + + return controllerCopy; + } + + /* new drools controller */ + + DroolsController controller = new MavenDroolsController + (newGroupId, newArtifactId, newVersion, + decoderConfigurations, + encoderConfigurations); + + synchronized(this) { + droolsControllers.put(controllerId, controller); + } + + return controller; + } + + /** + * {@inheritDoc} + */ + @Override + public void destroy(DroolsController controller) throws IllegalArgumentException { + unmanage(controller); + controller.halt(); + } + + /** + * {@inheritDoc} + */ + @Override + public void destroy() { + List<DroolsController> controllers = this.inventory(); + for (DroolsController controller: controllers) { + controller.halt(); + } + + synchronized(this) { + this.droolsControllers.clear(); + } + } + + /** + * unmanage the drools controller + * + * @param controller + * @return + * @throws IllegalArgumentException + */ + protected void unmanage(DroolsController controller) throws IllegalArgumentException { + if (controller == null) { + throw new IllegalArgumentException("No controller provided"); + } + + if (!controller.isBrained()) { + logger.info("Drools Controller is NOT OPERATIONAL - nothing to destroy"); + return; + } + + String controllerId = controller.getGroupId() + ":" + controller.getArtifactId(); + synchronized(this) { + if (!this.droolsControllers.containsKey(controllerId)) { + return; + } + + droolsControllers.remove(controllerId); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void shutdown(DroolsController controller) throws IllegalArgumentException { + this.unmanage(controller); + controller.shutdown(); + } + + /** + * {@inheritDoc} + */ + @Override + public void shutdown() { + List<DroolsController> controllers = this.inventory(); + for (DroolsController controller: controllers) { + controller.shutdown(); + } + + synchronized(this) { + this.droolsControllers.clear(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public DroolsController get(String groupId, + String artifactId, + String version) + throws IllegalArgumentException, IllegalStateException { + + if (groupId == null || artifactId == null || + groupId.isEmpty() || artifactId.isEmpty()) { + throw new IllegalArgumentException("Missing maven coordinates: " + + groupId + ":" + artifactId); + } + + String controllerId = groupId + ":" + artifactId; + + synchronized(this) { + if (this.droolsControllers.containsKey(controllerId)) { + return droolsControllers.get(controllerId); + } else { + throw new IllegalStateException("DroolController for " + + controllerId + " not found"); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public List<DroolsController> inventory() { + List<DroolsController> controllers = + new ArrayList<DroolsController>(this.droolsControllers.values()); + return controllers; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("IndexedDroolsControllerFactory [#droolsControllers=").append(droolsControllers.size()) + .append("]"); + return builder.toString(); + } + +} diff --git a/policy-management/src/main/java/org/onap/policy/drools/controller/internal/MavenDroolsController.java b/policy-management/src/main/java/org/onap/policy/drools/controller/internal/MavenDroolsController.java new file mode 100644 index 00000000..db62f782 --- /dev/null +++ b/policy-management/src/main/java/org/onap/policy/drools/controller/internal/MavenDroolsController.java @@ -0,0 +1,910 @@ +/*- + * ============LICENSE_START======================================================= + * policy-management + * ================================================================================ + * Copyright (C) 2017 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.drools.controller.internal; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.collections4.queue.CircularFifoQueue; +import org.drools.core.ClassObjectFilter; +import org.kie.api.definition.KiePackage; +import org.kie.api.definition.rule.Query; +import org.kie.api.runtime.KieSession; +import org.kie.api.runtime.rule.FactHandle; +import org.kie.api.runtime.rule.QueryResults; +import org.kie.api.runtime.rule.QueryResultsRow; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.onap.policy.drools.controller.DroolsController; +import org.onap.policy.drools.core.PolicyContainer; +import org.onap.policy.drools.core.PolicySession; +import org.onap.policy.drools.core.jmx.PdpJmx; +import org.onap.policy.drools.event.comm.TopicSink; +import org.onap.policy.drools.protocol.coders.EventProtocolCoder; +import org.onap.policy.drools.protocol.coders.JsonProtocolFilter; +import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration; +import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration.CustomGsonCoder; +import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration.CustomJacksonCoder; +import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration.PotentialCoderFilter; +import org.onap.policy.drools.utils.ReflectionUtil; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Maven-based Drools Controller that interacts with the + * policy-core PolicyContainer and PolicySession to manage + * Drools containers instantiated using Maven. + */ +public class MavenDroolsController implements DroolsController { + + /** + * logger + */ + private static Logger logger = LoggerFactory.getLogger(MavenDroolsController.class); + + /** + * Policy Container, the access object to the policy-core layer + */ + @JsonIgnore + protected final PolicyContainer policyContainer; + + /** + * alive status of this drools controller, + * reflects invocation of start()/stop() only + */ + protected volatile boolean alive = false; + + /** + * locked status of this drools controller, + * reflects if i/o drools related operations are permitted, + * more specifically: offer() and deliver(). + * It does not affect the ability to start and stop + * underlying drools infrastructure + */ + protected volatile boolean locked = false; + + /** + * list of topics, each with associated decoder classes, each + * with a list of associated filters. + */ + protected List<TopicCoderFilterConfiguration> decoderConfigurations; + + /** + * list of topics, each with associated encoder classes, each + * with a list of associated filters. + */ + protected List<TopicCoderFilterConfiguration> encoderConfigurations; + + /** + * recent source events processed + */ + protected final CircularFifoQueue<Object> recentSourceEvents = new CircularFifoQueue<Object>(10); + + /** + * recent sink events processed + */ + protected final CircularFifoQueue<String> recentSinkEvents = new CircularFifoQueue<String>(10); + + /** + * original Drools Model/Rules classloader hash + */ + protected int modelClassLoaderHash; + + /** + * Expanded version of the constructor + * + * @param groupId maven group id + * @param artifactId maven artifact id + * @param version maven version + * @param decoderConfiguration list of topic -> decoders -> filters mapping + * @param encoderConfiguration list of topic -> encoders -> filters mapping + * + * @throws IllegalArgumentException invalid arguments passed in + */ + public MavenDroolsController(String groupId, + String artifactId, + String version, + List<TopicCoderFilterConfiguration> decoderConfigurations, + List<TopicCoderFilterConfiguration> encoderConfigurations) + throws IllegalArgumentException { + + if (logger.isInfoEnabled()) + logger.info("DROOLS CONTROLLER: instantiation " + this + + " -> {" + groupId + ":" + artifactId + ":" + version + "}"); + + if (groupId == null || artifactId == null || version == null || + groupId.isEmpty() || artifactId.isEmpty() || version.isEmpty()) { + throw new IllegalArgumentException("Missing maven coordinates: " + + groupId + ":" + artifactId + ":" + + version); + } + + this.policyContainer= new PolicyContainer(groupId, artifactId, version); + this.init(decoderConfigurations, encoderConfigurations); + + if (logger.isInfoEnabled()) + logger.info("DROOLS CONTROLLER: instantiation completed " + this); + } + + /** + * init encoding/decoding configuration + * @param decoderConfiguration list of topic -> decoders -> filters mapping + * @param encoderConfiguration list of topic -> encoders -> filters mapping + */ + protected void init(List<TopicCoderFilterConfiguration> decoderConfigurations, + List<TopicCoderFilterConfiguration> encoderConfigurations) { + + this.decoderConfigurations = decoderConfigurations; + this.encoderConfigurations = encoderConfigurations; + + this.initCoders(decoderConfigurations, true); + this.initCoders(encoderConfigurations, false); + + this.modelClassLoaderHash = this.policyContainer.getClassLoader().hashCode(); + } + + /** + * {@inheritDoc} + */ + @Override + public void updateToVersion(String newGroupId, String newArtifactId, String newVersion, + List<TopicCoderFilterConfiguration> decoderConfigurations, + List<TopicCoderFilterConfiguration> encoderConfigurations) + throws IllegalArgumentException, LinkageError, Exception { + + if (logger.isInfoEnabled()) + logger.info("UPDATE-TO-VERSION: " + this + " -> {" + newGroupId + ":" + newArtifactId + ":" + newVersion + "}"); + + if (newGroupId == null || newArtifactId == null || newVersion == null || + newGroupId.isEmpty() || newArtifactId.isEmpty() || newVersion.isEmpty()) { + throw new IllegalArgumentException("Missing maven coordinates: " + + newGroupId + ":" + newArtifactId + ":" + + newVersion); + } + + if (newGroupId.equalsIgnoreCase(DroolsController.NO_GROUP_ID) || + newArtifactId.equalsIgnoreCase(DroolsController.NO_ARTIFACT_ID) || + newVersion.equalsIgnoreCase(DroolsController.NO_VERSION)) { + throw new IllegalArgumentException("BRAINLESS maven coordinates provided: " + + newGroupId + ":" + newArtifactId + ":" + + newVersion); + } + + if (newGroupId.equalsIgnoreCase(this.getGroupId()) && + newArtifactId.equalsIgnoreCase(this.getArtifactId()) && + newVersion.equalsIgnoreCase(this.getVersion())) { + logger.warn("Al in the right version: " + newGroupId + ":" + + newArtifactId + ":" + newVersion + " vs. " + this); + return; + } + + if (!newGroupId.equalsIgnoreCase(this.getGroupId()) || + !newArtifactId.equalsIgnoreCase(this.getArtifactId())) { + throw new IllegalArgumentException("Group ID and Artifact ID maven coordinates must be identical for the upgrade: " + + newGroupId + ":" + newArtifactId + ":" + + newVersion + " vs. " + this); + } + + /* upgrade */ + String messages = this.policyContainer.updateToVersion(newVersion); + if (logger.isWarnEnabled()) + logger.warn(this + "UPGRADE results: " + messages); + + /* + * If all sucessful (can load new container), now we can remove all coders from previous sessions + */ + this.removeCoders(); + + /* + * add the new coders + */ + this.init(decoderConfigurations, encoderConfigurations); + + if (logger.isInfoEnabled()) + logger.info("UPDATE-TO-VERSION: completed " + this); + } + + /** + * initialize decoders for all the topics supported by this controller + * Note this is critical to be done after the Policy Container is + * instantiated to be able to fetch the corresponding classes. + * + * @param decoderConfiguration list of topic -> decoders -> filters mapping + */ + protected void initCoders(List<TopicCoderFilterConfiguration> coderConfigurations, + boolean decoder) + throws IllegalArgumentException { + + if (logger.isInfoEnabled()) + logger.info("INIT-CODERS: " + this); + + if (coderConfigurations == null) { + return; + } + + + for (TopicCoderFilterConfiguration coderConfig: coderConfigurations) { + String topic = coderConfig.getTopic(); + + CustomGsonCoder customGsonCoder = coderConfig.getCustomGsonCoder(); + if (coderConfig.getCustomGsonCoder() != null && + coderConfig.getCustomGsonCoder().getClassContainer() != null && + !coderConfig.getCustomGsonCoder().getClassContainer().isEmpty()) { + + String customGsonCoderClass = coderConfig.getCustomGsonCoder().getClassContainer(); + if (!ReflectionUtil.isClass(this.policyContainer.getClassLoader(), + customGsonCoderClass)) { + logger.error(customGsonCoderClass + " cannot be retrieved"); + throw new IllegalArgumentException(customGsonCoderClass + " cannot be retrieved"); + } else { + if (logger.isInfoEnabled()) + logger.info("CLASS FETCHED " + customGsonCoderClass); + } + } + + CustomJacksonCoder customJacksonCoder = coderConfig.getCustomJacksonCoder(); + if (coderConfig.getCustomJacksonCoder() != null && + coderConfig.getCustomJacksonCoder().getClassContainer() != null && + !coderConfig.getCustomJacksonCoder().getClassContainer().isEmpty()) { + + String customJacksonCoderClass = coderConfig.getCustomJacksonCoder().getClassContainer(); + if (!ReflectionUtil.isClass(this.policyContainer.getClassLoader(), + customJacksonCoderClass)) { + logger.error(customJacksonCoderClass + " cannot be retrieved"); + throw new IllegalArgumentException(customJacksonCoderClass + " cannot be retrieved"); + } else { + if (logger.isInfoEnabled()) + logger.info("CLASS FETCHED " + customJacksonCoderClass); + } + } + + List<PotentialCoderFilter> coderFilters = coderConfig.getCoderFilters(); + if (coderFilters == null || coderFilters.isEmpty()) { + continue; + } + + for (PotentialCoderFilter coderFilter : coderFilters) { + String potentialCodedClass = coderFilter.getCodedClass(); + JsonProtocolFilter protocolFilter = coderFilter.getFilter(); + + if (!ReflectionUtil.isClass(this.policyContainer.getClassLoader(), + potentialCodedClass)) { + logger.error(potentialCodedClass + " cannot be retrieved"); + throw new IllegalArgumentException(potentialCodedClass + " cannot be retrieved"); + } else { + if (logger.isInfoEnabled()) + logger.info("CLASS FETCHED " + potentialCodedClass); + } + + if (decoder) + EventProtocolCoder.manager.addDecoder(this.getGroupId(), this.getArtifactId(), + topic, potentialCodedClass, protocolFilter, + customGsonCoder, + customJacksonCoder, + this.policyContainer.getClassLoader().hashCode()); + else + EventProtocolCoder.manager.addEncoder(this.getGroupId(), this.getArtifactId(), + topic, potentialCodedClass, protocolFilter, + customGsonCoder, + customJacksonCoder, + this.policyContainer.getClassLoader().hashCode()); + } + } + } + + + /** + * remove decoders. + */ + protected void removeDecoders() + throws IllegalArgumentException { + if (logger.isInfoEnabled()) + logger.info("REMOVE-DECODERS: " + this); + + if (this.decoderConfigurations == null) { + return; + } + + + for (TopicCoderFilterConfiguration coderConfig: decoderConfigurations) { + String topic = coderConfig.getTopic(); + EventProtocolCoder.manager.removeDecoders + (this.getGroupId(), this.getArtifactId(), topic); + } + } + + /** + * remove decoders. + */ + protected void removeEncoders() + throws IllegalArgumentException { + + if (logger.isInfoEnabled()) + logger.info("REMOVE-ENCODERS: " + this); + + if (this.encoderConfigurations == null) + return; + + + for (TopicCoderFilterConfiguration coderConfig: encoderConfigurations) { + String topic = coderConfig.getTopic(); + EventProtocolCoder.manager.removeEncoders + (this.getGroupId(), this.getArtifactId(), topic); + } + } + + + /** + * {@inheritDoc} + */ + @Override + public boolean ownsCoder(Class<? extends Object> coderClass, int modelHash) throws IllegalStateException { + if (!ReflectionUtil.isClass + (this.policyContainer.getClassLoader(), coderClass.getCanonicalName())) { + logger.error(this + coderClass.getCanonicalName() + " cannot be retrieved. "); + return false; + } + + if (modelHash == this.modelClassLoaderHash) { + if (logger.isInfoEnabled()) + logger.info(coderClass.getCanonicalName() + + this + " class loader matches original drools controller rules classloader " + + coderClass.getClassLoader()); + return true; + } else { + if (logger.isWarnEnabled()) + logger.warn(this + coderClass.getCanonicalName() + " class loaders don't match " + + coderClass.getClassLoader() + " vs " + + this.policyContainer.getClassLoader()); + return false; + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean start() { + + if (logger.isInfoEnabled()) + logger.info("START: " + this); + + synchronized (this) { + if (this.alive) + return true; + + this.alive = true; + } + + return this.policyContainer.start(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean stop() { + + logger.info("STOP: " + this); + + synchronized (this) { + if (!this.alive) + return true; + + this.alive = false; + } + + return this.policyContainer.stop(); + } + + + /** + * {@inheritDoc} + */ + @Override + public void shutdown() throws IllegalStateException { + logger.info("{}: SHUTDOWN", this); + + try { + this.stop(); + this.removeCoders(); + } catch (Exception e) { + logger.error("{} SHUTDOWN FAILED because of {}", this, e.getMessage(), e); + } finally { + this.policyContainer.shutdown(); + } + + } + + + /** + * {@inheritDoc} + */ + @Override + public void halt() throws IllegalStateException { + logger.info("{}: HALT", this); + + try { + this.stop(); + this.removeCoders(); + } catch (Exception e) { + logger.error("{} HALT FAILED because of {}", this, e.getMessage(), e); + } finally { + this.policyContainer.destroy(); + } + } + + /** + * removes this drools controllers and encoders and decoders from operation + */ + protected void removeCoders() { + logger.info("{}: REMOVE-CODERS", this); + + try { + this.removeDecoders(); + } catch (IllegalArgumentException e) { + logger.error("{} REMOVE-DECODERS FAILED because of {}", this, e.getMessage(), e); + } + + try { + this.removeEncoders(); + } catch (IllegalArgumentException e) { + logger.error("{} REMOVE-ENCODERS FAILED because of {}", this, e.getMessage(), e); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isAlive() { + return this.alive; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean offer(String topic, String event) { + logger.debug("{}: OFFER: {} <- {}", this, topic, event); + + if (this.locked) + return true; + + if (!this.alive) + return true; + + // 0. Check if the policy container has any sessions + + if (this.policyContainer.getPolicySessions().size() <= 0) { + // no sessions + return true; + } + + // 1. Now, check if this topic has a decoder: + + if (!EventProtocolCoder.manager.isDecodingSupported(this.getGroupId(), + this.getArtifactId(), + topic)) { + + logger.warn("{}: DECODING-UNSUPPORTED {}:{}:{}", this, + topic, this.getGroupId(), this.getArtifactId()); + return true; + } + + // 2. Decode + + Object anEvent; + try { + anEvent = EventProtocolCoder.manager.decode(this.getGroupId(), + this.getArtifactId(), + topic, + event); + } catch (UnsupportedOperationException uoe) { + logger.debug("{}: DECODE FAILED: {} <- {} because of {}", this, topic, + event, uoe.getMessage(), uoe); + return true; + } catch (Exception e) { + logger.warn("{}: DECODE FAILED: {} <- {} because of {}", this, topic, + event, e.getMessage(), e); + return true; + } + + synchronized(this.recentSourceEvents) { + this.recentSourceEvents.add(anEvent); + } + + // increment event count for Nagios monitoring + PdpJmx.getInstance().updateOccured(); + + // Broadcast + + if (logger.isInfoEnabled()) + logger.info(this + "BROADCAST-INJECT of " + event + " FROM " + topic + " INTO " + this.policyContainer.getName()); + + if (!this.policyContainer.insertAll(anEvent)) + logger.warn(this + "Failed to inject into PolicyContainer " + this.getSessionNames()); + + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean deliver(TopicSink sink, Object event) + throws IllegalArgumentException, + IllegalStateException, + UnsupportedOperationException { + + if (logger.isInfoEnabled()) + logger.info(this + "DELIVER: " + event + " FROM " + this + " TO " + sink); + + if (sink == null) + throw new IllegalArgumentException + (this + " invalid sink"); + + if (event == null) + throw new IllegalArgumentException + (this + " invalid event"); + + if (this.locked) + throw new IllegalStateException + (this + " is locked"); + + if (!this.alive) + throw new IllegalStateException + (this + " is stopped"); + + String json = + EventProtocolCoder.manager.encode(sink.getTopic(), event, this); + + synchronized(this.recentSinkEvents) { + this.recentSinkEvents.add(json); + } + + return sink.send(json); + + } + + /** + * {@inheritDoc} + */ + @Override + public String getVersion() { + return this.policyContainer.getVersion(); + } + + /** + * {@inheritDoc} + */ + @Override + public String getArtifactId() { + return this.policyContainer.getArtifactId(); + } + + /** + * {@inheritDoc} + */ + @Override + public String getGroupId() { + return this.policyContainer.getGroupId(); + } + + /** + * @return the modelClassLoaderHash + */ + public int getModelClassLoaderHash() { + return modelClassLoaderHash; + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized boolean lock() { + logger.info("LOCK: " + this); + + this.locked = true; + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized boolean unlock() { + logger.info("UNLOCK: " + this); + + this.locked = false; + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isLocked() { + return this.locked; + } + + /** + * {@inheritDoc} + */ + @JsonIgnore + public PolicyContainer getContainer() { + return this.policyContainer; + } + + /** + * {@inheritDoc} + */ + @JsonProperty("sessions") + @Override + public List<String> getSessionNames() { + return getSessionNames(true); + } + + /** + * {@inheritDoc} + */ + @JsonProperty("sessionCoordinates") + @Override + public List<String> getCanonicalSessionNames() { + return getSessionNames(false); + } + + /** + * get session names + * @param abbreviated true for the short form, otherwise the long form + * @return session names + */ + protected List<String> getSessionNames(boolean abbreviated) { + List<String> sessionNames = new ArrayList<String>(); + try { + for (PolicySession session: this.policyContainer.getPolicySessions()) { + if (abbreviated) + sessionNames.add(session.getName()); + else + sessionNames.add(session.getFullName()); + } + } catch (Exception e) { + logger.warn("Can't retrieve CORE sessions: " + e.getMessage(), e); + sessionNames.add(e.getMessage()); + } + return sessionNames; + } + + /** + * provides the underlying core layer container sessions + * + * @return the attached Policy Container + */ + protected List<PolicySession> getSessions() { + List<PolicySession> sessions = new ArrayList<PolicySession>(); + sessions.addAll(this.policyContainer.getPolicySessions()); + return sessions; + } + + /** + * provides the underlying core layer container session with name sessionName + * + * @param sessionName session name + * @return the attached Policy Container + * @throws IllegalArgumentException when an invalid session name is provided + * @throws IllegalStateException when the drools controller is in an invalid state + */ + protected PolicySession getSession(String sessionName) { + if (sessionName == null || sessionName.isEmpty()) + throw new IllegalArgumentException("A Session Name must be provided"); + + List<PolicySession> sessions = this.getSessions(); + for (PolicySession session : sessions) { + if (sessionName.equals(session.getName()) || sessionName.equals(session.getName())) + return session; + } + + throw new IllegalArgumentException("Invalid Session Name: " + sessionName); + } + + /** + * {@inheritDoc} + */ + @Override + public Map<String,Integer> factClassNames(String sessionName) throws IllegalArgumentException { + if (sessionName == null || sessionName.isEmpty()) + throw new IllegalArgumentException("Invalid Session Name: " + sessionName); + + // List<String> classNames = new ArrayList<>(); + Map<String,Integer> classNames = new HashMap<>(); + + PolicySession session = getSession(sessionName); + KieSession kieSession = session.getKieSession(); + + Collection<FactHandle> facts = session.getKieSession().getFactHandles(); + for (FactHandle fact : facts) { + try { + String className = kieSession.getObject(fact).getClass().getName(); + if (classNames.containsKey(className)) + classNames.put(className, classNames.get(className) + 1); + else + classNames.put(className, 1); + } catch (Exception e) { + if (logger.isInfoEnabled()) + logger.info("Object cannot be retrieved from fact: " + fact); + } + } + + return classNames; + } + + /** + * {@inheritDoc} + */ + @Override + public long factCount(String sessionName) throws IllegalArgumentException { + if (sessionName == null || sessionName.isEmpty()) + throw new IllegalArgumentException("Invalid Session Name: " + sessionName); + + PolicySession session = getSession(sessionName); + return session.getKieSession().getFactCount(); + } + + /** + * {@inheritDoc} + */ + @Override + public List<Object> facts(String sessionName, String className, boolean delete) { + if (sessionName == null || sessionName.isEmpty()) + throw new IllegalArgumentException("Invalid Session Name: " + sessionName); + + if (className == null || className.isEmpty()) + throw new IllegalArgumentException("Invalid Class Name: " + className); + + Class<?> factClass = + ReflectionUtil.fetchClass(this.policyContainer.getClassLoader(), className); + if (factClass == null) + throw new IllegalArgumentException("Class cannot be fetched in model's classloader: " + className); + + PolicySession session = getSession(sessionName); + KieSession kieSession = session.getKieSession(); + + List<Object> factObjects = new ArrayList<>(); + + Collection<FactHandle> factHandles = kieSession.getFactHandles(new ClassObjectFilter(factClass)); + for (FactHandle factHandle : factHandles) { + try { + factObjects.add(kieSession.getObject(factHandle)); + if (delete) + kieSession.delete(factHandle); + } catch (Exception e) { + if (logger.isInfoEnabled()) + logger.info("Object cannot be retrieved from fact: " + factHandle); + } + } + + return factObjects; + } + + /** + * {@inheritDoc} + */ + @Override + public List<Object> factQuery(String sessionName, String queryName, String queriedEntity, boolean delete, Object... queryParams) { + if (sessionName == null || sessionName.isEmpty()) + throw new IllegalArgumentException("Invalid Session Name: " + sessionName); + + if (queryName == null || queryName.isEmpty()) + throw new IllegalArgumentException("Invalid Query Name: " + queryName); + + if (queriedEntity == null || queriedEntity.isEmpty()) + throw new IllegalArgumentException("Invalid Queried Entity: " + queriedEntity); + + PolicySession session = getSession(sessionName); + KieSession kieSession = session.getKieSession(); + + boolean found = false; + for (KiePackage kiePackage : kieSession.getKieBase().getKiePackages()) { + for (Query q : kiePackage.getQueries()) { + if (q.getName() != null && q.getName().equals(queryName)) { + found = true; + break; + } + } + } + if (!found) + throw new IllegalArgumentException("Invalid Query Name: " + queryName); + + List<Object> factObjects = new ArrayList<>(); + + QueryResults queryResults = kieSession.getQueryResults(queryName, queryParams); + for (QueryResultsRow row : queryResults) { + try { + factObjects.add(row.get(queriedEntity)); + if (delete) + kieSession.delete(row.getFactHandle(queriedEntity)); + } catch (Exception e) { + if (logger.isInfoEnabled()) + logger.info("Object cannot be retrieved from fact: " + row); + } + } + + return factObjects; + } + + /** + * {@inheritDoc} + */ + @Override + public Class<?> fetchModelClass(String className) throws IllegalStateException { + Class<?> modelClass = + ReflectionUtil.fetchClass(this.policyContainer.getClassLoader(), className); + return modelClass; + } + + /** + * @return the recentSourceEvents + */ + @Override + public Object[] getRecentSourceEvents() { + synchronized(this.recentSourceEvents) { + Object[] events = new Object[recentSourceEvents.size()]; + return recentSourceEvents.toArray(events); + } + } + + /** + * @return the recentSinkEvents + */ + @Override + public String[] getRecentSinkEvents() { + synchronized(this.recentSinkEvents) { + String[] events = new String[recentSinkEvents.size()]; + return recentSinkEvents.toArray(events); + } + } + + + /** + * {@inheritDoc} + */ + @Override + public boolean isBrained() { + return true; + } + + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("MavenDroolsController [policyContainer=") + .append((policyContainer != null) ? policyContainer.getName() : "NULL").append(":") + .append(", alive=") + .append(alive).append(", locked=") + .append(", modelClassLoaderHash=").append(modelClassLoaderHash).append("]"); + return builder.toString(); + } + +} diff --git a/policy-management/src/main/java/org/onap/policy/drools/controller/internal/NullDroolsController.java b/policy-management/src/main/java/org/onap/policy/drools/controller/internal/NullDroolsController.java new file mode 100644 index 00000000..3fef6fa9 --- /dev/null +++ b/policy-management/src/main/java/org/onap/policy/drools/controller/internal/NullDroolsController.java @@ -0,0 +1,262 @@ +/*- + * ============LICENSE_START======================================================= + * policy-management + * ================================================================================ + * Copyright (C) 2017 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.drools.controller.internal; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.onap.policy.drools.controller.DroolsController; +import org.onap.policy.drools.core.PolicyContainer; +import org.onap.policy.drools.event.comm.TopicSink; +import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration; + +/** + * no-op Drools Controller + */ +public class NullDroolsController implements DroolsController { + + /** + * {@inheritDoc} + */ + @Override + public boolean start() throws IllegalStateException { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean stop() throws IllegalStateException { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public void shutdown() throws IllegalStateException { + return; + } + + /** + * {@inheritDoc} + */ + @Override + public void halt() throws IllegalStateException { + return; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isAlive() { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean lock() { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean unlock() { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isLocked() { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public String getGroupId() { + return NO_GROUP_ID; + } + + /** + * {@inheritDoc} + */ + @Override + public String getArtifactId() { + return NO_ARTIFACT_ID; + } + + /** + * {@inheritDoc} + */ + @Override + public String getVersion() { + return NO_VERSION; + } + + /** + * {@inheritDoc} + */ + @Override + public List<String> getSessionNames() { + return new ArrayList<String>(); + } + + /** + * {@inheritDoc} + */ + @Override + public List<String> getCanonicalSessionNames() { + return new ArrayList<String>(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean offer(String topic, String event) { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean deliver(TopicSink sink, Object event) + throws IllegalArgumentException, IllegalStateException, UnsupportedOperationException { + throw new IllegalStateException(this.getClass().getCanonicalName() + " invoked"); + } + + /** + * {@inheritDoc} + */ + @Override + public Object[] getRecentSourceEvents() { + return new String[0]; + } + + /** + * {@inheritDoc} + */ + @Override + public PolicyContainer getContainer() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public String[] getRecentSinkEvents() { + return new String[0]; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean ownsCoder(Class<? extends Object> coderClass, int modelHash) throws IllegalStateException { + throw new IllegalStateException(this.getClass().getCanonicalName() + " invoked"); + } + + /** + * {@inheritDoc} + */ + @Override + public Class<?> fetchModelClass(String className) throws IllegalArgumentException { + throw new IllegalArgumentException(this.getClass().getCanonicalName() + " invoked"); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isBrained() { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("NullDroolsController []"); + return builder.toString(); + } + + /** + * {@inheritDoc} + */ + @Override + public void updateToVersion(String newGroupId, String newArtifactId, String newVersion, + List<TopicCoderFilterConfiguration> decoderConfigurations, + List<TopicCoderFilterConfiguration> encoderConfigurations) + throws IllegalArgumentException, LinkageError, Exception { + throw new IllegalArgumentException(this.getClass().getCanonicalName() + " invoked"); + } + + /** + * {@inheritDoc} + */ + @Override + public Map<String, Integer> factClassNames(String sessionName) + throws IllegalArgumentException { + return new HashMap<String,Integer>(); + } + + /** + * {@inheritDoc} + */ + @Override + public long factCount(String sessionName) throws IllegalArgumentException { + return 0; + } + + /** + * {@inheritDoc} + */ + @Override + public List<Object> facts(String sessionName, String className, boolean delete) { + return new ArrayList<Object>(); + } + + /** + * {@inheritDoc} + */ + @Override + public List<Object> factQuery(String sessionName, String queryName, + String queriedEntity, + boolean delete, Object... queryParams) { + return new ArrayList<Object>(); + } +} diff --git a/policy-management/src/main/java/org/onap/policy/drools/features/PolicyControllerFeatureAPI.java b/policy-management/src/main/java/org/onap/policy/drools/features/PolicyControllerFeatureAPI.java new file mode 100644 index 00000000..9d166f58 --- /dev/null +++ b/policy-management/src/main/java/org/onap/policy/drools/features/PolicyControllerFeatureAPI.java @@ -0,0 +1,222 @@ +/*- + * ============LICENSE_START======================================================= + * policy-management + * ================================================================================ + * Copyright (C) 2017 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.drools.features; + +import java.util.Properties; + +import org.onap.policy.drools.event.comm.Topic.CommInfrastructure; +import org.onap.policy.drools.system.PolicyController; +import org.onap.policy.drools.utils.OrderedService; +import org.onap.policy.drools.utils.OrderedServiceImpl; + +public interface PolicyControllerFeatureAPI extends OrderedService { + + /** + * called before creating a controller with name 'name' and + * properties 'properties' + * + * @param name name of the the controller + * @param properties configuration properties + * + * @return a policy controller. A take over of the creation operation + * is performed by returning a non-null policy controller. + * 'null' indicates that no take over has taken place, and processing should + * continue to the next feature provider. + */ + public default PolicyController beforeCreate(String name, Properties properties) {return null;} + + /** + * called after creating a controller with name 'name' + * + * @param controller controller + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise. + */ + public default boolean afterCreate(PolicyController controller) {return false;} + + /** + * intercept before the Policy Controller is started. + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise. + */ + public default boolean beforeStart(PolicyController controller) {return false;} + + /** + * intercept after the Policy Controller is started. + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise. + */ + public default boolean afterStart(PolicyController controller) {return false;} + + /** + * intercept before the Policy Controller is stopped. + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise.. + */ + public default boolean beforeStop(PolicyController controller) {return false;} + + /** + * intercept after the Policy Controller is stopped + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise.d. + */ + public default boolean afterStop(PolicyController controller) {return false;} + + /** + * intercept before the Policy Controller is locked + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise. + */ + public default boolean beforeLock(PolicyController controller) {return false;} + + /** + * intercept after the Policy Controller is locked + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise.. + */ + public default boolean afterLock(PolicyController controller) {return false;} + + /** + * intercept before the Policy Controller is locked + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise. + */ + public default boolean beforeUnlock(PolicyController controller) {return false;} + + /** + * intercept after the Policy Controller is locked + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise. + */ + public default boolean afterUnlock(PolicyController controller) {return false;} + + /** + * intercept before the Policy Controller is shut down + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise.. + */ + public default boolean beforeShutdown(PolicyController controller) {return false;} + + /** + * called after the Policy Controller is shut down + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise. + */ + public default boolean afterShutdown(PolicyController controller) {return false;} + + /** + * intercept before the Policy Controller is halted + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise.. + */ + public default boolean beforeHalt(PolicyController controller) {return false;} + + /** + * called after the Policy Controller is halted + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise. + */ + public default boolean afterHalt(PolicyController controller) {return false;} + + + /** + * intercept before the Policy Controller is offered an event + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise. + */ + public default boolean beforeOffer(PolicyController controller, + CommInfrastructure protocol, + String topic, + String event) {return false;} + + /** + * called after the Policy Controller processes an event offer + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise. + */ + public default boolean afterOffer(PolicyController controller, + CommInfrastructure protocol, + String topic, + String event, + boolean success) {return false;} + + /** + * intercept before the Policy Controller delivers (posts) an event + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise. + */ + public default boolean beforeDeliver(PolicyController controller, + CommInfrastructure protocol, + String topic, + Object event) {return false;} + + /** + * called after the Policy Controller delivers (posts) an event + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise. + */ + public default boolean afterDeliver(PolicyController controller, + CommInfrastructure protocol, + String topic, + Object event, + boolean success) {return false;} + + + /** + * Feature providers implementing this interface + */ + public static final OrderedServiceImpl<PolicyControllerFeatureAPI> providers = + new OrderedServiceImpl<PolicyControllerFeatureAPI>(PolicyControllerFeatureAPI.class); +} diff --git a/policy-management/src/main/java/org/onap/policy/drools/features/PolicyEngineFeatureAPI.java b/policy-management/src/main/java/org/onap/policy/drools/features/PolicyEngineFeatureAPI.java new file mode 100644 index 00000000..eb65c706 --- /dev/null +++ b/policy-management/src/main/java/org/onap/policy/drools/features/PolicyEngineFeatureAPI.java @@ -0,0 +1,202 @@ +/*- + * ============LICENSE_START======================================================= + * policy-engine + * ================================================================================ + * Copyright (C) 2017 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.drools.features; + +import java.util.Properties; + +import org.onap.policy.drools.system.PolicyEngine; +import org.onap.policy.drools.utils.OrderedService; +import org.onap.policy.drools.utils.OrderedServiceImpl; + +/** + * Policy Engine Feature API. + * Provides Interception Points during the Policy Engine lifecycle. + */ +public interface PolicyEngineFeatureAPI extends OrderedService { + + /** + * intercept before the Policy Engine is commanded to boot. + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise. + */ + public default boolean beforeBoot(PolicyEngine engine, String cliArgs[]) {return false;}; + + /** + * intercept after the Policy Engine is booted. + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise. + */ + public default boolean afterBoot(PolicyEngine engine) {return false;}; + + /** + * intercept before the Policy Engine is configured. + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise. + */ + public default boolean beforeConfigure(PolicyEngine engine, Properties properties) {return false;}; + + /** + * intercept after the Policy Engine is configured. + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise. + */ + public default boolean afterConfigure(PolicyEngine engine) {return false;}; + + /** + * intercept before the Policy Engine goes active. + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise. + */ + public default boolean beforeActivate(PolicyEngine engine) {return false;}; + + /** + * intercept after the Policy Engine goes active. + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise. + */ + public default boolean afterActivate(PolicyEngine engine) {return false;}; + + /** + * intercept before the Policy Engine goes standby. + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise. + */ + public default boolean beforeDeactivate(PolicyEngine engine) {return false;}; + + /** + * intercept after the Policy Engine goes standby. + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise. + */ + public default boolean afterDeactivate(PolicyEngine engine) {return false;}; + + /** + * intercept before the Policy Engine is started. + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise. + */ + public default boolean beforeStart(PolicyEngine engine) {return false;}; + + /** + * intercept after the Policy Engine is started. + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise. + */ + public default boolean afterStart(PolicyEngine engine) {return false;}; + + /** + * intercept before the Policy Engine is stopped. + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise.. + */ + public default boolean beforeStop(PolicyEngine engine) {return false;}; + + /** + * intercept after the Policy Engine is stopped + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise.d. + */ + public default boolean afterStop(PolicyEngine engine) {return false;}; + + /** + * intercept before the Policy Engine is locked + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise. + */ + public default boolean beforeLock(PolicyEngine engine) {return false;}; + + /** + * intercept after the Policy Engine is locked + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise.. + */ + public default boolean afterLock(PolicyEngine engine) {return false;}; + + /** + * intercept before the Policy Engine is locked + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise. + */ + public default boolean beforeUnlock(PolicyEngine engine) {return false;}; + + /** + * intercept after the Policy Engine is locked + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise. + */ + public default boolean afterUnlock(PolicyEngine engine) {return false;}; + + /** + * intercept the Policy Engine is shut down + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise.. + */ + public default boolean beforeShutdown(PolicyEngine engine){return false;}; + + /** + * called after the Policy Engine is shut down + * + * @return true if this feature intercepts and takes ownership + * of the operation preventing the invocation of + * lower priority features. False, otherwise. + */ + public default boolean afterShutdown(PolicyEngine engine) {return false;}; + + /** + * Feature providers implementing this interface + */ + public static final OrderedServiceImpl<PolicyEngineFeatureAPI> providers = + new OrderedServiceImpl<>(PolicyEngineFeatureAPI.class); +} diff --git a/policy-management/src/main/java/org/onap/policy/drools/persistence/SystemPersistence.java b/policy-management/src/main/java/org/onap/policy/drools/persistence/SystemPersistence.java new file mode 100644 index 00000000..52aac744 --- /dev/null +++ b/policy-management/src/main/java/org/onap/policy/drools/persistence/SystemPersistence.java @@ -0,0 +1,248 @@ +/*- + * ============LICENSE_START======================================================= + * policy-management + * ================================================================================ + * Copyright (C) 2017 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.drools.persistence; + +import java.io.File; +import java.io.FileWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.Properties; + +import org.onap.policy.drools.utils.PropertyUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public interface SystemPersistence { + + /** + * configuration directory + */ + public static String CONFIG_DIR_NAME = "config"; + + /** + * policy controllers suffix + */ + public final static String CONTROLLER_SUFFIX_IDENTIFIER = "-controller"; + + /** + * policy controller properties file suffix + */ + public final static String PROPERTIES_FILE_CONTROLLER_SUFFIX = + CONTROLLER_SUFFIX_IDENTIFIER + ".properties"; + + /** + * policy engine properties file name + */ + public final static String PROPERTIES_FILE_ENGINE = "policy-engine.properties"; + + + /** + * backs up a controller configuration. + * + * @param controllerName the controller name + * @return true if the configuration is backed up + */ + public boolean backupController(String controllerName); + + /** + * persists controller configuration + * + * @param controllerName the controller name + * @param configuration object containing the configuration + * @return true if storage is succesful, false otherwise + * @throws IllegalArgumentException if the configuration cannot be handled by the persistence manager + */ + public boolean storeController(String controllerName, Object configuration) + throws IllegalArgumentException; + + /** + * delete controller configuration + * + * @param controllerName the controller name + * @return true if storage is succesful, false otherwise + */ + public boolean deleteController(String controllerName); + + /** + * get controller properties + * + * @param controllerName controller name + * @return properties for this controller + * @throws IllegalArgumentException if the controller name does not lead to a properties configuration + */ + public Properties getControllerProperties(String controllerName) + throws IllegalArgumentException; + + /** + * get properties by name + * + * @param name + * @return properties + * @throws IllegalArgumentException if the name does not lead to a properties configuration + */ + public Properties getProperties(String name) throws IllegalArgumentException; + + /** + * Persistence Manager. For now it is a file-based properties management, + * In the future, it will probably be DB based, so manager implementation + * will change. + */ + public static final SystemPersistence manager = new SystemPropertiesPersistence(); +} + +/** + * Properties based Persistence + */ +class SystemPropertiesPersistence implements SystemPersistence { + + /** + * logger + */ + private static Logger logger = LoggerFactory.getLogger(SystemPropertiesPersistence.class); + + /** + * backs up the properties-based controller configuration + * @param controllerName the controller name + * @return true if the configuration is backed up in disk or back up does not apply, false otherwise. + */ + @Override + public boolean backupController(String controllerName) { + Path controllerPropertiesPath = + Paths.get(CONFIG_DIR_NAME, controllerName + PROPERTIES_FILE_CONTROLLER_SUFFIX); + + if (Files.exists(controllerPropertiesPath)) { + try { + logger.warn("There is an existing configuration file at " + + controllerPropertiesPath.toString() + + " with contents: " + Files.readAllBytes(controllerPropertiesPath)); + Path controllerPropertiesBakPath = + Paths.get(CONFIG_DIR_NAME, controllerName + + PROPERTIES_FILE_CONTROLLER_SUFFIX + ".bak"); + Files.copy(controllerPropertiesPath, + controllerPropertiesBakPath, StandardCopyOption.REPLACE_EXISTING); + } catch (Exception e) { + logger.warn("{}: cannot be backed up", controllerName, e); + return false; + } + } + + return true; + } + + /** + * persists properties-based controller configuration and makes a backup if necessary + * @param controllerName the controller name + * @return true if the properties has been stored in disk, false otherwise + */ + @Override + public boolean storeController(String controllerName, Object configuration) { + if (!(configuration instanceof Properties)) { + throw new IllegalArgumentException("configuration must be of type properties to be handled by this manager"); + } + + Properties properties = (Properties) configuration; + + Path controllerPropertiesPath = + Paths.get(CONFIG_DIR_NAME, controllerName + PROPERTIES_FILE_CONTROLLER_SUFFIX); + if (Files.exists(controllerPropertiesPath)) { + try { + Properties oldProperties = PropertyUtil.getProperties(controllerPropertiesPath.toFile()); + if (oldProperties.equals(properties)) { + logger.info("A properties file with the same contents already exists for controller " + + controllerName + + ". No action is taken"); + return true; + } else { + this.backupController(controllerName); + } + } catch (Exception e) { + logger.info("{}: no existing Properties", controllerName); + // continue + } + } + + try { + File controllerPropertiesFile = controllerPropertiesPath.toFile(); + FileWriter writer = new FileWriter(controllerPropertiesFile); + properties.store(writer, "Machine created Policy Controller Configuration"); + } catch (Exception e) { + logger.warn("{}: cannot be STORED", controllerName, e); + return false; + } + + return true; + } + + /** + * deletes properties-based controller configuration + * @param controllerName the controller name + * @return true if the properties has been deleted from disk, false otherwise + */ + @Override + public boolean deleteController(String controllerName) { + + Path controllerPropertiesPath = + Paths.get(CONFIG_DIR_NAME, + controllerName + PROPERTIES_FILE_CONTROLLER_SUFFIX); + + if (Files.exists(controllerPropertiesPath)) { + try { + Path controllerPropertiesBakPath = + Paths.get(CONFIG_DIR_NAME, controllerName + + PROPERTIES_FILE_CONTROLLER_SUFFIX + ".bak"); + Files.move(controllerPropertiesPath, + controllerPropertiesBakPath, + StandardCopyOption.REPLACE_EXISTING); + } catch (Exception e) { + logger.warn("{}: cannot be DELETED", controllerName, e); + return false; + } + } + + return true; + } + + @Override + public Properties getControllerProperties(String controllerName) throws IllegalArgumentException { + return this.getProperties(controllerName + CONTROLLER_SUFFIX_IDENTIFIER); + } + + @Override + public Properties getProperties(String name) throws IllegalArgumentException { + Path propertiesPath = + Paths.get(CONFIG_DIR_NAME, name + ".properties"); + + if (!Files.exists(propertiesPath)) { + throw new IllegalArgumentException("properties for " + name + " are not persisted."); + } + + try { + return PropertyUtil.getProperties(propertiesPath.toFile()); + } catch (Exception e) { + logger.warn("{}: can't read properties @ {}", name, propertiesPath); + throw new IllegalArgumentException("can't read properties for " + + name + " @ " + + propertiesPath); + } + } +} diff --git a/policy-management/src/main/java/org/onap/policy/drools/protocol/coders/EventProtocolCoder.java b/policy-management/src/main/java/org/onap/policy/drools/protocol/coders/EventProtocolCoder.java new file mode 100644 index 00000000..762f4476 --- /dev/null +++ b/policy-management/src/main/java/org/onap/policy/drools/protocol/coders/EventProtocolCoder.java @@ -0,0 +1,1393 @@ +/*- + * ============LICENSE_START======================================================= + * policy-management + * ================================================================================ + * Copyright (C) 2017 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.drools.protocol.coders; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.onap.policy.drools.controller.DroolsController; +import org.onap.policy.drools.protocol.coders.EventProtocolCoder.CoderFilters; +import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration.CustomGsonCoder; +import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration.CustomJacksonCoder; +import org.onap.policy.drools.utils.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Coder (Encoder/Decoder) of Events. + */ +public interface EventProtocolCoder { + + public static class CoderFilters { + + /** + * coder class + */ + protected String factClass; + + /** + * filters to apply to the selection of the decodedClass; + */ + protected JsonProtocolFilter filter; + + /** + * classloader hash + */ + protected int modelClassLoaderHash; + + + /** + * constructor + * + * @param codedClass coder class + * @param filter filters to apply + */ + public CoderFilters(String codedClass, JsonProtocolFilter filter, int modelClassLoaderHash) { + this.factClass = codedClass; + this.filter = filter; + this.modelClassLoaderHash = modelClassLoaderHash; + } + + /** + * @return the codedClass + */ + public String getCodedClass() { + return factClass; + } + + /** + * @param codedClass the decodedClass to set + */ + public void setCodedClass(String codedClass) { + this.factClass = codedClass; + } + + /** + * @return the filter + */ + public synchronized JsonProtocolFilter getFilter() { + return filter; + } + + /** + * @param filter the filter to set + */ + public synchronized void setFilter(JsonProtocolFilter filter) { + this.filter = filter; + } + + public int getModelClassLoaderHash() { + return modelClassLoaderHash; + } + + public void setFromClassLoaderHash(int fromClassLoaderHash) { + this.modelClassLoaderHash = fromClassLoaderHash; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("CoderFilters [factClass=").append(factClass).append(", filter=").append(filter) + .append(", modelClassLoaderHash=").append(modelClassLoaderHash).append("]"); + return builder.toString(); + } + + } + + /** + * Adds a Decoder class to decode the protocol over this topic + * + * @param groupId of the controller + * @param artifactId of the controller + * @param topic the topic + * @param eventClass the event class + * @param protocolFilter filters to selectively choose a particular decoder + * when there are multiples + * + * @throw IllegalArgumentException if an invalid parameter is passed + */ + public void addDecoder(String groupId, String artifactId, + String topic, + String eventClass, + JsonProtocolFilter protocolFilter, + CustomGsonCoder customGsonCoder, + CustomJacksonCoder customJacksonCoder, + int modelClassLoaderHash) + throws IllegalArgumentException; + + /** + * removes all decoders associated with the controller id + * @param groupId of the controller + * @param artifactId of the controller + * @param topic of the controller + * + * @throws IllegalArgumentException if invalid arguments have been provided + */ + void removeEncoders(String groupId, String artifactId, String topic) throws IllegalArgumentException; + + /** + * removes decoders associated with the controller id and topic + * @param groupId of the controller + * @param artifactId of the controller + * @param topic the topic + * + * @throws IllegalArgumentException if invalid arguments have been provided + */ + public void removeDecoders(String groupId, String artifactId, String topic) throws IllegalArgumentException; + + /** + * Given a controller id and a topic, it gives back its filters + * + * @param groupId of the controller + * @param artifactId of the controller + * @param topic the topic + * + * return list of decoders + * + * @throw IllegalArgumentException if an invalid parameter is passed + */ + public List<CoderFilters> getDecoderFilters(String groupId, String artifactId, String topic) + throws IllegalArgumentException; + + + /** + * Given a controller id and a topic, it gives back the decoding configuration + * + * @param groupId of the controller + * @param artifactId of the controller + * @param topic the topic + * + * return decoding toolset + * + * @throw IllegalArgumentException if an invalid parameter is passed + */ + public ProtocolCoderToolset getDecoders(String groupId, String artifactId, String topic) + throws IllegalArgumentException; + + /** + * Given a controller id and a topic, it gives back all the decoding configurations + * + * @param groupId of the controller + * @param artifactId of the controller + * @param topic the topic + * + * return decoding toolset + * + * @throw IllegalArgumentException if an invalid parameter is passed + */ + public List<ProtocolCoderToolset> getDecoders(String groupId, String artifactId) + throws IllegalArgumentException; + + + /** + * gets all decoders associated with the group and artifact ids + * @param groupId of the controller + * @param artifactId of the controller + * + * @throws IllegalArgumentException if invalid arguments have been provided + */ + public List<CoderFilters> getDecoderFilters(String groupId, String artifactId) throws IllegalArgumentException; + + + /** + * Given a controller id and a topic, it gives back the classes that implements the encoding + * + * @param groupId of the controller + * @param artifactId of the controller + * @param topic the topic + * + * return list of decoders + * + * @throw IllegalArgumentException if an invalid parameter is passed + */ + public List<CoderFilters> getEncoderFilters(String groupId, String artifactId, String topic) + throws IllegalArgumentException; + + /** + * gets all encoders associated with the group and artifact ids + * @param groupId of the controller + * @param artifactId of the controller + * + * @throws IllegalArgumentException if invalid arguments have been provided + */ + public List<CoderFilters> getEncoderFilters(String groupId, String artifactId) throws IllegalArgumentException; + + /** + * Given a controller id, a topic, and a classname, it gives back the classes that implements the decoding + * + * @param groupId of the controller + * @param artifactId of the controller + * @param topic the topic + * @param classname classname + * + * return list of decoders + * + * @throw IllegalArgumentException if an invalid parameter is passed + */ + public CoderFilters getDecoderFilters(String groupId, String artifactId, String topic, String classname) + throws IllegalArgumentException; + + /** + * is there a decoder supported for the controller id and topic + * + * @param groupId of the controller + * @param artifactId of the controller + * @param topic protocol + * @return true if supported + */ + public boolean isDecodingSupported(String groupId, String artifactId, String topic); + + /** + * Adds a Encoder class to encode the protocol over this topic + * + * @param groupId of the controller + * @param artifactId of the controller + * @param topic the topic + * @param eventClass the event class + * @param protocolFilter filters to selectively choose a particular decoder + * when there are multiples + * + * @throw IllegalArgumentException if an invalid parameter is passed + */ + public void addEncoder(String groupId, String artifactId, String topic, + String eventClass, + JsonProtocolFilter protocolFilter, + CustomGsonCoder customGsonCoder, + CustomJacksonCoder customJacksonCoder, + int modelClassLoaderHash) + throws IllegalArgumentException; + + /** + * is there an encoder supported for the controller id and topic + * + * @param groupId of the controller + * @param artifactId of the controller + * @param topic protocol + * @return true if supported + */ + public boolean isEncodingSupported(String groupId, String artifactId, String topic); + + /** + * get encoder based on coordinates and classname + * + * @param groupId of the controller + * @param artifactId of the controller + * @param topic protocol + * @param json event string + * @return + * @throws IllegalArgumentException invalid arguments passed in + */ + public CoderFilters getEncoderFilters(String groupId, String artifactId, String topic, String classname) + throws IllegalArgumentException; + + /** + * get encoder based on topic and encoded class + * + * @param topic topic + * @param encodedClass encoded class + * @return + * @throws IllegalArgumentException invalid arguments passed in + */ + public List<CoderFilters> getReverseEncoderFilters(String topic, String encodedClass) + throws IllegalArgumentException; + + /** + * gets the identifier of the creator of the encoder + * + * @param topic topic + * @param encodedClass encoded class + * @return a drools controller + * @throws IllegalArgumentException invalid arguments passed in + */ + public DroolsController getDroolsController(String topic, Object encodedClass) + throws IllegalArgumentException; + + /** + * gets the identifier of the creator of the encoder + * + * @param topic topic + * @param encodedClass encoded class + * @return list of drools controllers + * @throws IllegalArgumentException invalid arguments passed in + */ + public List<DroolsController> getDroolsControllers(String topic, Object encodedClass) + throws IllegalArgumentException; + + /** + * decode topic's stringified event (json) to corresponding Event Object. + * + * @param groupId of the controller + * @param artifactId of the controller + * @param topic protocol + * @param json event string + * @return + * @throws IllegalArgumentException invalid arguments passed in + * @throws UnsupportedOperationException if the operation is not supported + * @throws IllegalStateException if the system is in an illegal state + */ + public Object decode(String groupId, String artifactId, String topic, String json) + throws IllegalArgumentException, UnsupportedOperationException, IllegalStateException; + + /** + * encodes topic's stringified event (json) to corresponding Event Object. + * + * @param groupId of the controller + * @param artifactId of the controller + * @param topic protocol + * @param event Object + * + * @throws IllegalArgumentException invalid arguments passed in + */ + public String encode(String groupId, String artifactId, String topic, Object event) + throws IllegalArgumentException, IllegalStateException, UnsupportedOperationException; + + /** + * encodes topic's stringified event (json) to corresponding Event Object. + * + * @param topic topic + * @param event event object + * + * @throws IllegalArgumentException invalid arguments passed in + * @throws UnsupportedOperationException operation cannot be performed + */ + public String encode(String topic, Object event) + throws IllegalArgumentException, IllegalStateException, UnsupportedOperationException; + + /** + * encodes topic's stringified event (json) to corresponding Event Object. + * + * @param topic topic + * @param event event object + * @param droolsController + * + * @throws IllegalArgumentException invalid arguments passed in + * @throws UnsupportedOperationException operation cannot be performed + */ + public String encode(String topic, Object event, DroolsController droolsController) + throws IllegalArgumentException, IllegalStateException, UnsupportedOperationException; + + /** + * singleton reference to the global event protocol coder + */ + public static EventProtocolCoder manager = new MultiplexorEventProtocolCoder(); +} + +/** + * Protocol Coder that does its best attempt to decode/encode, selecting the best + * class and best fitted json parsing tools. + */ +class MultiplexorEventProtocolCoder implements EventProtocolCoder { + /** + * Logger + */ + private static Logger logger = LoggerFactory.getLogger(MultiplexorEventProtocolCoder.class); + + /** + * Decoders + */ + protected EventProtocolDecoder decoders = new EventProtocolDecoder(); + + /** + * Encoders + */ + protected EventProtocolEncoder encoders = new EventProtocolEncoder(); + + + /** + * {@inheritDoc} + */ + @Override + public void addDecoder(String groupId, String artifactId, String topic, + String eventClass, + JsonProtocolFilter protocolFilter, + CustomGsonCoder customGsonCoder, + CustomJacksonCoder customJacksonCoder, + int modelClassLoaderHash) + throws IllegalArgumentException { + logger.info("{}: add-decoder {}:{}:{}:{}:{}:{}:{}:{}", this, + groupId, artifactId, topic, eventClass, + protocolFilter, customGsonCoder, customJacksonCoder, + modelClassLoaderHash); + this.decoders.add(groupId, artifactId, topic, eventClass, protocolFilter, + customGsonCoder, customJacksonCoder, modelClassLoaderHash); + } + + /** + * {@inheritDoc} + */ + @Override + public void addEncoder(String groupId, String artifactId, String topic, + String eventClass, + JsonProtocolFilter protocolFilter, + CustomGsonCoder customGsonCoder, + CustomJacksonCoder customJacksonCoder, + int modelClassLoaderHash) + throws IllegalArgumentException { + logger.info("{}: add-decoder {}:{}:{}:{}:{}:{}:{}:{}", this, + groupId, artifactId, topic, eventClass, + protocolFilter, customGsonCoder, customJacksonCoder, + modelClassLoaderHash); + this.encoders.add(groupId, artifactId, topic, eventClass, protocolFilter, + customGsonCoder, customJacksonCoder, modelClassLoaderHash); + } + + /** + * {@inheritDoc} + */ + @Override + public void removeDecoders(String groupId, String artifactId, String topic) + throws IllegalArgumentException { + logger.info("{}: remove-decoder {}:{}:{}", this, groupId, artifactId, topic); + this.decoders.remove(groupId, artifactId, topic); + } + + /** + * {@inheritDoc} + */ + @Override + public void removeEncoders(String groupId, String artifactId, String topic) + throws IllegalArgumentException { + logger.info("{}: remove-encoder {}:{}:{}", this, groupId, artifactId, topic); + this.encoders.remove(groupId, artifactId, topic); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isDecodingSupported(String groupId, String artifactId, String topic) { + return this.decoders.isCodingSupported(groupId, artifactId, topic); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isEncodingSupported(String groupId, String artifactId, String topic) { + return this.encoders.isCodingSupported(groupId, artifactId, topic); + } + + /** + * {@inheritDoc} + */ + @Override + public Object decode(String groupId, String artifactId, String topic, String json) + throws IllegalArgumentException, UnsupportedOperationException, IllegalStateException { + logger.debug("{}: decode {}:{}:{}:{}", this, groupId, artifactId, topic, json); + return this.decoders.decode(groupId, artifactId, topic, json); + } + + /** + * {@inheritDoc} + */ + @Override + public String encode(String groupId, String artifactId, String topic, Object event) + throws IllegalArgumentException, IllegalStateException, UnsupportedOperationException { + logger.debug("{}: encode {}:{}:{}:{}", this, groupId, artifactId, topic, event); + return this.encoders.encode(groupId, artifactId, topic, event); + } + + /** + * {@inheritDoc} + */ + @Override + public String encode(String topic, Object event) + throws IllegalArgumentException, IllegalStateException, UnsupportedOperationException { + logger.debug("{}: encode {}:{}", this, topic, event); + return this.encoders.encode(topic, event); + } + + /** + * {@inheritDoc} + */ + @Override + public String encode(String topic, Object event, DroolsController droolsController) + throws IllegalArgumentException, IllegalStateException, UnsupportedOperationException { + logger.debug("{}: encode {}:{}:{}", this, topic, event, droolsController); + return this.encoders.encode(topic, event, droolsController); + } + + /** + * {@inheritDoc} + */ + @Override + public List<CoderFilters> getDecoderFilters(String groupId, String artifactId, String topic) + throws IllegalArgumentException { + return this.decoders.getFilters(groupId, artifactId, topic); + } + + /** + * {@inheritDoc} + */ + @Override + public ProtocolCoderToolset getDecoders(String groupId, String artifactId, String topic) + throws IllegalArgumentException { + Pair<ProtocolCoderToolset,ProtocolCoderToolset> decoderToolsets = this.decoders.getCoders(groupId, artifactId, topic); + if (decoderToolsets == null) + throw new IllegalArgumentException("Decoders not found for " + groupId + ":" + artifactId + ":" + topic); + + return decoderToolsets.first(); + } + + /** + * {@inheritDoc} + */ + @Override + public List<CoderFilters> getEncoderFilters(String groupId, String artifactId, String topic) + throws IllegalArgumentException { + return this.encoders.getFilters(groupId, artifactId, topic); + } + + /** + * {@inheritDoc} + */ + @Override + public CoderFilters getDecoderFilters(String groupId, String artifactId, String topic, String classname) + throws IllegalArgumentException { + return this.decoders.getFilters(groupId, artifactId, topic, classname); + } + + /** + * {@inheritDoc} + */ + @Override + public CoderFilters getEncoderFilters(String groupId, String artifactId, String topic, String classname) + throws IllegalArgumentException { + return this.encoders.getFilters(groupId, artifactId, topic, classname); + } + + /** + * {@inheritDoc} + */ + @Override + public List<CoderFilters> getReverseEncoderFilters(String topic, String encodedClass) throws IllegalArgumentException { + return this.encoders.getReverseFilters(topic, encodedClass); + } + + /** + * get all deocders by maven coordinates and topic + * + * @param groupId group id + * @param artifactId artifact id + * + * @return list of decoders + * @throws IllegalArgumentException if invalid input + */ + @Override + public List<ProtocolCoderToolset> getDecoders(String groupId, String artifactId) + throws IllegalArgumentException { + + List<Pair<ProtocolCoderToolset,ProtocolCoderToolset>> decoderToolsets = this.decoders.getCoders(groupId, artifactId); + if (decoderToolsets == null) + throw new IllegalArgumentException("Decoders not found for " + groupId + ":" + artifactId); + + List<ProtocolCoderToolset> parser1CoderToolset = new ArrayList<>(); + for (Pair<ProtocolCoderToolset,ProtocolCoderToolset> coderToolsetPair : decoderToolsets) { + parser1CoderToolset.add(coderToolsetPair.first()); + } + + return parser1CoderToolset; + } + + /** + * {@inheritDoc} + */ + @Override + public List<CoderFilters> getDecoderFilters(String groupId, String artifactId) throws IllegalArgumentException { + return this.decoders.getFilters(groupId, artifactId); + + } + + /** + * {@inheritDoc} + */ + @Override + public List<CoderFilters> getEncoderFilters(String groupId, String artifactId) throws IllegalArgumentException { + return this.encoders.getFilters(groupId, artifactId); + } + + /** + * {@inheritDoc} + */ + @Override + public DroolsController getDroolsController(String topic, Object encodedClass) throws IllegalArgumentException { + return this.encoders.getDroolsController(topic, encodedClass); + } + + /** + * {@inheritDoc} + */ + @Override + public List<DroolsController> getDroolsControllers(String topic, Object encodedClass) throws IllegalArgumentException { + return this.encoders.getDroolsControllers(topic, encodedClass); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("MultiplexorEventProtocolCoder [decoders=").append(decoders).append(", encoders=") + .append(encoders).append("]"); + return builder.toString(); + } +} + +/** + * This protocol Coder that does its best attempt to decode/encode, selecting the best + * class and best fitted json parsing tools. + */ +abstract class GenericEventProtocolCoder { + private static Logger logger = LoggerFactory.getLogger(GenericEventProtocolCoder.class); + + /** + * Mapping topic:controller-id -> <protocol-decoder-toolset-pair> + * where protocol-coder-toolset-pair contains both a jackson-protocol-coder-toolset + * and a gson-protocol-coder-toolset. The first value of the pair will the + * protocol coder toolset most likely to be successful with the encoding or decoding, + * and consequently the second value will be the less likely. + */ + protected final HashMap<String, Pair<ProtocolCoderToolset,ProtocolCoderToolset>> coders = + new HashMap<String, Pair<ProtocolCoderToolset,ProtocolCoderToolset>>(); + + /** + * Mapping topic + classname -> Protocol Set + */ + protected final HashMap<String, List<Pair<ProtocolCoderToolset,ProtocolCoderToolset>>> reverseCoders = + new HashMap<String, List<Pair<ProtocolCoderToolset,ProtocolCoderToolset>>>(); + + protected boolean multipleToolsetRetries = false; + + GenericEventProtocolCoder(boolean multipleToolsetRetries) { + this.multipleToolsetRetries = multipleToolsetRetries; + } + + /** + * Index a new coder + * + * @param groupId of the controller + * @param artifactId of the controller + * @param topic the topic + * @param eventClass the event class + * @param protocolFilter filters to selectively choose a particular decoder + * when there are multiples + * + * @throw IllegalArgumentException if an invalid parameter is passed + */ + public void add(String groupId, String artifactId, + String topic, + String eventClass, + JsonProtocolFilter protocolFilter, + CustomGsonCoder customGsonCoder, + CustomJacksonCoder customJacksonCoder, + int modelClassLoaderHash) + throws IllegalArgumentException { + if (groupId == null || groupId.isEmpty()) { + throw new IllegalArgumentException("Invalid group id"); + } + + if (artifactId == null || artifactId.isEmpty()) + throw new IllegalArgumentException("Invalid artifact id"); + + if (topic == null || topic.isEmpty()) + throw new IllegalArgumentException("Invalid Topic"); + + if (eventClass == null) { + throw new IllegalArgumentException("Invalid Event Class"); + } + + String key = this.codersKey(groupId, artifactId, topic); + String reverseKey = this.reverseCodersKey(topic, eventClass); + + synchronized(this) { + if (coders.containsKey(key)) { + Pair<ProtocolCoderToolset, ProtocolCoderToolset> toolsets = coders.get(key); + + logger.info("{}: adding coders for existing {}: ", this, key, toolsets.first()); + + toolsets.first().addCoder(eventClass, protocolFilter, modelClassLoaderHash); + toolsets.second().addCoder(eventClass, protocolFilter, modelClassLoaderHash); + + if (!reverseCoders.containsKey(reverseKey)) { + logger.info("{}: adding new reverse coders (multiple classes case) for {}:{}: {}", + this, reverseKey, key, toolsets.first()); + + List<Pair<ProtocolCoderToolset,ProtocolCoderToolset>> reverseMappings = + new ArrayList<Pair<ProtocolCoderToolset,ProtocolCoderToolset>>(); + reverseMappings.add(toolsets); + reverseCoders.put(reverseKey, reverseMappings); + } + return; + } + + GsonProtocolCoderToolset gsonCoderTools = + new GsonProtocolCoderToolset + (topic, key, + groupId, artifactId, + eventClass, protocolFilter, + customGsonCoder, + modelClassLoaderHash); + + JacksonProtocolCoderToolset jacksonCoderTools = + new JacksonProtocolCoderToolset + (topic, key, + groupId, artifactId, + eventClass, protocolFilter, + customJacksonCoder, + modelClassLoaderHash); + + // Use Gson as the first priority encoding/decoding toolset, and Jackson + // as second. This is because it has been observed that they can diverge + // somewhat in the encoding/decoding data types, which can produce json + // that may result incompatible with what some network elements are + // expecting. As decoding takes place, this element will reconfigure + // itself to set the jackson one as the favoured one first, if errors + // are detected in the gson encoding + + Pair<ProtocolCoderToolset,ProtocolCoderToolset> coderTools = + new Pair<ProtocolCoderToolset,ProtocolCoderToolset>(gsonCoderTools, + jacksonCoderTools); + + logger.info("{}: adding coders for new {}: {}", this, key, coderTools.first()); + + coders.put(key, coderTools); + + if (reverseCoders.containsKey(reverseKey)) { + // There is another controller (different group id/artifact id/topic) + // that shares the class and the topic. + + List<Pair<ProtocolCoderToolset,ProtocolCoderToolset>> toolsets = + reverseCoders.get(reverseKey); + boolean present = false; + for (Pair<ProtocolCoderToolset,ProtocolCoderToolset> parserSet: toolsets) { + // just doublecheck + present = parserSet.first().getControllerId().equals(key); + if (present) { + /* anomaly */ + logger.error("{}: unexpected toolset reverse mapping found for {}:{}: {}", + this, reverseKey, key, parserSet.first()); + } + } + + if (present) { + return; + } else { + logger.info("{}: adding coder set for {}: {} ", this, + reverseKey, coderTools.getFirst()); + toolsets.add(coderTools); + } + } else { + List<Pair<ProtocolCoderToolset,ProtocolCoderToolset>> toolsets = + new ArrayList<Pair<ProtocolCoderToolset,ProtocolCoderToolset>>(); + toolsets.add(coderTools); + + logger.info("{}: adding toolset for reverse key {}: {}", this, reverseKey, toolsets); + reverseCoders.put(reverseKey, toolsets); + } + + } + } + + /** + * produces key for indexing toolset entries + * + * @param group group id + * @param artifactId artifact id + * @param topic topic + * @return index key + */ + protected String codersKey(String groupId, String artifactId, String topic) { + return groupId + ":" + artifactId + ":" + topic; + } + + /** + * produces a key for the reverse index + * + * @param topic topic + * @param eventClass coded class + * @return reverse index key + */ + protected String reverseCodersKey(String topic, String eventClass) { + return topic + ":" + eventClass; + } + + /** + * remove coder + * + * @param groupId group id + * @param artifactId artifact id + * @param topic topic + * @throws IllegalArgumentException if invalid input + */ + public void remove(String groupId, String artifactId, String topic) + throws IllegalArgumentException { + + if (groupId == null || groupId.isEmpty()) + throw new IllegalArgumentException("Invalid group id"); + + if (artifactId == null || artifactId.isEmpty()) + throw new IllegalArgumentException("Invalid artifact id"); + + if (topic == null || topic.isEmpty()) + throw new IllegalArgumentException("Invalid Topic"); + + String key = this.codersKey(groupId, artifactId, topic); + + synchronized(this) { + if (coders.containsKey(key)) { + Pair<ProtocolCoderToolset, ProtocolCoderToolset> p = coders.remove(key); + + logger.info("{}: removed toolset for {}: {}", this, key, p.getFirst()); + + for (CoderFilters codeFilter : p.first().getCoders()) { + String className = codeFilter.getCodedClass(); + String reverseKey = this.reverseCodersKey(topic, className); + if (this.reverseCoders.containsKey(reverseKey) ) { + List<Pair<ProtocolCoderToolset, ProtocolCoderToolset>> toolsets = + this.reverseCoders.get(reverseKey); + Iterator<Pair<ProtocolCoderToolset, ProtocolCoderToolset>> toolsetsIter = + toolsets.iterator(); + while (toolsetsIter.hasNext()) { + Pair<ProtocolCoderToolset, ProtocolCoderToolset> toolset = toolsetsIter.next(); + if (toolset.first().getControllerId().equals(key)) { + logger.info("{}: removed coder from toolset for {} from reverse mapping {}: ", + this, reverseKey); + toolsetsIter.remove(); + } + } + + if (this.reverseCoders.get(reverseKey).isEmpty()) { + logger.info("{}: removing reverse mapping for {}: ", this, reverseKey); + this.reverseCoders.remove(reverseKey); + } + } + } + } + } + } + + /** + * does it support coding? + * + * @param groupId group id + * @param artifactId artifact id + * @param topic topic + * @return true if its is codable + */ + public boolean isCodingSupported(String groupId, String artifactId, String topic) { + + if (groupId == null || groupId.isEmpty()) + throw new IllegalArgumentException("Invalid group id"); + + if (artifactId == null || artifactId.isEmpty()) + throw new IllegalArgumentException("Invalid artifact id"); + + if (topic == null || topic.isEmpty()) + throw new IllegalArgumentException("Invalid Topic"); + + String key = this.codersKey(groupId, artifactId, topic); + synchronized(this) { + return (coders.containsKey(key)); + } + } + + /** + * decode a json string into an Object + * + * @param groupId group id + * @param artifactId artifact id + * @param topic topic + * @param json json string to convert to object + * @return the decoded object + * @throws IllegalArgumentException if invalid argument is provided + * @throws UnsupportedOperationException if the operation cannot be performed + */ + public Object decode(String groupId, String artifactId, String topic, String json) + throws IllegalArgumentException, UnsupportedOperationException, IllegalStateException { + + if (!isCodingSupported(groupId, artifactId, topic)) + throw new IllegalArgumentException("Unsupported:" + codersKey(groupId, artifactId, topic) + " for encoding"); + + String key = this.codersKey(groupId, artifactId, topic); + Pair<ProtocolCoderToolset,ProtocolCoderToolset> coderTools = coders.get(key); + try { + Object event = coderTools.first().decode(json); + if (event != null) + return event; + } catch (Exception e) { + logger.debug("{}, cannot decode {}", this, json, e); + } + + if (multipleToolsetRetries) { + // try the less favored toolset + try { + Object event = coderTools.second().decode(json); + if (event != null) { + // change the priority of the toolset + synchronized(this) { + ProtocolCoderToolset first = coderTools.first(); + ProtocolCoderToolset second = coderTools.second(); + coderTools.first(second); + coderTools.second(first); + } + + return event; + } + } catch (Exception e) { + throw new UnsupportedOperationException(e); + } + } + + throw new UnsupportedOperationException("Cannot decode neither with gson or jackson"); + } + + /** + * encode an object into a json string + * + * @param groupId group id + * @param artifactId artifact id + * @param topic topic + * @param event object to convert to string + * @return the json string + * @throws IllegalArgumentException if invalid argument is provided + * @throws UnsupportedOperationException if the operation cannot be performed + */ + public String encode(String groupId, String artifactId, String topic, Object event) + throws IllegalArgumentException, UnsupportedOperationException { + + if (!isCodingSupported(groupId, artifactId, topic)) + throw new IllegalArgumentException + ("Unsupported:" + codersKey(groupId, artifactId, topic)); + + if (event == null) + throw new IllegalArgumentException("Unsupported topic:" + topic); + + // reuse the decoder set, since there must be affinity in the model + String key = this.codersKey(groupId, artifactId, topic); + return this.encodeInternal(key, event); + } + + /** + * encode an object into a json string + * + * @param key identifier + * @param event object to convert to string + * @return the json string + * @throws IllegalArgumentException if invalid argument is provided + * @throws UnsupportedOperationException if the operation cannot be performed + */ + protected String encodeInternal(String key, Object event) + throws IllegalArgumentException, UnsupportedOperationException { + + logger.debug("{}: encode for {}: {}", this, key, event); + + Pair<ProtocolCoderToolset,ProtocolCoderToolset> coderTools = coders.get(key); + try { + String json = coderTools.first().encode(event); + if (json != null && !json.isEmpty()) + return json; + } catch (Exception e) { + logger.warn("{}: cannot encode (first) for {}: {}", this, key, event, e); + } + + if (multipleToolsetRetries) { + // try the less favored toolset + try { + String json = coderTools.second().encode(event); + if (json != null) { + // change the priority of the toolset + synchronized(this) { + ProtocolCoderToolset first = coderTools.first(); + ProtocolCoderToolset second = coderTools.second(); + coderTools.first(second); + coderTools.second(first); + } + + return json; + } + } catch (Exception e) { + logger.error("{}: cannot encode (second) for {}: {}", this, key, event, e); + throw new UnsupportedOperationException(e); + } + } + + throw new UnsupportedOperationException("Cannot decode neither with gson or jackson"); + } + + /** + * encode an object into a json string + * + * @param topic topic + * @param event object to convert to string + * @return the json string + * @throws IllegalArgumentException if invalid argument is provided + * @throws UnsupportedOperationException if the operation cannot be performed + */ + public String encode(String topic, Object event) + throws IllegalArgumentException, IllegalStateException, UnsupportedOperationException { + + if (event == null) + throw new IllegalArgumentException("Invalid encoded class"); + + if (topic == null || topic.isEmpty()) + throw new IllegalArgumentException("Invalid topic"); + + List<DroolsController> droolsControllers = droolsCreators(topic, event); + + if (droolsControllers.isEmpty()) + throw new IllegalArgumentException("no drools controller has been found"); + + if (droolsControllers.size() > 1) { + logger.warn("{}: multiple drools-controller {} for {}:{} ", this, topic, + droolsControllers, event.getClass().getCanonicalName()); + // continue + } + + String key = codersKey(droolsControllers.get(0).getGroupId(), droolsControllers.get(0).getArtifactId(), topic); + return this.encodeInternal(key, event); + } + + /** + * encode an object into a json string + * + * @param topic topic + * @param encodedClass object to convert to string + * @return the json string + * @throws IllegalArgumentException if invalid argument is provided + * @throws UnsupportedOperationException if the operation cannot be performed + */ + public String encode(String topic, Object encodedClass, DroolsController droolsController) + throws IllegalArgumentException, IllegalArgumentException, UnsupportedOperationException { + + if (encodedClass == null) + throw new IllegalArgumentException("Invalid encoded class"); + + if (topic == null || topic.isEmpty()) + throw new IllegalArgumentException("Invalid topic"); + + String key = codersKey(droolsController.getGroupId(), droolsController.getArtifactId(), topic); + return this.encodeInternal(key, encodedClass); + } + + /** + * @param topic + * @param encodedClass + * @param reverseKey + * @return + * @throws IllegalStateException + * @throws IllegalArgumentException + */ + protected List<DroolsController> droolsCreators(String topic, Object encodedClass) + throws IllegalStateException, IllegalArgumentException { + + List<DroolsController> droolsControllers = new ArrayList<DroolsController>(); + + String reverseKey = this.reverseCodersKey(topic, encodedClass.getClass().getCanonicalName()); + if (!this.reverseCoders.containsKey(reverseKey)) { + logger.warn("{}: no reverse mapping for {}", this, reverseKey); + return droolsControllers; + } + + List<Pair<ProtocolCoderToolset, ProtocolCoderToolset>> + toolsets = this.reverseCoders.get(reverseKey); + + // There must be multiple toolset pairs associated with <topic,classname> reverseKey + // case 2 different controllers use the same models and register the same encoder for + // the same topic. This is assumed not to occur often but for the purpose of encoding + // but there should be no side-effects. Ownership is crosscheck against classname and + // classloader reference. + + if (toolsets == null || toolsets.isEmpty()) + throw new IllegalStateException("No Encoders toolsets available for topic "+ topic + + " encoder " + encodedClass.getClass().getCanonicalName()); + + for (Pair<ProtocolCoderToolset, ProtocolCoderToolset> encoderSet : toolsets) { + // figure out the right toolset + String groupId = encoderSet.first().getGroupId(); + String artifactId = encoderSet.first().getArtifactId(); + List<CoderFilters> coders = encoderSet.first().getCoders(); + for (CoderFilters coder : coders) { + if (coder.getCodedClass().equals(encodedClass.getClass().getCanonicalName())) { + DroolsController droolsController = + DroolsController.factory.get(groupId, artifactId, ""); + if (droolsController.ownsCoder(encodedClass.getClass(), coder.getModelClassLoaderHash())) { + droolsControllers.add(droolsController); + } + } + } + } + + if (droolsControllers.isEmpty()) + throw new IllegalStateException("No Encoders toolsets available for "+ topic + + ":" + encodedClass.getClass().getCanonicalName()); + + return droolsControllers; + } + + + /** + * get all filters by maven coordinates and topic + * + * @param groupId group id + * @param artifactId artifact id + * @param topic topic + * @return list of coders + * @throws IllegalArgumentException if invalid input + */ + public List<CoderFilters> getFilters(String groupId, String artifactId, String topic) + throws IllegalArgumentException { + + if (!isCodingSupported(groupId, artifactId, topic)) + throw new IllegalArgumentException("Unsupported:" + codersKey(groupId, artifactId, topic)); + + String key = this.codersKey(groupId, artifactId, topic); + Pair<ProtocolCoderToolset,ProtocolCoderToolset> coderTools = coders.get(key); + return coderTools.first().getCoders(); + } + + /** + * get all coders by maven coordinates and topic + * + * @param groupId group id + * @param artifactId artifact id + * @param topic topic + * @return list of coders + * @throws IllegalArgumentException if invalid input + */ + public Pair<ProtocolCoderToolset,ProtocolCoderToolset> getCoders(String groupId, String artifactId, String topic) + throws IllegalArgumentException { + + if (!isCodingSupported(groupId, artifactId, topic)) + throw new IllegalArgumentException("Unsupported:" + codersKey(groupId, artifactId, topic)); + + String key = this.codersKey(groupId, artifactId, topic); + Pair<ProtocolCoderToolset,ProtocolCoderToolset> coderTools = coders.get(key); + return coderTools; + } + + /** + * get all coders by maven coordinates and topic + * + * @param groupId group id + * @param artifactId artifact id + * @return list of coders + * @throws IllegalArgumentException if invalid input + */ + public List<CoderFilters> getFilters(String groupId, String artifactId) + throws IllegalArgumentException { + + if (groupId == null || groupId.isEmpty()) + throw new IllegalArgumentException("Invalid group id"); + + if (artifactId == null || artifactId.isEmpty()) + throw new IllegalArgumentException("Invalid artifact id"); + + String key = this.codersKey(groupId, artifactId, ""); + + List<CoderFilters> codersFilters = new ArrayList<CoderFilters>(); + for (Map.Entry<String, Pair<ProtocolCoderToolset,ProtocolCoderToolset>> entry : coders.entrySet()) { + if (entry.getKey().startsWith(key)) { + codersFilters.addAll(entry.getValue().first().getCoders()); + } + } + + return codersFilters; + } + + /** + * get all coders by maven coordinates and topic + * + * @param groupId group id + * @param artifactId artifact id + * @return list of coders + * @throws IllegalArgumentException if invalid input + */ + public List<Pair<ProtocolCoderToolset,ProtocolCoderToolset>> getCoders(String groupId, String artifactId) + throws IllegalArgumentException { + + if (groupId == null || groupId.isEmpty()) + throw new IllegalArgumentException("Invalid group id"); + + if (artifactId == null || artifactId.isEmpty()) + throw new IllegalArgumentException("Invalid artifact id"); + + String key = this.codersKey(groupId, artifactId, ""); + + List<Pair<ProtocolCoderToolset,ProtocolCoderToolset>> coderToolset = new ArrayList<Pair<ProtocolCoderToolset,ProtocolCoderToolset>>(); + for (Map.Entry<String, Pair<ProtocolCoderToolset,ProtocolCoderToolset>> entry : coders.entrySet()) { + if (entry.getKey().startsWith(key)) { + coderToolset.add(entry.getValue()); + } + } + + return coderToolset; + } + + + /** + * get all filters by maven coordinates, topic, and classname + * + * @param groupId group id + * @param artifactId artifact id + * @param topic topic + * @param classname + * @return list of coders + * @throws IllegalArgumentException if invalid input + */ + public CoderFilters getFilters(String groupId, String artifactId, String topic, String classname) + throws IllegalArgumentException { + + if (!isCodingSupported(groupId, artifactId, topic)) + throw new IllegalArgumentException("Unsupported:" + codersKey(groupId, artifactId, topic)); + + if (classname == null || classname.isEmpty()) + throw new IllegalArgumentException("classname must be provided"); + + String key = this.codersKey(groupId, artifactId, topic); + Pair<ProtocolCoderToolset,ProtocolCoderToolset> coderTools = coders.get(key); + return coderTools.first().getCoder(classname); + } + + /** + * get coded based on class and topic + * + * @param topic + * @param codedClass + * @return + * @throws IllegalArgumentException + */ + public List<CoderFilters> getReverseFilters(String topic, String codedClass) + throws IllegalArgumentException { + + if (topic == null || topic.isEmpty()) + throw new IllegalArgumentException("Unsupported"); + + if (codedClass == null) + throw new IllegalArgumentException("class must be provided"); + + String key = this.reverseCodersKey(topic, codedClass); + List<Pair<ProtocolCoderToolset,ProtocolCoderToolset>> toolsets = this.reverseCoders.get(key); + if (toolsets == null) + throw new IllegalArgumentException("No Coder found for " + key); + + + List<CoderFilters> coders = new ArrayList<CoderFilters>(); + for (Pair<ProtocolCoderToolset,ProtocolCoderToolset> toolset: toolsets) { + coders.addAll(toolset.first().getCoders()); + } + + return coders; + } + + /** + * returns group and artifact id of the creator of the encoder + * + * @param topic + * @param fact + * @return + * @throws IllegalArgumentException + */ + DroolsController getDroolsController(String topic, Object fact) + throws IllegalArgumentException { + + if (topic == null || topic.isEmpty()) + throw new IllegalArgumentException("Unsupported"); + + if (fact == null) + throw new IllegalArgumentException("class must be provided"); + + List<DroolsController> droolsControllers = droolsCreators(topic, fact); + + if (droolsControllers.isEmpty()) + throw new IllegalArgumentException("Invalid Topic: " + topic); + + if (droolsControllers.size() > 1) { + logger.warn("{}: multiple drools-controller {} for {}:{} ", this, + droolsControllers, topic, fact.getClass().getCanonicalName()); + // continue + } + return droolsControllers.get(0); + } + + /** + * returns group and artifact id of the creator of the encoder + * + * @param topic + * @param fact + * @return + * @throws IllegalArgumentException + */ + List<DroolsController> getDroolsControllers(String topic, Object fact) + throws IllegalArgumentException { + + if (topic == null || topic.isEmpty()) + throw new IllegalArgumentException("Unsupported"); + + if (fact == null) + throw new IllegalArgumentException("class must be provided"); + + List<DroolsController> droolsControllers = droolsCreators(topic, fact); + if (droolsControllers.size() > 1) { + // unexpected + logger.warn("{}: multiple drools-controller {} for {}:{} ", this, + droolsControllers, topic, fact.getClass().getCanonicalName()); + // continue + } + return droolsControllers; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("GenericEventProtocolCoder [coders=").append(coders.keySet()).append(", reverseCoders=") + .append(reverseCoders.keySet()).append("]"); + return builder.toString(); + } +} + +class EventProtocolDecoder extends GenericEventProtocolCoder { + + public EventProtocolDecoder(){super(false);} + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("EventProtocolDecoder [toString()=").append(super.toString()).append("]"); + return builder.toString(); + } + +} + +class EventProtocolEncoder extends GenericEventProtocolCoder { + + public EventProtocolEncoder(){super(false);} + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("EventProtocolEncoder [toString()=").append(super.toString()).append("]"); + return builder.toString(); + } +}
\ No newline at end of file diff --git a/policy-management/src/main/java/org/onap/policy/drools/protocol/coders/JsonProtocolFilter.java b/policy-management/src/main/java/org/onap/policy/drools/protocol/coders/JsonProtocolFilter.java new file mode 100644 index 00000000..6afeb26a --- /dev/null +++ b/policy-management/src/main/java/org/onap/policy/drools/protocol/coders/JsonProtocolFilter.java @@ -0,0 +1,308 @@ +/*- + * ============LICENSE_START======================================================= + * policy-management + * ================================================================================ + * Copyright (C) 2017 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.drools.protocol.coders; + +import java.util.ArrayList; +import java.util.List; + +import org.onap.policy.drools.utils.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +/** + * JSON Protocol Filter. Evaluates an JSON string and evaluates if it + * passes its filters. + */ +public class JsonProtocolFilter { + + /** + * Logger + */ + private static Logger logger = LoggerFactory.getLogger(JsonProtocolFilter.class); + + /** + * Helper class to collect Filter information + */ + public static class FilterRule { + /** + * Field name + */ + protected String name; + + /** + * Field Value regex + */ + protected String regex; + + /** + * Filter Constructor + * + * @param name field name + * @param regex field regex value + */ + public FilterRule(String name, String regex) { + this.name = name; + this.regex = regex; + } + + /** + * Default constructor (for serialization only) + */ + public FilterRule() { + super(); + } + + /** + * gets name + * + * @return + */ + public String getName() { + return name; + } + + /** + * gets regex + * + * @return + */ + public String getRegex() { + return regex; + } + + /** + * sets field name + * @param name field name + */ + public void setName(String name) { + this.name = name; + } + + /** + * sets regex name + * @param regex + */ + public void setRegex(String regex) { + this.regex = regex; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Filter [name=").append(name).append(", regex=").append(regex).append("]"); + return builder.toString(); + } + } + + /** + * all the filters to be applied + */ + protected List<FilterRule> rules = new ArrayList<FilterRule>(); + + /** + * + * @param rawFilters raw filter initialization + * + * @throws IllegalArgumentException an invalid input has been provided + */ + public static JsonProtocolFilter fromRawFilters(List<Pair<String, String>> rawFilters) + throws IllegalArgumentException { + + if (rawFilters == null) { + throw new IllegalArgumentException("No raw filters provided"); + } + + List<FilterRule> filters = new ArrayList<FilterRule>(); + for (Pair<String, String> filterPair: rawFilters) { + if (filterPair.first() == null || filterPair.first().isEmpty()) { + continue; + } + + filters.add(new FilterRule(filterPair.first(), filterPair.second())); + } + return new JsonProtocolFilter(filters); + } + + /** + * Create a Protocol Filter + * + * @throws IllegalArgumentException an invalid input has been provided + */ + public JsonProtocolFilter() throws IllegalArgumentException {} + + /** + * + * @param rawFilters raw filter initialization + * + * @throws IllegalArgumentException an invalid input has been provided + */ + public JsonProtocolFilter(List<FilterRule> filters) throws IllegalArgumentException { + this.rules = filters; + } + + /** + * are there any filters? + * + * @return true if there are filters, false otherwise + */ + public boolean isRules() { + return !this.rules.isEmpty(); + } + + /** + * accept a JSON string as conformant it if passes all filters + * + * @param json json is a JSON object + * @return true if json string is conformant + * + * @throws IllegalArgumentException an invalid input has been provided + */ + public synchronized boolean accept(JsonElement json) throws IllegalArgumentException { + if (json == null) { + throw new IllegalArgumentException("no JSON provided"); + } + + if (rules.isEmpty()) { + return true; + } + + try { + if (!json.isJsonObject()) { + return false; + } + + JsonObject event = json.getAsJsonObject(); + for (FilterRule filter: rules) { + if (filter.regex == null || + filter.regex.isEmpty() || + filter.regex.equals(".*")) { + + // Only check for presence + if (!event.has(filter.name)) { + return false; + } + } else { + JsonElement field = event.get(filter.name); + if (field == null) { + return false; + } + + String fieldValue = field.getAsString(); + if (!fieldValue.matches(filter.regex)) { + return false; + } + } + } + return true; + } catch (Exception e) { + throw new IllegalArgumentException(e); + } + } + + /** + * accept a JSON string as conformant it if passes all filters + * + * @param json json string + * @return true if json string is conformant + * + * @throws IllegalArgumentException an invalid input has been provided + */ + public synchronized boolean accept(String json) throws IllegalArgumentException { + if (json == null || json.isEmpty()) { + throw new IllegalArgumentException("no JSON provided"); + } + + if (rules.isEmpty()) { + return true; + } + + try { + JsonElement element = new JsonParser().parse(json); + if (element == null || !element.isJsonObject()) { + return false; + } + + return this.accept(element.getAsJsonObject()); + } catch (IllegalArgumentException ile) { + throw ile; + } catch (Exception e) { + logger.info("{}: cannot accept {} because of {}", + this, json, e.getMessage(), e); + throw new IllegalArgumentException(e); + } + } + + public List<FilterRule> getRules() { + return rules; + } + + public synchronized void setRules(List<FilterRule> rulesFilters) { + this.rules = rulesFilters; + } + + public synchronized void deleteRules(String name) { + for (FilterRule rule : new ArrayList<>(this.rules)) { + if (rule.name.equals(name)) { + this.rules.remove(rule); + } + } + } + + public List<FilterRule> getRules(String name) { + ArrayList<FilterRule> temp = new ArrayList<>(); + for (FilterRule rule : new ArrayList<>(this.rules)) { + if (rule.name.equals(name)) { + temp.add(rule); + } + } + return temp; + } + + public synchronized void deleteRule(String name, String regex) { + for (FilterRule rule : new ArrayList<>(this.rules)) { + if (rule.name.equals(name) && rule.regex.equals(regex)) { + this.rules.remove(rule); + } + } + } + + public synchronized void addRule(String name, String regex) { + for (FilterRule rule : new ArrayList<>(this.rules)) { + if (rule.name.equals(name) && rule.regex.equals(regex)) { + return; + } + } + + this.rules.add(new FilterRule(name,regex)); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("JsonProtocolFilter [rules=").append(rules).append("]"); + return builder.toString(); + } + +} diff --git a/policy-management/src/main/java/org/onap/policy/drools/protocol/coders/ProtocolCoderToolset.java b/policy-management/src/main/java/org/onap/policy/drools/protocol/coders/ProtocolCoderToolset.java new file mode 100644 index 00000000..8e7dec0b --- /dev/null +++ b/policy-management/src/main/java/org/onap/policy/drools/protocol/coders/ProtocolCoderToolset.java @@ -0,0 +1,683 @@ +/*- + * ============LICENSE_START======================================================= + * policy-management + * ================================================================================ + * Copyright (C) 2017 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.drools.protocol.coders; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.time.Instant; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.onap.policy.drools.controller.DroolsController; +import org.onap.policy.drools.protocol.coders.EventProtocolCoder.CoderFilters; +import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration.CustomCoder; +import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration.CustomGsonCoder; +import org.onap.policy.drools.protocol.coders.TopicCoderFilterConfiguration.CustomJacksonCoder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +/** + * Protocol Coding/Decoding Toolset + */ +public abstract class ProtocolCoderToolset { + + /** + * Logger + */ + private static Logger logger = LoggerFactory.getLogger(ProtocolCoderToolset.class); + + /** + * topic + */ + protected final String topic; + + /** + * controller id + */ + protected final String controllerId; + + /** + * group id + */ + protected final String groupId; + + /** + * artifact id + */ + protected final String artifactId; + + /** + * Protocols and associated Filters + */ + protected final List<CoderFilters> coders = new ArrayList<CoderFilters>(); + + /** + * Tree model (instead of class model) generic parsing to be able to inspect elements + */ + protected JsonParser filteringParser = new JsonParser(); + + /** + * custom coder + */ + protected CustomCoder customCoder; + + /** + * Constructor + * + * @param topic the topic + * @param controllerId the controller id + * @param codedClass the decoded class + * @param filters list of filters that apply to the + * selection of this decodedClass in case of multiplicity + * @throws IllegalArgumentException if invalid data has been passed in + */ + public ProtocolCoderToolset(String topic, + String controllerId, + String groupId, + String artifactId, + String codedClass, + JsonProtocolFilter filters, + CustomCoder customCoder, + int modelClassLoaderHash) + throws IllegalArgumentException { + + if (topic == null || controllerId == null || + groupId == null || artifactId == null || + codedClass == null || filters == null || + topic.isEmpty() || controllerId.isEmpty()) { + // TODO + throw new IllegalArgumentException("Invalid input"); + } + + this.topic = topic; + this.controllerId = controllerId; + this.groupId = groupId; + this.artifactId = artifactId; + this.coders.add(new CoderFilters(codedClass, filters, modelClassLoaderHash)); + this.customCoder = customCoder; + } + + /** + * gets the coder + filters associated with this class name + * + * @param classname class name + * @return the decoder filters or null if not found + */ + public CoderFilters getCoder(String classname) { + for (CoderFilters decoder: this.coders) { + if (decoder.factClass.equals(classname)) { + return decoder; + } + } + return null; + } + + /** + * get all coder filters in use + * + * @return coder filters + */ + public List<CoderFilters> getCoders() { + return this.coders; + } + + /** + * add coder or replace it exists + * + * @param eventClass decoder + * @param filter filter + */ + public void addCoder(String eventClass, JsonProtocolFilter filter, int modelClassLoaderHash) { + synchronized(this) { + for (CoderFilters coder: this.coders) { + if (coder.factClass.equals(eventClass)) { + // this is a better check than checking pointers, just + // in case classloader is different and this is just an update + coder.factClass = eventClass; + coder.filter = filter; + coder.modelClassLoaderHash = modelClassLoaderHash; + return; + } + } + } + + this.coders.add(new CoderFilters(eventClass, filter, modelClassLoaderHash)); + } + + /** + * remove coder + * + * @param eventClass decoder + * @param filter filter + */ + public void removeCoders(String eventClass) { + synchronized(this) { + Iterator<CoderFilters> codersIt = this.coders.iterator(); + while (codersIt.hasNext()) { + CoderFilters coder = codersIt.next(); + if (coder.factClass.equals(eventClass)) { + codersIt.remove(); + } + } + } + } + + /** + * gets the topic + * + * @return the topic + */ + public String getTopic() {return topic;} + + /** + * gets the controller id + * + * @return the controller id + */ + public String getControllerId() {return controllerId;} + + /** + * @return the groupId + */ + public String getGroupId() { + return groupId; + } + + /** + * @return the artifactId + */ + public String getArtifactId() { + return artifactId; + } + + /** + * @return the customCoder + */ + public CustomCoder getCustomCoder() { + return customCoder; + } + + /** + * @param customCoder the customCoder to set + */ + public void setCustomCoder(CustomCoder customCoder) { + this.customCoder = customCoder; + } + + /** + * performs filtering on a json string + * + * @param json json string + * @return the decoder that passes the filter, otherwise null + * @throws UnsupportedOperationException can't filter + * @throws IllegalArgumentException invalid input + */ + protected CoderFilters filter(String json) + throws UnsupportedOperationException, IllegalArgumentException, IllegalStateException { + + + // 1. Get list of decoding classes for this controller Id and topic + // 2. If there are no classes, return error + // 3. Otherwise, from the available classes for decoding, pick the first one that + // passes the filters + + // Don't parse if it is not necessary + + if (this.coders.isEmpty()) { + // TODO this is an error + throw new IllegalStateException("No coders available"); + } + + if (this.coders.size() == 1) { + JsonProtocolFilter filter = this.coders.get(0).getFilter(); + if (!filter.isRules()) { + return this.coders.get(0); + } + } + + JsonElement event; + try { + event = this.filteringParser.parse(json); + } catch (Exception e) { + throw new UnsupportedOperationException(e); + } + + for (CoderFilters decoder: this.coders) { + try { + boolean accepted = decoder.getFilter().accept(event); + if (accepted) { + return decoder; + } + } catch (Exception e) { + logger.info("{}: unexpected failure accepting {} because of {}", + this, event, e.getMessage(), e); + // continue + } + } + + return null; + } + + /** + * Decode json into a POJO object + * @param json json string + * + * @return a POJO object for the json string + * @throws IllegalArgumentException if an invalid parameter has been received + * @throws UnsupportedOperationException if parsing into POJO is not possible + */ + public abstract Object decode(String json) + throws IllegalArgumentException, UnsupportedOperationException, IllegalStateException; + + /** + * Encodes a POJO object into a JSON String + * + * @param event JSON POJO event to be converted to String + * @return JSON string version of POJO object + * @throws IllegalArgumentException if an invalid parameter has been received + * @throws UnsupportedOperationException if parsing into POJO is not possible + */ + public abstract String encode(Object event) + throws IllegalArgumentException, UnsupportedOperationException; + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("ProtocolCoderToolset [topic=").append(topic).append(", controllerId=").append(controllerId) + .append(", groupId=").append(groupId).append(", artifactId=").append(artifactId).append(", coders=") + .append(coders).append(", filteringParser=").append(filteringParser).append(", customCoder=") + .append(customCoder).append("]"); + return builder.toString(); + } +} + +/** + * Tools used for encoding/decoding using Jackson + */ +class JacksonProtocolCoderToolset extends ProtocolCoderToolset { + private static Logger logger = LoggerFactory.getLogger(JacksonProtocolCoderToolset.class); + /** + * decoder + */ + @JsonIgnore + protected final ObjectMapper decoder = new ObjectMapper(); + + /** + * encoder + */ + @JsonIgnore + protected final ObjectMapper encoder = new ObjectMapper(); + + /** + * Toolset to encode/decode tools associated with a topic + * + * @param topic topic + * @param decodedClass decoded class of an event + * @param filter + */ + public JacksonProtocolCoderToolset(String topic, String controllerId, + String groupId, String artifactId, + String decodedClass, + JsonProtocolFilter filter, + CustomJacksonCoder customJacksonCoder, + int modelClassLoaderHash) { + super(topic, controllerId, groupId, artifactId, decodedClass, filter, customJacksonCoder, modelClassLoaderHash); + decoder.registerModule(new JavaTimeModule()); + decoder.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, + false); + } + + /** + * gets the Jackson decoder + * + * @return the Jackson decoder + */ + @JsonIgnore + protected ObjectMapper getDecoder() {return decoder;} + + /** + * gets the Jackson encoder + * + * @return the Jackson encoder + */ + @JsonIgnore + protected ObjectMapper getEncoder() {return encoder;} + + /** + * {@inheritDoc} + */ + @Override + public Object decode(String json) + throws IllegalArgumentException, UnsupportedOperationException, IllegalStateException { + + // 0. Use custom coder if available + + if (this.customCoder != null) { + throw new UnsupportedOperationException + ("Jackon Custom Decoder is not supported at this time"); + } + + DroolsController droolsController = + DroolsController.factory.get(groupId, artifactId, ""); + if (droolsController == null) { + logger.warn("{}: no drools-controller to process {}", this, json); + throw new IllegalStateException("no drools-controller to process event"); + } + + CoderFilters decoderFilter = filter(json); + if (decoderFilter == null) { + logger.debug("{}: no decoder to process {}", this, json); + throw new UnsupportedOperationException("no decoder to process event"); + } + + Class<?> decoderClass; + try { + decoderClass = + droolsController.fetchModelClass(decoderFilter.getCodedClass()); + if (decoderClass == null) { + logger.warn("{}: cannot fetch application class {}", this, decoderFilter.getCodedClass()); + throw new IllegalStateException("cannot fetch application class " + decoderFilter.getCodedClass()); + } + } catch (Exception e) { + logger.warn("{}: cannot fetch application class {} because of {}", this, + decoderFilter.getCodedClass(), e.getMessage()); + throw new UnsupportedOperationException("cannot fetch application class " + decoderFilter.getCodedClass(), e); + } + + + try { + Object fact = this.decoder.readValue(json, decoderClass); + return fact; + } catch (Exception e) { + logger.warn("{} cannot decode {} into {} because of {}", + this, json, decoderClass.getName(), e.getMessage(), e); + throw new UnsupportedOperationException("cannont decode into " + decoderFilter.getCodedClass(), e); + } + } + + /** + * {@inheritDoc} + */ + @Override + public String encode(Object event) + throws IllegalArgumentException, UnsupportedOperationException { + + // 0. Use custom coder if available + + if (this.customCoder != null) { + throw new UnsupportedOperationException + ("Jackon Custom Encoder is not supported at this time"); + } + + try { + String encodedEvent = this.encoder.writeValueAsString(event); + return encodedEvent; + } catch (JsonProcessingException e) { + logger.error("{} cannot encode {} because of {}", this, event, e.getMessage(), e); + throw new UnsupportedOperationException("event cannot be encoded"); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("JacksonProtocolCoderToolset [toString()=").append(super.toString()).append("]"); + return builder.toString(); + } + +} + +/** + * Tools used for encoding/decoding using Jackson + */ +class GsonProtocolCoderToolset extends ProtocolCoderToolset { + + /** + * Logger + */ + private static Logger logger = LoggerFactory.getLogger(GsonProtocolCoderToolset.class); + + /** + * Formatter for JSON encoding/decoding + */ + @JsonIgnore + public static DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSxxx"); + + @JsonIgnore + public static DateTimeFormatter zuluFormat = DateTimeFormatter.ISO_INSTANT; + + /** + * Adapter for ZonedDateTime + */ + + public static class GsonUTCAdapter implements JsonSerializer<ZonedDateTime>, JsonDeserializer<ZonedDateTime> { + + public ZonedDateTime deserialize(JsonElement element, Type type, JsonDeserializationContext context) + throws JsonParseException { + try { + return ZonedDateTime.parse(element.getAsString(), format); + } catch (Exception e) { + logger.info("GsonUTCAdapter: cannot parse {} because of {}", + element, e.getMessage(), e); + } + return null; + } + + public JsonElement serialize(ZonedDateTime datetime, Type type, JsonSerializationContext context) { + return new JsonPrimitive(datetime.format(format)); + } + } + + public static class GsonInstantAdapter implements JsonSerializer<Instant>, JsonDeserializer<Instant> { + + @Override + public Instant deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + return Instant.ofEpochMilli(json.getAsLong()); + } + + @Override + public JsonElement serialize(Instant src, Type typeOfSrc, JsonSerializationContext context) { + return new JsonPrimitive(src.toEpochMilli()); + } + + } + + + /** + * decoder + */ + @JsonIgnore + protected final Gson decoder = new GsonBuilder().disableHtmlEscaping(). + registerTypeAdapter(ZonedDateTime.class, new GsonUTCAdapter()). + create(); + + /** + * encoder + */ + @JsonIgnore + protected final Gson encoder = new GsonBuilder().disableHtmlEscaping(). + registerTypeAdapter(ZonedDateTime.class, new GsonUTCAdapter()). + create(); + + /** + * Toolset to encode/decode tools associated with a topic + * + * @param topic topic + * @param decodedClass decoded class of an event + * @param filter + */ + public GsonProtocolCoderToolset(String topic, String controllerId, + String groupId, String artifactId, + String decodedClass, + JsonProtocolFilter filter, + CustomGsonCoder customGsonCoder, + int modelClassLoaderHash) { + super(topic, controllerId, groupId, artifactId, decodedClass, filter, customGsonCoder, modelClassLoaderHash); + } + + /** + * gets the Gson decoder + * + * @return the Gson decoder + */ + @JsonIgnore + protected Gson getDecoder() {return decoder;} + + /** + * gets the Gson encoder + * + * @return the Gson encoder + */ + @JsonIgnore + protected Gson getEncoder() {return encoder;} + + /** + * {@inheritDoc} + */ + @Override + public Object decode(String json) + throws IllegalArgumentException, UnsupportedOperationException, IllegalStateException { + + DroolsController droolsController = + DroolsController.factory.get(groupId, artifactId, ""); + if (droolsController == null) { + logger.warn("{}: no drools-controller to process {}", this, json); + throw new IllegalStateException("no drools-controller to process event"); + } + + CoderFilters decoderFilter = filter(json); + if (decoderFilter == null) { + logger.debug("{}: no decoder to process {}", this, json); + throw new UnsupportedOperationException("no decoder to process event"); + } + + Class<?> decoderClass; + try { + decoderClass = + droolsController.fetchModelClass(decoderFilter.getCodedClass()); + if (decoderClass == null) { + logger.warn("{}: cannot fetch application class {}", this, decoderFilter.getCodedClass()); + throw new IllegalStateException("cannot fetch application class " + decoderFilter.getCodedClass()); + } + } catch (Exception e) { + logger.warn("{}: cannot fetch application class {} because of {}", this, + decoderFilter.getCodedClass(), e.getMessage()); + throw new UnsupportedOperationException("cannot fetch application class " + decoderFilter.getCodedClass(), e); + } + + if (this.customCoder != null) { + try { + Class<?> gsonClassContainer = + droolsController.fetchModelClass(this.customCoder.getClassContainer()); + Field gsonField = gsonClassContainer.getField(this.customCoder.staticCoderField); + Object gsonObject = gsonField.get(null); + Method fromJsonMethod = gsonObject.getClass(). + getDeclaredMethod + ("fromJson", new Class[]{String.class, Class.class}); + Object fact = fromJsonMethod.invoke(gsonObject, json, decoderClass); + return fact; + } catch (Exception e) { + logger.warn("{}: cannot fetch application class {} because of {}", this, + decoderFilter.getCodedClass(), e.getMessage()); + throw new UnsupportedOperationException("cannot fetch application class " + decoderFilter.getCodedClass(), e); + } + } else { + try { + Object fact = this.decoder.fromJson(json, decoderClass); + return fact; + } catch (Exception e) { + logger.warn("{} cannot decode {} into {} because of {}", + this, json, decoderClass.getName(), e.getMessage(), e); + throw new UnsupportedOperationException("cannont decode into " + decoderFilter.getCodedClass(), e); + } + } + } + + + + /** + * {@inheritDoc} + */ + @Override + public String encode(Object event) + throws IllegalArgumentException, UnsupportedOperationException { + + DroolsController droolsController = + DroolsController.factory.get(groupId, artifactId, null); + if (droolsController == null) { + logger.info("{}: no drools-controller to process {} (continue)", this, event); + throw new IllegalStateException("custom-coder but no drools-controller"); + } + + if (this.customCoder != null) { + try { + Class<?> gsonClassContainer = + droolsController.fetchModelClass(this.customCoder.getClassContainer()); + Field gsonField = gsonClassContainer.getField(this.customCoder.staticCoderField); + Object gsonObject = gsonField.get(null); + Method toJsonMethod = gsonObject.getClass(). + getDeclaredMethod + ("toJson", new Class[]{Object.class}); + String encodedJson = (String) toJsonMethod.invoke(gsonObject, event); + return encodedJson; + } catch (Exception e) { + logger.warn("{} cannot custom-encode {} because of {}", this, event, e.getMessage(), e); + throw new UnsupportedOperationException("event cannot be encoded", e); + } + } else { + try { + String encodedEvent = this.encoder.toJson(event); + return encodedEvent; + } catch (Exception e) { + logger.warn("{} cannot encode {} because of {}", this, event, e.getMessage(), e); + throw new UnsupportedOperationException("event cannot be encoded", e); + } + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("GsonProtocolCoderToolset [toString()=").append(super.toString()).append("]"); + return builder.toString(); + } +} diff --git a/policy-management/src/main/java/org/onap/policy/drools/protocol/coders/TopicCoderFilterConfiguration.java b/policy-management/src/main/java/org/onap/policy/drools/protocol/coders/TopicCoderFilterConfiguration.java new file mode 100644 index 00000000..93737267 --- /dev/null +++ b/policy-management/src/main/java/org/onap/policy/drools/protocol/coders/TopicCoderFilterConfiguration.java @@ -0,0 +1,309 @@ +/*- + * ============LICENSE_START======================================================= + * policy-management + * ================================================================================ + * Copyright (C) 2017 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.drools.protocol.coders; + +import java.util.List; + +public class TopicCoderFilterConfiguration { + + /** + * Custom coder, contains class and static field to access parser that the controller + * desires to use instead of the framework provided parser + */ + public static abstract class CustomCoder { + protected String className; + protected String staticCoderField; + + /** + * create custom coder from raw string in the following format + * (typically embedded in a property file): + * + * Note this is to support decoding/encoding of partial structures that are + * only known by the model. + * + * @param rawCustomCoder with format: <class-containing-custom-coder>,<static-coder-field> + */ + public CustomCoder(String rawCustomCoder) throws IllegalArgumentException { + if (rawCustomCoder != null && !rawCustomCoder.isEmpty()) { + + this.className = rawCustomCoder.substring(0,rawCustomCoder.indexOf(",")); + if (this.className == null || this.className.isEmpty()) { + throw new IllegalArgumentException("No classname to create CustomCoder cannot be created"); + } + + this.staticCoderField = rawCustomCoder.substring(rawCustomCoder.indexOf(",")+1); + if (this.staticCoderField == null || this.staticCoderField.isEmpty()) { + throw new IllegalArgumentException + ("No staticCoderField to create CustomCoder cannot be created for class " + + className); + } + + } + } + /** + * @param classContainer + * @param staticCoderField + */ + public CustomCoder(String className, String staticCoderField) throws IllegalArgumentException { + if (className == null || className.isEmpty()) { + throw new IllegalArgumentException("No classname to create CustomCoder cannot be created"); + } + + if (staticCoderField == null || staticCoderField.isEmpty()) { + throw new IllegalArgumentException + ("No staticCoderField to create CustomCoder cannot be created for class " + + className); + } + + this.className = className; + this.staticCoderField = staticCoderField; + } + + /** + * @return the className + */ + public String getClassContainer() { + return className; + } + + /** + * @param className the className to set + */ + public void setClassContainer(String className) { + this.className = className; + } + + /** + * @return the staticCoderField + */ + public String getStaticCoderField() { + return staticCoderField; + } + + /** + * @param staticCoderField the staticGson to set + */ + public void setStaticCoderField(String staticCoderField) { + this.staticCoderField = staticCoderField; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("CustomCoder [className=").append(className).append(", staticCoderField=") + .append(staticCoderField).append("]"); + return builder.toString(); + } + } + + public static class CustomGsonCoder extends CustomCoder { + + public CustomGsonCoder(String className, String staticCoderField) { + super(className, staticCoderField); + } + + public CustomGsonCoder(String customGson) throws IllegalArgumentException { + super(customGson); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("CustomGsonCoder [toString()=").append(super.toString()).append("]"); + return builder.toString(); + } + + } + + public static class CustomJacksonCoder extends CustomCoder { + + public CustomJacksonCoder(String className, String staticCoderField) { + super(className, staticCoderField); + } + + public CustomJacksonCoder(String customJackson) throws IllegalArgumentException { + super(customJackson); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("CustomJacksonCoder [toString()=").append(super.toString()).append("]"); + return builder.toString(); + } + + } + + /** + * Coder/Decoder class and Filter container. The decoder class is potential, + * in order to be operational needs to be fetched from an available + * class loader. + * + */ + public static class PotentialCoderFilter { + + /** + * decoder class (pending from being able to be fetched and found + * in some class loader) + */ + protected String codedClass; + + /** + * filters to apply to the selection of the decodedClass; + */ + protected JsonProtocolFilter filter; + + /** + * constructor + * + * @param codedClass decoder class + * @param filter filters to apply + */ + public PotentialCoderFilter(String codedClass, JsonProtocolFilter filter) { + this.codedClass = codedClass; + this.filter = filter; + } + + /** + * @return the decodedClass + */ + public String getCodedClass() { + return codedClass; + } + + /** + * @param decodedClass the decodedClass to set + */ + public void setCodedClass(String decodedClass) { + this.codedClass = decodedClass; + } + + /** + * @return the filter + */ + public JsonProtocolFilter getFilter() { + return filter; + } + + /** + * @param filter the filter to set + */ + public void setFilter(JsonProtocolFilter filter) { + this.filter = filter; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("PotentialCoderFilter [codedClass=").append(codedClass).append(", filter=").append(filter) + .append("]"); + return builder.toString(); + } + } + + /** + * the source topic + */ + protected final String topic; + + /** + * List of decoder -> filters + */ + protected final List<PotentialCoderFilter> coderFilters; + + /** + * custom gson coder that this controller prefers to use instead of the framework ones + */ + protected CustomGsonCoder customGsonCoder; + + /** + * custom jackson coder that this controller prefers to use instead of the framework ones + */ + protected CustomJacksonCoder customJacksonCoder; + + /** + * Constructor + * + * @param decoderFilters list of decoders and associated filters + * @param topic the topic + */ + public TopicCoderFilterConfiguration(String topic, List<PotentialCoderFilter> decoderFilters, + CustomGsonCoder customGsonCoder, + CustomJacksonCoder customJacksonCoder) { + this.coderFilters = decoderFilters; + this.topic = topic; + this.customGsonCoder = customGsonCoder; + this.customJacksonCoder = customJacksonCoder; + } + + /** + * @return the topic + */ + public String getTopic() { + return topic; + } + + /** + * @return the decoderFilters + */ + public List<PotentialCoderFilter> getCoderFilters() { + return coderFilters; + } + + /** + * @return the customGsonCoder + */ + public CustomGsonCoder getCustomGsonCoder() { + return customGsonCoder; + } + + /** + * @param customGsonCoder the customGsonCoder to set + */ + public void setCustomGsonCoder(CustomGsonCoder customGsonCoder) { + this.customGsonCoder = customGsonCoder; + } + + /** + * @return the customJacksonCoder + */ + public CustomJacksonCoder getCustomJacksonCoder() { + return customJacksonCoder; + } + + /** + * @param customJacksonCoder the customJacksonCoder to set + */ + public void setCustomJacksonCoder(CustomJacksonCoder customJacksonCoder) { + this.customJacksonCoder = customJacksonCoder; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("TopicCoderFilterConfiguration [topic=").append(topic).append(", coderFilters=") + .append(coderFilters).append(", customGsonCoder=").append(customGsonCoder) + .append(", customJacksonCoder=").append(customJacksonCoder).append("]"); + return builder.toString(); + } + + +} diff --git a/policy-management/src/main/java/org/onap/policy/drools/protocol/configuration/ControllerConfiguration.java b/policy-management/src/main/java/org/onap/policy/drools/protocol/configuration/ControllerConfiguration.java new file mode 100644 index 00000000..ded7a52c --- /dev/null +++ b/policy-management/src/main/java/org/onap/policy/drools/protocol/configuration/ControllerConfiguration.java @@ -0,0 +1,280 @@ +/*- + * ============LICENSE_START======================================================= + * policy-management + * ================================================================================ + * Copyright (C) 2017 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.drools.protocol.configuration; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + + +/** + * Drools Related Information + * + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ControllerConfiguration { + + public static final String CONFIG_CONTROLLER_OPERATION_CREATE = "create"; + public static final String CONFIG_CONTROLLER_OPERATION_UPDATE = "update"; + public static final String CONFIG_CONTROLLER_OPERATION_LOCK = "lock"; + public static final String CONFIG_CONTROLLER_OPERATION_UNLOCK = "unlock"; + + /** + * + * (Required) + * + */ + @JsonProperty("name") + private String name; + /** + * Set of operations that can be applied to a controller: create, lock + * (Required) + * + */ + @JsonProperty("operation") + private String operation; + /** + * Maven Related Information + * + */ + @JsonProperty("drools") + private DroolsConfiguration drools; + @JsonIgnore + private Map<String, Object> additionalProperties = new HashMap<String, Object>(); + protected final static Object NOT_FOUND_VALUE = new Object(); + + /** + * No args constructor for use in serialization + * + */ + public ControllerConfiguration() { + } + + /** + * + * @param name + * @param drools + * @param operation + */ + public ControllerConfiguration(String name, String operation, DroolsConfiguration drools) { + this.name = name; + this.operation = operation; + this.drools = drools; + } + + /** + * + * (Required) + * + * @return + * The name + */ + @JsonProperty("name") + public String getName() { + return name; + } + + /** + * + * (Required) + * + * @param name + * The name + */ + @JsonProperty("name") + public void setName(String name) { + this.name = name; + } + + public ControllerConfiguration withName(String name) { + this.name = name; + return this; + } + + /** + * Set of operations that can be applied to a controller: create, lock + * (Required) + * + * @return + * The operation + */ + @JsonProperty("operation") + public String getOperation() { + return operation; + } + + /** + * Set of operations that can be applied to a controller: create, lock + * (Required) + * + * @param operation + * The operation + */ + @JsonProperty("operation") + public void setOperation(String operation) { + this.operation = operation; + } + + public ControllerConfiguration withOperation(String operation) { + this.operation = operation; + return this; + } + + /** + * Maven Related Information + * + * @return + * The drools + */ + @JsonProperty("drools") + public DroolsConfiguration getDrools() { + return drools; + } + + /** + * Maven Related Information + * + * @param drools + * The drools + */ + @JsonProperty("drools") + public void setDrools(DroolsConfiguration drools) { + this.drools = drools; + } + + public ControllerConfiguration withDrools(DroolsConfiguration drools) { + this.drools = drools; + return this; + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } + + @JsonAnyGetter + public Map<String, Object> getAdditionalProperties() { + return this.additionalProperties; + } + + @JsonAnySetter + public void setAdditionalProperty(String name, Object value) { + this.additionalProperties.put(name, value); + } + + public ControllerConfiguration withAdditionalProperty(String name, Object value) { + this.additionalProperties.put(name, value); + return this; + } + + protected boolean declaredProperty(String name, Object value) { + switch (name) { + case "name": + if (value instanceof String) { + setName(((String) value)); + } else { + throw new IllegalArgumentException(("property \"name\" is of type \"java.lang.String\", but got "+ value.getClass().toString())); + } + return true; + case "operation": + if (value instanceof String) { + setOperation(((String) value)); + } else { + throw new IllegalArgumentException(("property \"operation\" is of type \"java.lang.String\", but got "+ value.getClass().toString())); + } + return true; + case "drools": + if (value instanceof DroolsConfiguration) { + setDrools(((DroolsConfiguration) value)); + } else { + throw new IllegalArgumentException(("property \"drools\" is of type \"org.onap.policy.drools.protocol.configuration.Drools\", but got "+ value.getClass().toString())); + } + return true; + default: + return false; + } + } + + protected Object declaredPropertyOrNotFound(String name, Object notFoundValue) { + switch (name) { + case "name": + return getName(); + case "operation": + return getOperation(); + case "drools": + return getDrools(); + default: + return notFoundValue; + } + } + + @SuppressWarnings({ + "unchecked" + }) + public<T >T get(String name) { + Object value = declaredPropertyOrNotFound(name, ControllerConfiguration.NOT_FOUND_VALUE); + if (ControllerConfiguration.NOT_FOUND_VALUE!= value) { + return ((T) value); + } else { + return ((T) getAdditionalProperties().get(name)); + } + } + + public void set(String name, Object value) { + if (!declaredProperty(name, value)) { + getAdditionalProperties().put(name, ((Object) value)); + } + } + + public ControllerConfiguration with(String name, Object value) { + if (!declaredProperty(name, value)) { + getAdditionalProperties().put(name, ((Object) value)); + } + return this; + } + + @Override + public int hashCode() { + return new HashCodeBuilder().append(name).append(operation).append(drools).append(additionalProperties).toHashCode(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof ControllerConfiguration) == false) { + return false; + } + ControllerConfiguration rhs = ((ControllerConfiguration) other); + return new EqualsBuilder().append(name, rhs.name).append(operation, rhs.operation).append(drools, rhs.drools).append(additionalProperties, rhs.additionalProperties).isEquals(); + } + +} diff --git a/policy-management/src/main/java/org/onap/policy/drools/protocol/configuration/DroolsConfiguration.java b/policy-management/src/main/java/org/onap/policy/drools/protocol/configuration/DroolsConfiguration.java new file mode 100644 index 00000000..5f32d7a4 --- /dev/null +++ b/policy-management/src/main/java/org/onap/policy/drools/protocol/configuration/DroolsConfiguration.java @@ -0,0 +1,278 @@ +/*- + * ============LICENSE_START======================================================= + * policy-management + * ================================================================================ + * Copyright (C) 2017 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.drools.protocol.configuration; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + + +/** + * Maven Related Information + * + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class DroolsConfiguration { + + /** + * Maven Artifact ID + * (Required) + * + */ + @JsonProperty("artifactId") + private String artifactId; + /** + * Maven Group ID + * (Required) + * + */ + @JsonProperty("groupId") + private String groupId; + /** + * Maven Version + * (Required) + * + */ + @JsonProperty("version") + private String version; + @JsonIgnore + private Map<String, Object> additionalProperties = new HashMap<String, Object>(); + protected final static Object NOT_FOUND_VALUE = new Object(); + + /** + * No args constructor for use in serialization + * + */ + public DroolsConfiguration() { + } + + /** + * + * @param groupId + * @param artifactId + * @param version + */ + public DroolsConfiguration(String artifactId, String groupId, String version) { + this.artifactId = artifactId; + this.groupId = groupId; + this.version = version; + } + + /** + * Maven Artifact ID + * (Required) + * + * @return + * The artifactId + */ + @JsonProperty("artifactId") + public String getArtifactId() { + return artifactId; + } + + /** + * Maven Artifact ID + * (Required) + * + * @param artifactId + * The artifactId + */ + @JsonProperty("artifactId") + public void setArtifactId(String artifactId) { + this.artifactId = artifactId; + } + + public DroolsConfiguration withArtifactId(String artifactId) { + this.artifactId = artifactId; + return this; + } + + /** + * Maven Group ID + * (Required) + * + * @return + * The groupId + */ + @JsonProperty("groupId") + public String getGroupId() { + return groupId; + } + + /** + * Maven Group ID + * (Required) + * + * @param groupId + * The groupId + */ + @JsonProperty("groupId") + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public DroolsConfiguration withGroupId(String groupId) { + this.groupId = groupId; + return this; + } + + /** + * Maven Version + * (Required) + * + * @return + * The version + */ + @JsonProperty("version") + public String getVersion() { + return version; + } + + /** + * Maven Version + * (Required) + * + * @param version + * The version + */ + @JsonProperty("version") + public void setVersion(String version) { + this.version = version; + } + + public DroolsConfiguration withVersion(String version) { + this.version = version; + return this; + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } + + @JsonAnyGetter + public Map<String, Object> getAdditionalProperties() { + return this.additionalProperties; + } + + @JsonAnySetter + public void setAdditionalProperty(String name, Object value) { + this.additionalProperties.put(name, value); + } + + public DroolsConfiguration withAdditionalProperty(String name, Object value) { + this.additionalProperties.put(name, value); + return this; + } + + protected boolean declaredProperty(String name, Object value) { + switch (name) { + case "artifactId": + if (value instanceof String) { + setArtifactId(((String) value)); + } else { + throw new IllegalArgumentException(("property \"artifactId\" is of type \"java.lang.String\", but got "+ value.getClass().toString())); + } + return true; + case "groupId": + if (value instanceof String) { + setGroupId(((String) value)); + } else { + throw new IllegalArgumentException(("property \"groupId\" is of type \"java.lang.String\", but got "+ value.getClass().toString())); + } + return true; + case "version": + if (value instanceof String) { + setVersion(((String) value)); + } else { + throw new IllegalArgumentException(("property \"version\" is of type \"java.lang.String\", but got "+ value.getClass().toString())); + } + return true; + default: + return false; + } + } + + protected Object declaredPropertyOrNotFound(String name, Object notFoundValue) { + switch (name) { + case "artifactId": + return getArtifactId(); + case "groupId": + return getGroupId(); + case "version": + return getVersion(); + default: + return notFoundValue; + } + } + + @SuppressWarnings({ + "unchecked" + }) + public<T >T get(String name) { + Object value = declaredPropertyOrNotFound(name, DroolsConfiguration.NOT_FOUND_VALUE); + if (DroolsConfiguration.NOT_FOUND_VALUE!= value) { + return ((T) value); + } else { + return ((T) getAdditionalProperties().get(name)); + } + } + + public void set(String name, Object value) { + if (!declaredProperty(name, value)) { + getAdditionalProperties().put(name, ((Object) value)); + } + } + + public DroolsConfiguration with(String name, Object value) { + if (!declaredProperty(name, value)) { + getAdditionalProperties().put(name, ((Object) value)); + } + return this; + } + + @Override + public int hashCode() { + return new HashCodeBuilder().append(artifactId).append(groupId).append(version).append(additionalProperties).toHashCode(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof DroolsConfiguration) == false) { + return false; + } + DroolsConfiguration rhs = ((DroolsConfiguration) other); + return new EqualsBuilder().append(artifactId, rhs.artifactId).append(groupId, rhs.groupId).append(version, rhs.version).append(additionalProperties, rhs.additionalProperties).isEquals(); + } + +} diff --git a/policy-management/src/main/java/org/onap/policy/drools/protocol/configuration/PdpdConfiguration.java b/policy-management/src/main/java/org/onap/policy/drools/protocol/configuration/PdpdConfiguration.java new file mode 100644 index 00000000..d7295fd4 --- /dev/null +++ b/policy-management/src/main/java/org/onap/policy/drools/protocol/configuration/PdpdConfiguration.java @@ -0,0 +1,283 @@ +/*- + * ============LICENSE_START======================================================= + * policy-management + * ================================================================================ + * Copyright (C) 2017 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.drools.protocol.configuration; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; + + +/** + * ENGINE-CONFIGURATION + * <p> + * + * + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class PdpdConfiguration { + + /** + * Controller Entity ID + */ + public static final String CONFIG_ENTITY_CONTROLLER = "controller"; + + /** + * Unique Transaction ID. This is an UUID. + * (Required) + * + */ + @JsonProperty("requestID") + private String requestID; + /** + * Set of entities on which configuration can be performed: controller + * (Required) + * + */ + @JsonProperty("entity") + private String entity; + /** + * Controller Information, only applicable when the entity is set to controller + * + */ + @JsonProperty("controllers") + private List<ControllerConfiguration> controllers = new ArrayList<ControllerConfiguration>(); + @JsonIgnore + private Map<String, Object> additionalProperties = new HashMap<String, Object>(); + protected final static Object NOT_FOUND_VALUE = new Object(); + + /** + * No args constructor for use in serialization + * + */ + public PdpdConfiguration() { + } + + /** + * + * @param controller + * @param requestID + * @param entity + */ + public PdpdConfiguration(String requestID, String entity, List<ControllerConfiguration> controllers) { + this.requestID = requestID; + this.entity = entity; + this.controllers = controllers; + } + + /** + * Unique Transaction ID. This is an UUID. + * (Required) + * + * @return + * The requestID + */ + @JsonProperty("requestID") + public String getRequestID() { + return requestID; + } + + /** + * Unique Transaction ID. This is an UUID. + * (Required) + * + * @param requestID + * The requestID + */ + @JsonProperty("requestID") + public void setRequestID(String requestID) { + this.requestID = requestID; + } + + public PdpdConfiguration withRequestID(String requestID) { + this.requestID = requestID; + return this; + } + + /** + * Set of entities on which configuration can be performed: controller + * (Required) + * + * @return + * The entity + */ + @JsonProperty("entity") + public String getEntity() { + return entity; + } + + /** + * Set of entities on which configuration can be performed: controller + * (Required) + * + * @param entity + * The entity + */ + @JsonProperty("entity") + public void setEntity(String entity) { + this.entity = entity; + } + + public PdpdConfiguration withEntity(String entity) { + this.entity = entity; + return this; + } + + /** + * Controller Information, only applicable when the entity is set to controller + * + * @return + * The controller + */ + @JsonProperty("controller") + public List<ControllerConfiguration> getControllers() { + return controllers; + } + + /** + * Controller Information, only applicable when the entity is set to controller + * + * @param controller + * The controller + */ + @JsonProperty("controller") + public void setControllers(List<ControllerConfiguration> controllers) { + this.controllers = controllers; + } + + public PdpdConfiguration withController(List<ControllerConfiguration> controllers) { + this.controllers = controllers; + return this; + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } + + @JsonAnyGetter + public Map<String, Object> getAdditionalProperties() { + return this.additionalProperties; + } + + @JsonAnySetter + public void setAdditionalProperty(String name, Object value) { + this.additionalProperties.put(name, value); + } + + public PdpdConfiguration withAdditionalProperty(String name, Object value) { + this.additionalProperties.put(name, value); + return this; + } + + @SuppressWarnings("unchecked") + protected boolean declaredProperty(String name, Object value) { + switch (name) { + case "requestID": + if (value instanceof String) { + setRequestID(((String) value)); + } else { + throw new IllegalArgumentException(("property \"requestID\" is of type \"java.lang.String\", but got "+ value.getClass().toString())); + } + return true; + case "entity": + if (value instanceof String) { + setEntity(((String) value)); + } else { + throw new IllegalArgumentException(("property \"entity\" is of type \"java.lang.String\", but got "+ value.getClass().toString())); + } + return true; + case "controllers": + if (value instanceof List) { + setControllers(((List<ControllerConfiguration> ) value)); + } else { + throw new IllegalArgumentException(("property \"controllers\" is of type \"java.util.List<org.onap.policy.drools.protocol.configuration.Controller>\", but got "+ value.getClass().toString())); + } + return true; + default: + return false; + } + } + + protected Object declaredPropertyOrNotFound(String name, Object notFoundValue) { + switch (name) { + case "requestID": + return getRequestID(); + case "entity": + return getEntity(); + case "controllers": + return getControllers(); + default: + return notFoundValue; + } + } + + @SuppressWarnings({ + "unchecked" + }) + public<T >T get(String name) { + Object value = declaredPropertyOrNotFound(name, PdpdConfiguration.NOT_FOUND_VALUE); + if (PdpdConfiguration.NOT_FOUND_VALUE!= value) { + return ((T) value); + } else { + return ((T) getAdditionalProperties().get(name)); + } + } + + public void set(String name, Object value) { + if (!declaredProperty(name, value)) { + getAdditionalProperties().put(name, ((Object) value)); + } + } + + public PdpdConfiguration with(String name, Object value) { + if (!declaredProperty(name, value)) { + getAdditionalProperties().put(name, ((Object) value)); + } + return this; + } + + @Override + public int hashCode() { + return new HashCodeBuilder().append(requestID).append(entity).append(controllers).append(additionalProperties).toHashCode(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if ((other instanceof PdpdConfiguration) == false) { + return false; + } + PdpdConfiguration rhs = ((PdpdConfiguration) other); + return new EqualsBuilder().append(requestID, rhs.requestID).append(entity, rhs.entity).append(controllers, rhs.controllers).append(additionalProperties, rhs.additionalProperties).isEquals(); + } + +} diff --git a/policy-management/src/main/java/org/onap/policy/drools/server/restful/RestManager.java b/policy-management/src/main/java/org/onap/policy/drools/server/restful/RestManager.java new file mode 100644 index 00000000..4f5e33e7 --- /dev/null +++ b/policy-management/src/main/java/org/onap/policy/drools/server/restful/RestManager.java @@ -0,0 +1,2335 @@ +/*- + * ============LICENSE_START======================================================= + * policy-management + * ================================================================================ + * Copyright (C) 2017 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.drools.server.restful; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.UUID; +import java.util.regex.Pattern; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.onap.policy.drools.controller.DroolsController; +import org.onap.policy.drools.event.comm.TopicEndpoint; +import org.onap.policy.drools.event.comm.TopicSink; +import org.onap.policy.drools.event.comm.TopicSource; +import org.onap.policy.drools.event.comm.bus.DmaapTopicSink; +import org.onap.policy.drools.event.comm.bus.DmaapTopicSource; +import org.onap.policy.drools.event.comm.bus.UebTopicSink; +import org.onap.policy.drools.event.comm.bus.UebTopicSource; +import org.onap.policy.drools.features.PolicyControllerFeatureAPI; +import org.onap.policy.drools.features.PolicyEngineFeatureAPI; +import org.onap.policy.drools.properties.PolicyProperties; +import org.onap.policy.drools.protocol.coders.EventProtocolCoder; +import org.onap.policy.drools.protocol.coders.EventProtocolCoder.CoderFilters; +import org.onap.policy.drools.protocol.coders.JsonProtocolFilter; +import org.onap.policy.drools.protocol.coders.JsonProtocolFilter.FilterRule; +import org.onap.policy.drools.protocol.coders.ProtocolCoderToolset; +import org.onap.policy.drools.protocol.configuration.ControllerConfiguration; +import org.onap.policy.drools.protocol.configuration.PdpdConfiguration; +import org.onap.policy.drools.system.PolicyController; +import org.onap.policy.drools.system.PolicyEngine; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.qos.logback.classic.LoggerContext; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; +import io.swagger.annotations.Info; +import io.swagger.annotations.SwaggerDefinition; +import io.swagger.annotations.Tag; + + +/** + * Telemetry JAX-RS Interface to the PDP-D + */ + +@Path("/policy/pdp") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@Api +@SwaggerDefinition( + info = @Info( + description = "PDP-D Telemetry Services", + version = "v1.0", + title = "PDP-D Telemetry" + ), + consumes = {MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN}, + produces = {MediaType.APPLICATION_JSON}, + schemes = {SwaggerDefinition.Scheme.HTTP}, + tags = { + @Tag(name = "pdp-d-telemetry", description = "Drools PDP Telemetry Operations") + } +) +public class RestManager { + /** + * Logger + */ + private static Logger logger = LoggerFactory.getLogger(RestManager.class); + + @GET + @Path("engine") + @ApiOperation( + value="Retrieves the Engine Operational Status", + notes="Top-level abstraction. Provides a global view of resources", + response=PolicyEngine.class + ) + public Response engine() { + return Response.status(Response.Status.OK).entity(PolicyEngine.manager).build(); + } + + @DELETE + @Path("engine") + @ApiOperation( + value="Shuts down the Engine", + notes="Deleting the engine, the top-level abstraction, equivalenty shuts it down", + response=PolicyEngine.class + ) + public Response engineShutdown() { + try { + PolicyEngine.manager.shutdown(); + } catch (IllegalStateException e) { + logger.error("{}: cannot shutdown {} because of {}", this, PolicyEngine.manager, e.getMessage(), e); + return Response.status(Response.Status.BAD_REQUEST). + entity(PolicyEngine.manager). + build(); + } + + return Response.status(Response.Status.OK). + entity(PolicyEngine.manager). + build(); + } + + @GET + @Path("engine/features") + @ApiOperation( + value="Engine Features", + notes="Provides the list of loaded features using the PolicyEngineFeatureAPI", + responseContainer="List" + ) + public Response engineFeatures() { + return Response.status(Response.Status.OK).entity(PolicyEngine.manager.getFeatures()).build(); + } + + @GET + @Path("engine/features/inventory") + @ApiOperation( + value="Engine Detailed Feature Inventory", + notes="Provides detailed list of loaded features using the PolicyEngineFeatureAPI", + responseContainer="List", + response=PolicyEngineFeatureAPI.class + ) + public Response engineFeaturesInventory() { + return Response.status(Response.Status.OK).entity(PolicyEngine.manager.getFeatureProviders()).build(); + } + + @GET + @Path("engine/features/{featureName}") + @ApiOperation( + value="Engine Feature", + notes="Provides Details for a given feature Engine Provider", + response=PolicyEngineFeatureAPI.class + ) + @ApiResponses(value = { + @ApiResponse(code=404, message = "The feature cannot be found") + }) + public Response engineFeature(@ApiParam(value="Feature Name", required=true) + @PathParam("featureName") String featureName) { + try { + return Response.status(Response.Status.OK). + entity(PolicyEngine.manager.getFeatureProvider(featureName)).build(); + } catch(IllegalArgumentException iae) { + return Response.status(Response.Status.NOT_FOUND). + entity(new Error(iae.getMessage())).build(); + } + } + + @GET + @Path("engine/inputs") + @ApiOperation( + value="Engine Input Ports", + notes="List of input ports", + responseContainer="List" + ) + public Response engineInputs() { + return Response.status(Response.Status.OK). + entity(Arrays.asList(Inputs.values())). + build(); + } + + @POST + @Path("engine/inputs/configuration") + @ApiOperation( + value="Engine Input Configuration Requests", + notes="Feeds a configuration request input into the Engine" + ) + @ApiResponses(value={ + @ApiResponse(code=406, message = "The configuration request cannot be honored") + }) + public Response engineUpdate( + @ApiParam(value="Configuration to apply", required=true) PdpdConfiguration configuration) { + PolicyController controller = null; + boolean success = true; + try { + success = PolicyEngine.manager.configure(configuration); + } catch (Exception e) { + success = false; + logger.info("{}: cannot configure {} because of {}", this, PolicyEngine.manager, e.getMessage(), e); + } + + if (!success) + return Response.status(Response.Status.NOT_ACCEPTABLE). + entity(new Error("cannot perform operation")).build(); + else + return Response.status(Response.Status.OK).entity(controller).build(); + } + + @GET + @Path("engine/properties") + @ApiOperation( + value="Engine Configuration Properties", + notes="Used for booststrapping the engine", + response=Properties.class + ) + public Response engineProperties() { + return Response.status(Response.Status.OK).entity(PolicyEngine.manager.getProperties()).build(); + } + + @GET + @Path("engine/switches") + @ApiOperation( + value="Engine Control Switches", + notes="List of the Engine Control Switches", + responseContainer="List" + ) + public Response engineSwitches() { + return Response.status(Response.Status.OK). + entity(Arrays.asList(Switches.values())). + build(); + } + + @PUT + @Path("engine/switches/activation") + @ApiOperation( + value="Switches on the Engine Activation Switch", + notes="Turns on Activation Switch on the Engine. This order entails that the engine " + + "and controllers are unlocked and started", + response=PolicyEngine.class + ) + public Response engineActivation() { + boolean success = true; + try { + PolicyEngine.manager.activate(); + } catch (Exception e) { + success = false; + logger.info("{}: cannot activate {} because of {}", this, PolicyEngine.manager, e.getMessage(), e); + } + + if (!success) + return Response.status(Response.Status.NOT_ACCEPTABLE). + entity(new Error("cannot perform operation")).build(); + else + return Response.status(Response.Status.OK).entity(PolicyEngine.manager).build(); + } + + @DELETE + @Path("engine/switches/activation") + @ApiOperation( + value="Switches off Engine Activation Switch", + notes="Turns off the Activation Switch on the Engine. This order entails that the engine " + + "and controllers are locked (with the exception of those resources defined as unmanaged)", + response=PolicyEngine.class + ) + public Response engineDeactivation() { + boolean success = true; + try { + PolicyEngine.manager.deactivate(); + } catch (Exception e) { + success = false; + logger.info("{}: cannot deactivate {} because of {}", this, PolicyEngine.manager, e.getMessage(), e); + } + + if (!success) + return Response.status(Response.Status.NOT_ACCEPTABLE). + entity(new Error("cannot perform operation")).build(); + else + return Response.status(Response.Status.OK).entity(PolicyEngine.manager).build(); + } + + @PUT + @Path("engine/switches/lock") + @ApiOperation( + value="Switches on the Engine Lock Control", + notes="This switch locks all the engine resources as a whole, except those that are defined unmanaged", + response=PolicyEngine.class + ) + @ApiResponses(value = { + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled") + }) + public Response engineLock() { + boolean success = PolicyEngine.manager.lock(); + if (success) + return Response.status(Status.OK). + entity(PolicyEngine.manager). + build(); + else + return Response.status(Status.NOT_ACCEPTABLE). + entity(new Error("cannot perform operation")). + build(); + } + + @DELETE + @Path("engine/switches/lock") + @ApiOperation( + value="Switches off the Lock control", + notes="This switch locks all the engine resources as a whole, except those that are defined unmanaged", + response=PolicyEngine.class + ) + @ApiResponses(value = { + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled") + }) + public Response engineUnlock() { + boolean success = PolicyEngine.manager.unlock(); + if (success) + return Response.status(Status.OK). + entity(PolicyEngine.manager). + build(); + else + return Response.status(Status.NOT_ACCEPTABLE). + entity(new Error("cannot perform operation")). + build(); + } + + @GET + @Path("engine/controllers") + @ApiOperation( + value="Lists the Policy Controllers Names", + notes="Unique Policy Controller Identifiers", + responseContainer="List" + ) + public Response controllers() { + return Response.status(Response.Status.OK).entity(PolicyEngine.manager.getPolicyControllerIds()).build(); + } + + @GET + @Path("engine/controllers/inventory") + @ApiOperation( + value="Lists the Policy Controllers", + notes="Detailed list of Policy Controllers", + responseContainer="List", + response=PolicyController.class + ) + public Response controllerInventory() { + return Response.status(Response.Status.OK).entity(PolicyEngine.manager.getPolicyControllers()).build(); + } + + @POST + @Path("engine/controllers") + @ApiOperation( + value="Creates and starts a new Policy Controller", + notes="Controller creation based on properties", + response=PolicyController.class + ) + @ApiResponses(value = { + @ApiResponse(code=400, message = "Invalid configuration information has been provided"), + @ApiResponse(code=304, message = "The controller already exists"), + @ApiResponse(code=406, message = "The administrative state of the system prevents it " + + "from processing this request"), + @ApiResponse(code=206, message = "The controller has been created " + + "but cannot be started"), + @ApiResponse(code=201, message = "The controller has been succesfully created and started") + }) + public Response controllerAdd(@ApiParam(value="Configuration Properties to apply", required = true) + Properties config) { + if (config == null) + return Response.status(Response.Status.BAD_REQUEST). + entity(new Error("A configuration must be provided")). + build(); + + String controllerName = config.getProperty(PolicyProperties.PROPERTY_CONTROLLER_NAME); + if (controllerName == null || controllerName.isEmpty()) + return Response.status(Response.Status.BAD_REQUEST). + entity(new Error + ("Configuration must have an entry for " + + PolicyProperties.PROPERTY_CONTROLLER_NAME)). + build(); + + PolicyController controller; + try { + controller = PolicyController.factory.get(controllerName); + if (controller != null) + return Response.status(Response.Status.NOT_MODIFIED). + entity(controller). + build(); + } catch (IllegalArgumentException e) { + // This is OK + } catch (IllegalStateException e) { + logger.info("{}: cannot get policy-controller because of {}", this, e.getMessage(), e); + return Response.status(Response.Status.NOT_ACCEPTABLE). + entity(new Error(controllerName + " not found")).build(); + } + + try { + controller = PolicyEngine.manager.createPolicyController + (config.getProperty(PolicyProperties.PROPERTY_CONTROLLER_NAME), config); + } catch (IllegalArgumentException | IllegalStateException e) { + logger.warn("{}: cannot create policy-controller because of {}", this, e.getMessage(), e); + return Response.status(Response.Status.BAD_REQUEST). + entity(new Error(e.getMessage())). + build(); + } + + try { + boolean success = controller.start(); + if (!success) { + logger.info("{}: cannot start {}", this, controller); + return Response.status(Response.Status.PARTIAL_CONTENT). + entity(new Error(controllerName + " can't be started")).build(); + } + } catch (IllegalStateException e) { + logger.info("{}: cannot start {} because of {}", this, controller, e.getMessage(), e);; + return Response.status(Response.Status.PARTIAL_CONTENT). + entity(controller).build(); + } + + return Response.status(Response.Status.CREATED). + entity(controller). + build(); + } + + @GET + @Path("engine/controllers/features") + @ApiOperation( + value="Lists of Feature Providers Identifiers", + notes="Unique Policy Controller Identifiers", + responseContainer="List" + ) + public Response controllerFeatures() { + return Response.status(Response.Status.OK).entity(PolicyEngine.manager.getFeatures()).build(); + } + + @GET + @Path("engine/controllers/features/inventory") + @ApiOperation( + value="Detailed Controllers Feature Inventory", + notes="Provides detailed list of loaded features using the PolicyControllerFeatureAPI", + responseContainer="List", + response=PolicyControllerFeatureAPI.class + ) + public Response controllerFeaturesInventory() { + return Response.status(Response.Status.OK). + entity(PolicyController.factory.getFeatureProviders()). + build(); + } + + @GET + @Path("engine/controllers/features/{featureName}") + @ApiOperation( + value="Controller Feature", + notes="Provides Details for a given Policy Controller feature provider", + response=PolicyControllerFeatureAPI.class + ) + @ApiResponses(value = { + @ApiResponse(code=404, message = "The feature cannot be found") + }) + public Response controllerFeature(@ApiParam(value="Feature Name", required=true) + @PathParam("featureName") String featureName) { + try { + return Response.status(Response.Status.OK). + entity(PolicyController.factory.getFeatureProvider(featureName)). + build(); + } catch(IllegalArgumentException iae) { + return Response.status(Response.Status.NOT_FOUND). + entity(new Error(iae.getMessage())).build(); + } + } + + @GET + @Path("engine/controllers/{controller}") + @ApiOperation( + value="Retrieves a Policy Controller", + notes="A Policy Controller is a concrete drools application abstraction. " + + "It aggregates networking, drools, and other resources," + + "as provides operational controls over drools applications", + response=PolicyController.class + ) + @ApiResponses(value = { + @ApiResponse(code=404, message="The controller cannot be found"), + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled") + }) + public Response controller(@ApiParam(value="Policy Controller Name", required=true) + @PathParam("controller") String controllerName) { + try { + return Response.status(Response.Status.OK). + entity(PolicyController.factory.get(controllerName)). + build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.NOT_FOUND). + entity(new Error(controllerName + " not found")). + build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.NOT_ACCEPTABLE). + entity(new Error(controllerName + " not acceptable")). + build(); + } + } + + @DELETE + @Path("engine/controllers/{controller}") + @ApiOperation( + value="Deletes a Policy Controller", + notes="A Policy Controller is a concrete drools application abstraction. " + + "It aggregates networking, drools, and other resources," + + "as provides operational controls over drools applications", + response=PolicyController.class + ) + @ApiResponses(value = { + @ApiResponse(code=404, message="The controller cannot be found"), + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled"), + @ApiResponse(code=500, message="A problem has occurred while deleting the Policy Controller") + }) + public Response controllerDelete(@ApiParam(value="Policy Controller Name", required=true) + @PathParam("controller") String controllerName) { + + PolicyController controller; + try { + controller = + PolicyController.factory.get(controllerName); + if (controller == null) + return Response.status(Response.Status.BAD_REQUEST). + entity(new Error(controllerName + " does not exist")). + build(); + } catch (IllegalArgumentException e) { + logger.info("{}: cannot get policy-controller {} because of {}", this, controllerName, e.getMessage(), e); + return Response.status(Response.Status.BAD_REQUEST). + entity(new Error(controllerName + " not found: " + e.getMessage())). + build(); + } catch (IllegalStateException e) { + logger.info("{}: cannot get policy-controller {} because of {}", this, controllerName, e.getMessage(), e); + return Response.status(Response.Status.NOT_ACCEPTABLE). + entity(new Error(controllerName + " not acceptable")).build(); + } + + try { + PolicyEngine.manager.removePolicyController(controllerName); + } catch (IllegalArgumentException | IllegalStateException e) { + logger.info("{}: cannot remove policy-controller {} because of {}", this, controllerName, e.getMessage(), e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR). + entity(new Error(e.getMessage())). + build(); + } + + return Response.status(Response.Status.OK). + entity(controller). + build(); + } + + @GET + @Path("engine/controllers/{controller}/properties") + @ApiOperation( + value="Retrieves the configuration properties of a Policy Controller", + notes="Configuration resources used by the controller if Properties format", + response=PolicyController.class + ) + @ApiResponses(value = { + @ApiResponse(code=404, message="The controller cannot be found"), + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled") + }) + public Response controllerProperties(@ApiParam(value="Policy Controller Name", required=true) + @PathParam("controller") String controllerName) { + try { + PolicyController controller = PolicyController.factory.get(controllerName); + return Response.status(Response.Status.OK). + entity(controller.getProperties()). + build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.NOT_FOUND). + entity(new Error(controllerName + " not found")). + build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.NOT_ACCEPTABLE). + entity(new Error(controllerName + " not acceptable")). + build(); + } + } + + @GET + @Path("engine/controllers/{controller}/inputs") + @ApiOperation( + value="Policy Controller Input Ports", + notes="List of input ports", + responseContainer="List" + ) + public Response controllerInputs() { + return Response.status(Response.Status.OK). + entity(Arrays.asList(Inputs.values())). + build(); + } + + @POST + @Path("engine/controllers/{controller}/inputs/configuration") + @ApiOperation( + value="Policy Controller Input Configuration Requests", + notes="Feeds a configuration request input into the given Policy Controller" + ) + @ApiResponses(value={ + @ApiResponse(code=400, message = "The configuration request is invalid"), + @ApiResponse(code=406, message = "The configuration request cannot be honored") + }) + public Response controllerUpdate(@ApiParam(value="Policy Controller Name", required=true) + @PathParam("controller") String controllerName, + @ApiParam(value="Configuration to apply", required=true) + ControllerConfiguration controllerConfiguration) { + + if (controllerName == null || controllerName.isEmpty() || + controllerConfiguration == null || + controllerConfiguration.getName().intern() != controllerName) + return Response.status(Response.Status.BAD_REQUEST). + entity("A valid or matching controller names must be provided"). + build(); + + PolicyController controller; + try { + controller = PolicyEngine.manager.updatePolicyController(controllerConfiguration); + if (controller == null) + return Response.status(Response.Status.BAD_REQUEST). + entity(new Error(controllerName + " does not exist")). + build(); + } catch (IllegalArgumentException e) { + logger.info("{}: cannot update policy-controller {} because of {}", this, controllerName, e.getMessage(), e); + return Response.status(Response.Status.BAD_REQUEST). + entity(new Error(controllerName + " not found: " + e.getMessage())). + build(); + } catch (Exception e) { + logger.info("{}: cannot update policy-controller {} because of {}", this, controllerName, e.getMessage(), e); + return Response.status(Response.Status.NOT_ACCEPTABLE). + entity(new Error(controllerName + " not acceptable")).build(); + } + + return Response.status(Response.Status.OK). + entity(controller). + build(); + } + + @GET + @Path("engine/controllers/{controller}/switches") + @ApiOperation( + value="Policy Controller Switches", + notes="List of the Policy Controller Switches", + responseContainer="List" + ) + public Response controllerSwitches() { + return Response.status(Response.Status.OK). + entity(Arrays.asList(Switches.values())). + build(); + } + + @PUT + @Path("engine/controllers/{controller}/switches/lock") + @ApiOperation( + value="Switches on the Policy Controller Lock Control", + notes="This action on the switch locks the Policy Controller", + response=PolicyController.class + ) + @ApiResponses(value = { + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled") + }) + public Response controllerLock(@ApiParam(value="Policy Controller Name", required=true) + @PathParam("controller") String controllerName) { + PolicyController policyController = PolicyController.factory.get(controllerName); + boolean success = policyController.lock(); + if (success) + return Response.status(Status.OK). + entity(policyController). + build(); + else + return Response.status(Status.NOT_ACCEPTABLE). + entity(new Error("Controller " + controllerName + " cannot be locked")). + build(); + } + + @DELETE + @Path("engine/controllers/{controller}/switches/lock") + @ApiOperation( + value="Switches off the Policy Controller Lock Control", + notes="This action on the switch unlocks the Policy Controller", + response=PolicyController.class + ) + @ApiResponses(value = { + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled") + }) + public Response controllerUnlock(@ApiParam(value="Policy Controller Name", required=true) + @PathParam("controller") String controllerName) { + PolicyController policyController = PolicyController.factory.get(controllerName); + boolean success = policyController.unlock(); + if (success) + return Response.status(Status.OK). + entity(policyController). + build(); + else + return Response.status(Status.NOT_ACCEPTABLE). + entity(new Error("Controller " + controllerName + " cannot be unlocked")). + build(); + } + + @GET + @Path("engine/controllers/{controller}/drools") + @ApiOperation( + value="Retrieves the Drools Controller subcomponent of the Policy Controller", + notes="The Drools Controller provides an abstraction over the Drools subsystem", + response=DroolsController.class + ) + @ApiResponses(value = { + @ApiResponse(code=404, message="The controller cannot be found"), + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled") + }) + public Response drools(@ApiParam(value="Policy Controller Name", required=true) + @PathParam("controller") String controllerName) { + try { + DroolsController drools = getDroolsController(controllerName); + return Response.status(Response.Status.OK). + entity(drools). + build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.NOT_FOUND). + entity(new Error(controllerName + " not found")). + build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.NOT_ACCEPTABLE). + entity(new Error(controllerName + " not acceptable")). + build(); + } + } + + @GET + @Path("engine/controllers/{controller}/drools/facts") + @ApiOperation( + value="Retrieves Facts Summary information for a given controller", + notes="Provides the session names, and a count of fact object in the drools working memory", + responseContainer="Map" + ) + @ApiResponses(value = { + @ApiResponse(code=404, message="The controller cannot be found"), + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled") + }) + public Response droolsFacts(@ApiParam(value="Policy Controller Name", required=true) + @PathParam("controller") String controllerName) { + try { + Map<String,Long> sessionCounts = new HashMap<>(); + DroolsController drools = getDroolsController(controllerName); + for (String session : drools.getSessionNames()) { + sessionCounts.put(session, drools.factCount(session)); + } + return Response.status(Response.Status.OK). + entity(sessionCounts). + build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.NOT_FOUND). + entity(new Error(controllerName + " not found")). + build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.NOT_ACCEPTABLE). + entity(new Error(controllerName + " not acceptable")). + build(); + } + } + + @GET + @Path("engine/controllers/{controller}/drools/facts/{session}") + @ApiOperation( + value="Retrieves Fact Types (classnames) for a given controller and its count", + notes="The fact types are the classnames of the objects inserted in the drools working memory", + responseContainer="Map" + ) + @ApiResponses(value = { + @ApiResponse(code=404, message="The controller or session cannot be found"), + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled") + }) + public Response droolsFacts(@ApiParam(value="Policy Controller Name", required=true) + @PathParam("controller") String controllerName, + @ApiParam(value="Drools Session Name", required=true) + @PathParam("session") String sessionName) { + try { + DroolsController drools = getDroolsController(controllerName); + return Response.status(Response.Status.OK). + entity(drools.factClassNames(sessionName)). + build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.NOT_FOUND). + entity(new Error("entity not found")). + build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.NOT_ACCEPTABLE). + entity(new Error(controllerName + ":" + sessionName + " not acceptable")). + build(); + } + } + + @GET + @Path("engine/controllers/{controller}/drools/facts/{session}/{factType}") + @ApiOperation( + value="Retrieves fact objects of a given type in the drools working memory" + + "for a given controller and session", + notes="The fact types are the classnames of the objects inserted in the drools working memory", + responseContainer="List" + ) + @ApiResponses(value = { + @ApiResponse(code=404, message="The controller, session, or fact type cannot be found"), + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled") + }) + public Response droolsFacts(@ApiParam(value="Fact count", required=false) + @DefaultValue("false") @QueryParam("count") boolean count, + @ApiParam(value="Policy Controller Name", required=true) + @PathParam("controller") String controllerName, + @ApiParam(value="Drools Session Name", required=true) + @PathParam("session") String sessionName, + @ApiParam(value="Drools Fact Type", required=true) + @PathParam("factType") String factType) { + try { + DroolsController drools = getDroolsController(controllerName); + List<Object> facts = drools.facts(sessionName, factType, false); + if (!count) + return Response.status(Response.Status.OK).entity(facts).build(); + else + return Response.status(Response.Status.OK).entity(facts.size()).build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.NOT_FOUND). + entity(new Error(controllerName + ":" + sessionName + ":" + factType + + " not found")). + build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.NOT_ACCEPTABLE). + entity(new Error(controllerName + ":" + sessionName + ":" + factType + + " not acceptable")). + build(); + } + } + + @DELETE + @Path("engine/controllers/{controller}/drools/facts/{session}/{factType}") + @ApiOperation( + value="Deletes all the fact objects of a given type from the drools working memory" + + "for a given controller and session. The objects retracted from the working " + + "memory are provided in the response.", + notes="The fact types are the classnames of the objects inserted in the drools working memory", + responseContainer="List" + ) + @ApiResponses(value = { + @ApiResponse(code=404, message="The controller, session, or fact type, cannot be found"), + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled"), + @ApiResponse(code=500, message="A server error has occurred processing this request") + }) + public Response droolsFactsDelete(@ApiParam(value="Policy Controller Name", required=true) + @PathParam("controller") String controllerName, + @ApiParam(value="Drools Session Name", required=true) + @PathParam("session") String sessionName, + @ApiParam(value="Drools Fact Type", required=true) + @PathParam("factType") String factType) { + try { + DroolsController drools = getDroolsController(controllerName); + List<Object> facts = drools.facts(sessionName, factType, true); + return Response.status(Response.Status.OK).entity(facts).build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.NOT_FOUND). + entity(new Error(controllerName + ":" + sessionName + ":" + factType + + " not found")). + build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.NOT_ACCEPTABLE). + entity(new Error(controllerName + ":" + sessionName + ":" + factType + + " not acceptable")). + build(); + } catch (Exception e) { + return Response.status(Response.Status.INTERNAL_SERVER_ERROR). + entity(new Error(e.getMessage())). + build(); + } + } + + @GET + @Path("engine/controllers/{controller}/drools/facts/{session}/{query}/{queriedEntity}") + @ApiOperation( + value="Gets all the fact objects returned by a DRL query with no parameters from the drools working memory" + + "for a given controller and session", + notes="The DRL query must be defined in the DRL file", + responseContainer="List" + ) + @ApiResponses(value = { + @ApiResponse(code=404, message="The controller, session, or query information, cannot be found"), + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled"), + @ApiResponse(code=500, message="A server error has occurred processing this request") + }) + public Response droolsFacts(@ApiParam(value="Fact count", required=false) + @DefaultValue("false") @QueryParam("count") boolean count, + @ApiParam(value="Policy Controller Name", required=true) + @PathParam("controller") String controllerName, + @ApiParam(value="Drools Session Name", required=true) + @PathParam("session") String sessionName, + @ApiParam(value="Query Name Present in DRL", required=true) + @PathParam("query") String queryName, + @ApiParam(value="Query Identifier Present in the DRL Query", required=true) + @PathParam("queriedEntity") String queriedEntity) { + try { + DroolsController drools = getDroolsController(controllerName); + List<Object> facts = drools.factQuery(sessionName, queryName, queriedEntity, false); + if (!count) + return Response.status(Response.Status.OK).entity(facts).build(); + else + return Response.status(Response.Status.OK).entity(facts.size()).build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.NOT_FOUND). + entity(new Error(controllerName + ":" + sessionName + ":" + queryName + + queriedEntity + " not found")). + build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.NOT_ACCEPTABLE). + entity(new Error(controllerName + ":" + sessionName + ":" + queryName + + queriedEntity + " not acceptable")). + build(); + } catch (Exception e) { + return Response.status(Response.Status.INTERNAL_SERVER_ERROR). + entity(new Error(e.getMessage())). + build(); + } + } + + @POST + @Path("engine/controllers/{controller}/drools/facts/{session}/{query}/{queriedEntity}") + @ApiOperation( + value="Gets all the fact objects returned by a DRL query with parameters from the drools working memory" + + "for a given controller and session", + notes="The DRL query with parameters must be defined in the DRL file", + responseContainer="List" + ) + @ApiResponses(value = { + @ApiResponse(code=404, message="The controller, session, or query information, cannot be found"), + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled"), + @ApiResponse(code=500, message="A server error has occurred processing this request") + }) + public Response droolsFacts(@ApiParam(value="Policy Controller Name", required=true) + @PathParam("controller") String controllerName, + @ApiParam(value="Drools Session Name", required=true) + @PathParam("session") String sessionName, + @ApiParam(value="Query Name Present in DRL", required=true) + @PathParam("query") String queryName, + @ApiParam(value="Query Identifier Present in the DRL Query", required=true) + @PathParam("queriedEntity") String queriedEntity, + @ApiParam(value="Query Parameter Values to pass in the DRL Query", required=false) + List<Object> queryParameters) { + try { + DroolsController drools = getDroolsController(controllerName); + List<Object> facts; + if (queryParameters == null || queryParameters.isEmpty()) + facts = drools.factQuery(sessionName, queryName, queriedEntity, false); + else + facts = drools.factQuery(sessionName, queryName, queriedEntity, false, queryParameters.toArray()); + return Response.status(Response.Status.OK).entity(facts).build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.NOT_FOUND). + entity(new Error(controllerName + ":" + sessionName + ":" + queryName + + queriedEntity + " not found")). + build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.NOT_ACCEPTABLE). + entity(new Error(controllerName + ":" + sessionName + ":" + queryName + + queriedEntity + " not acceptable")). + build(); + } catch (Exception e) { + return Response.status(Response.Status.INTERNAL_SERVER_ERROR). + entity(new Error(e.getMessage())). + build(); + } + } + + @DELETE + @Path("engine/controllers/{controller}/drools/facts/{session}/{query}/{queriedEntity}") + @ApiOperation( + value="Deletes all the fact objects returned by a DRL query with parameters from the drools working memory" + + "for a given controller and session", + notes="The DRL query with parameters must be defined in the DRL file", + responseContainer="List" + ) + @ApiResponses(value = { + @ApiResponse(code=404, message="The controller, session, or query information, cannot be found"), + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled"), + @ApiResponse(code=500, message="A server error has occurred processing this request") + }) + public Response droolsFactsDelete(@ApiParam(value="Policy Controller Name", required=true) + @PathParam("controller") String controllerName, + @ApiParam(value="Drools Session Name", required=true) + @PathParam("session") String sessionName, + @ApiParam(value="Query Name Present in DRL", required=true) + @PathParam("query") String queryName, + @ApiParam(value="Query Identifier Present in the DRL Query", required=true) + @PathParam("queriedEntity") String queriedEntity, + @ApiParam(value="Query Parameter Values to pass in the DRL Query", required=false) + List<Object> queryParameters) { + try { + DroolsController drools = getDroolsController(controllerName); + List<Object> facts; + if (queryParameters == null || queryParameters.isEmpty()) + facts = drools.factQuery(sessionName, queryName, queriedEntity, true); + else + facts = drools.factQuery(sessionName, queryName, queriedEntity, true, queryParameters.toArray()); + return Response.status(Response.Status.OK).entity(facts).build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.NOT_FOUND). + entity(new Error(controllerName + ":" + sessionName + ":" + queryName + + queriedEntity + " not found")). + build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.NOT_ACCEPTABLE). + entity(new Error(controllerName + ":" + sessionName + ":" + queryName + + queriedEntity + " not acceptable")). + build(); + } catch (Exception e) { + return Response.status(Response.Status.INTERNAL_SERVER_ERROR). + entity(new Error(e.getMessage())). + build(); + } + } + + @POST + @Path("engine/controllers/tools/coders/decoders/filters/rules/{ruleName}") + @ApiOperation( + value="Produces a Decoder Rule Filter in a format that the Policy Controller can understand", + notes="The result can be used with other APIs to attach a filter to a decoder" + ) + public Response rules(@ApiParam(value="Negate regex?", required=true) + @DefaultValue("false") @QueryParam("negate") boolean negate, + @ApiParam(value="Rule Name", required=true) + @PathParam("ruleName") String name, + @ApiParam(value="Regex expression", required=true) + String regex) { + String literalRegex = Pattern.quote(regex); + if (negate) + literalRegex = "^(?!" + literalRegex + "$).*"; + + return Response.status(Status.OK). + entity(new JsonProtocolFilter.FilterRule(name,literalRegex)). + build(); + } + + @GET + @Path("engine/controllers/{controller}/decoders") + @ApiOperation( + value="Gets all the decoders used by a controller", + notes="A Policy Controller uses decoders to deserialize incoming network messages from " + + "subscribed network topics into specific (fact) objects. " + + "The deserialized (fact) object will typically be inserted in the drools working " + + " memory of the controlled drools application.", + responseContainer="List", + response=ProtocolCoderToolset.class + ) + @ApiResponses(value = { + @ApiResponse(code=404, message="The controller cannot be found"), + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled") + }) + public Response decoders(@ApiParam(value="Policy Controller Name", required=true) + @PathParam("controller") String controllerName) { + try { + DroolsController drools = getDroolsController(controllerName); + List<ProtocolCoderToolset> decoders = EventProtocolCoder.manager.getDecoders + (drools.getGroupId(), drools.getArtifactId()); + return Response.status(Response.Status.OK). + entity(decoders). + build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.NOT_FOUND). + entity(new Error(controllerName + " not found")). + build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.NOT_ACCEPTABLE). + entity(new Error(controllerName + " not acceptable")). + build(); + } + } + + @GET + @Path("engine/controllers/{controller}/decoders/filters") + @ApiOperation( + value="Gets all the filters used by a controller", + notes="A Policy Controller uses decoders to deserialize incoming network messages from " + + "subscribed network topics into specific (fact) objects. " + + "The deserialized (fact) object will typically be inserted in the drools working " + + " memory of the controlled drools application." + + "Acceptance filters are used to filter out undesired network messages for the given controller", + responseContainer="List", + response=CoderFilters.class + ) + @ApiResponses(value = { + @ApiResponse(code=404, message="The controller cannot be found"), + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled") + }) + public Response decoderFilters(@ApiParam(value="Policy Controller Name", required=true) + @PathParam("controller") String controllerName) { + try { + DroolsController drools = getDroolsController(controllerName); + List<CoderFilters> filters = EventProtocolCoder.manager.getDecoderFilters + (drools.getGroupId(), drools.getArtifactId()); + return Response.status(Response.Status.OK). + entity(filters). + build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.NOT_FOUND). + entity(new Error(controllerName + " not found")). + build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.NOT_ACCEPTABLE). + entity(new Error(controllerName + " not acceptable")). + build(); + } + } + + @GET + @Path("engine/controllers/{controller}/decoders/{topic}") + @ApiOperation( + value="Gets all the decoders in use by a controller for a networked topic", + notes="A Policy Controller uses decoders to deserialize incoming network messages from " + + "subscribed network topics into specific (fact) objects. " + + "The deserialized (fact) object will typically be inserted in the drools working " + + " memory of the controlled drools application.", + responseContainer="List", + response=ProtocolCoderToolset.class + ) + @ApiResponses(value = { + @ApiResponse(code=404, message="The controller or topic cannot be found"), + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled") + }) + public Response decoder(@ApiParam(value="Policy Controller Name", required=true) + @PathParam("controller") String controllerName, + @ApiParam(value="Networked Topic Name", required=true) + @PathParam("topic") String topic) { + try { + DroolsController drools = getDroolsController(controllerName); + ProtocolCoderToolset decoder = EventProtocolCoder.manager.getDecoders + (drools.getGroupId(), drools.getArtifactId(), topic); + return Response.status(Response.Status.OK). + entity(decoder). + build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.NOT_FOUND). + entity(new Error(controllerName + ":" + topic + " not found")). + build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.NOT_ACCEPTABLE). + entity(new Error(controllerName + ":" + topic + " not acceptable")). + build(); + } + } + + @GET + @Path("engine/controllers/{controller}/decoders/{topic}/filters") + @ApiOperation( + value="Gets all filters attached to decoders for a given networked topic in use by a controller", + notes="A Policy Controller uses decoders to deserialize incoming network messages from " + + "subscribed network topics into specific (fact) objects. " + + "The deserialized (fact) object will typically be inserted in the drools working " + + " memory of the controlled drools application." + + "Acceptance filters are used to filter out undesired network messages for the given controller", + responseContainer="List", + response=CoderFilters.class + ) + @ApiResponses(value = { + @ApiResponse(code=404, message="The controller or topic cannot be found"), + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled") + }) + public Response decoderFilter(@ApiParam(value="Policy Controller Name", required=true) + @PathParam("controller") String controllerName, + @ApiParam(value="Networked Topic Name", required=true) + @PathParam("topic") String topic) { + try { + DroolsController drools = getDroolsController(controllerName); + ProtocolCoderToolset decoder = EventProtocolCoder.manager.getDecoders + (drools.getGroupId(), drools.getArtifactId(), topic); + if (decoder == null) + return Response.status(Response.Status.BAD_REQUEST). + entity(new Error(topic + " does not exist")). + build(); + else + return Response.status(Response.Status.OK). + entity(decoder.getCoders()). + build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.NOT_FOUND). + entity(new Error(controllerName + ":" + topic + " not found")). + build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.NOT_ACCEPTABLE). + entity(new Error(controllerName + ":" + topic + " not acceptable")). + build(); + } + } + + @GET + @Path("engine/controllers/{controller}/decoders/{topic}/filters/{factType}") + @ApiOperation( + value="Gets all filters attached to decoders for a given subscribed networked topic " + + "and fact type", + notes="Decoders are associated with networked topics. A Policy Controller manages " + + "multiple topics and therefore its attached decoders. " + + "A Policy Controller uses filters to further specify the fact mapping. " + + "Filters are applied on a per fact type (classname).", + responseContainer="List", + response=CoderFilters.class + ) + @ApiResponses(value = { + @ApiResponse(code=404, message="The controller, topic, or fact type cannot be found"), + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled") + }) + public Response decoderFilter(@ApiParam(value="Policy Controller Name", required=true) + @PathParam("controller") String controllerName, + @ApiParam(value="Networked Topic Name", required=true) + @PathParam("topic") String topic, + @ApiParam(value="Fact Type", required=true) + @PathParam("factType") String factClass) { + try { + DroolsController drools = getDroolsController(controllerName); + ProtocolCoderToolset decoder = EventProtocolCoder.manager.getDecoders + (drools.getGroupId(), drools.getArtifactId(), topic); + CoderFilters filters = decoder.getCoder(factClass); + if (filters == null) + return Response.status(Response.Status.BAD_REQUEST). + entity(new Error(topic + ":" + factClass + " does not exist")). + build(); + else + return Response.status(Response.Status.OK). + entity(filters). + build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.NOT_FOUND). + entity(new Error(controllerName + ":" + topic + ":" + + factClass + " not found")). + build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.NOT_ACCEPTABLE). + entity(new Error(controllerName + ":" + topic + ":" + + factClass + " not acceptable")). + build(); + } + } + + @PUT + @Path("engine/controllers/{controller}/decoders/{topic}/filters/{factType}") + @ApiOperation( + value="Attaches filters to the decoder for a given networked topic " + + "and fact type", + notes="Decoders are associated with networked topics. A Policy Controller manages " + + "multiple topics and therefore its attached decoders. " + + "A Policy Controller uses filters to further specify the fact mapping. " + + "Filters are applied on a per fact type (classname).", + responseContainer="List", + response=CoderFilters.class + ) + @ApiResponses(value = { + @ApiResponse(code=404, message="The controller, topic, fact type, cannot be found, " + + "or a filter has not been provided"), + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled") + }) + public Response decoderFilter(@ApiParam(value="Policy Controller Name", required=true) + @PathParam("controller") String controllerName, + @ApiParam(value="Topic Name", required=true) + @PathParam("topic") String topic, + @ApiParam(value="Fact Type", required=true) + @PathParam("factType") String factClass, + @ApiParam(value="Configuration Filter", required=true) + JsonProtocolFilter configFilters) { + + if (configFilters == null) { + return Response.status(Response.Status.BAD_REQUEST). + entity(new Error("Configuration Filters not provided")). + build(); + } + + try { + DroolsController drools = getDroolsController(controllerName); + ProtocolCoderToolset decoder = EventProtocolCoder.manager.getDecoders + (drools.getGroupId(), drools.getArtifactId(), topic); + CoderFilters filters = decoder.getCoder(factClass); + if (filters == null) + return Response.status(Response.Status.BAD_REQUEST). + entity(new Error(topic + ":" + factClass + " does not exist")). + build(); + filters.setFilter(configFilters); + return Response.status(Response.Status.OK). + entity(filters). + build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.NOT_FOUND). + entity(new Error(controllerName + ":" + topic + ":" + + factClass + " not found")). + build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.NOT_ACCEPTABLE). + entity(new Error(controllerName + ":" + topic + ":" + + factClass + " not acceptable")). + build(); + } + } + + @GET + @Path("engine/controllers/{controller}/decoders/{topic}/filters/{factType}/rules") + @ApiOperation( + value="Gets the filter rules attached to a topic decoder of a controller", + notes="Decoders are associated with networked topics. A Policy Controller manages " + + "multiple topics and therefore its attached decoders. " + + "A Policy Controller uses filters to further specify the fact mapping. " + + "Filters are applied on a per fact type and are composed of field matching rules. ", + responseContainer="List", + response=FilterRule.class + ) + @ApiResponses(value = { + @ApiResponse(code=404, message="The controller, topic, or fact type cannot be found"), + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled") + }) + public Response decoderFilterRules(@ApiParam(value="Policy Controller Name", required=true) + @PathParam("controller") String controllerName, + @ApiParam(value="Topic Name", required=true) + @PathParam("topic") String topic, + @ApiParam(value="Fact Type", required=true) + @PathParam("factType") String factClass) { + try { + DroolsController drools = getDroolsController(controllerName); + ProtocolCoderToolset decoder = EventProtocolCoder.manager.getDecoders + (drools.getGroupId(), drools.getArtifactId(), topic); + + CoderFilters filters = decoder.getCoder(factClass); + if (filters == null) + return Response.status(Response.Status.BAD_REQUEST). + entity(new Error(controllerName + ":" + topic + ":" + factClass + " does not exist")). + build(); + + JsonProtocolFilter filter = filters.getFilter(); + if (filter == null) + return Response.status(Response.Status.BAD_REQUEST). + entity(new Error(controllerName + ":" + topic + ":" + factClass + " no filters")). + build(); + + return Response.status(Response.Status.OK). + entity(filter.getRules()). + build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.NOT_FOUND). + entity(new Error(controllerName + ":" + topic + ":" + + factClass + " not found")). + build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.NOT_ACCEPTABLE). + entity(new Error(controllerName + ":" + topic + ":" + + factClass + " not acceptable")). + build(); + } + } + + @GET + @Path("engine/controllers/{controller}/decoders/{topic}/filters/{factType}/rules/{ruleName}") + @ApiOperation( + value="Gets a filter rule by name attached to a topic decoder of a controller", + notes="Decoders are associated with networked topics. A Policy Controller manages " + + "multiple topics and therefore its attached decoders. " + + "A Policy Controller uses filters to further specify the fact mapping. " + + "Filters are applied on a per fact type and are composed of field matching rules. ", + responseContainer="List", + response=FilterRule.class + ) + @ApiResponses(value = { + @ApiResponse(code=404, message="The controller, topic, fact type, or rule name cannot be found"), + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled") + }) + public Response decoderFilterRules(@ApiParam(value="Policy Controller Name", required=true) + @PathParam("controller") String controllerName, + @ApiParam(value="Topic Name", required=true) + @PathParam("topic") String topic, + @ApiParam(value="Fact Type", required=true) + @PathParam("factType") String factClass, + @ApiParam(value="Rule Name", required=true) + @PathParam("ruleName") String ruleName) { + try { + DroolsController drools = getDroolsController(controllerName); + ProtocolCoderToolset decoder = EventProtocolCoder.manager.getDecoders + (drools.getGroupId(), drools.getArtifactId(), topic); + + CoderFilters filters = decoder.getCoder(factClass); + if (filters == null) + return Response.status(Response.Status.BAD_REQUEST). + entity(new Error(controllerName + ":" + topic + ":" + factClass + " does not exist")). + build(); + + JsonProtocolFilter filter = filters.getFilter(); + if (filter == null) + return Response.status(Response.Status.BAD_REQUEST). + entity(new Error(controllerName + ":" + topic + ":" + factClass + " no filters")). + build(); + + return Response.status(Response.Status.OK). + entity(filter.getRules(ruleName)). + build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.NOT_FOUND). + entity(new Error(controllerName + ":" + topic + ":" + + factClass + ": " + ruleName + " not found")). + build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.NOT_ACCEPTABLE). + entity(new Error(controllerName + ":" + topic + ":" + + factClass + ":" + ruleName + " not acceptable")). + build(); + } + } + + @DELETE + @Path("engine/controllers/{controller}/decoders/{topic}/filters/{factType}/rules/{ruleName}") + @ApiOperation( + value="Deletes a filter rule by name attached to a topic decoder of a controller", + notes="Decoders are associated with networked topics. A Policy Controller manages " + + "multiple topics and therefore its attached decoders. " + + "A Policy Controller uses filters to further specify the fact mapping. " + + "Filters are applied on a per fact type and are composed of field matching rules. ", + responseContainer="List", + response=FilterRule.class + ) + @ApiResponses(value = { + @ApiResponse(code=404, message="The controller, topic, fact type, or rule name cannot be found"), + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled") + }) + public Response decoderFilterRuleDelete(@ApiParam(value="Policy Controller Name", required=true) + @PathParam("controller") String controllerName, + @ApiParam(value="Topic Name", required=true) + @PathParam("topic") String topic, + @ApiParam(value="Fact Type", required=true) + @PathParam("factType") String factClass, + @ApiParam(value="Rule Name", required=true) + @PathParam("ruleName") String ruleName, + @ApiParam(value="Filter Rule", required=true) + FilterRule rule) { + + try { + DroolsController drools = getDroolsController(controllerName); + ProtocolCoderToolset decoder = EventProtocolCoder.manager.getDecoders + (drools.getGroupId(), drools.getArtifactId(), topic); + + CoderFilters filters = decoder.getCoder(factClass); + if (filters == null) + return Response.status(Response.Status.BAD_REQUEST). + entity(new Error(controllerName + ":" + topic + ":" + factClass + " does not exist")). + build(); + + JsonProtocolFilter filter = filters.getFilter(); + if (filter == null) + return Response.status(Response.Status.BAD_REQUEST). + entity(new Error(controllerName + ":" + topic + ":" + factClass + " no filters")). + build(); + + if (rule == null) { + filter.deleteRules(ruleName); + return Response.status(Response.Status.OK). + entity(filter.getRules()). + build(); + } + + if (rule.getName() == null || !rule.getName().equals(ruleName)) + return Response.status(Response.Status.BAD_REQUEST). + entity(new Error(controllerName + ":" + topic + ":" + factClass + ":" + ruleName + + " rule name request inconsistencies (" + rule.getName() + ")")). + build(); + + filter.deleteRule(ruleName, rule.getRegex()); + return Response.status(Response.Status.OK). + entity(filter.getRules()). + build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.NOT_FOUND). + entity(new Error(controllerName + ":" + topic + ":" + + factClass + ": " + ruleName + " not found")). + build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.NOT_ACCEPTABLE). + entity(new Error(controllerName + ":" + topic + ":" + + factClass + ":" + ruleName + " not acceptable")). + build(); + } + } + + @PUT + @Path("engine/controllers/{controller}/decoders/{topic}/filters/{factType}/rules") + @ApiOperation( + value="Places a new filter rule in a topic decoder", + notes="Decoders are associated with networked topics. A Policy Controller manages " + + "multiple topics and therefore its attached decoders. " + + "A Policy Controller uses filters to further specify the fact mapping. " + + "Filters are applied on a per fact type and are composed of field matching rules. ", + responseContainer="List", + response=FilterRule.class + ) + @ApiResponses(value = { + @ApiResponse(code=404, message="The controller, topic, or fact type cannot be found"), + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled") + }) + public Response decoderFilterRule(@ApiParam(value="Policy Controller Name", required=true) + @PathParam("controller") String controllerName, + @ApiParam(value="Topic Name", required=true) + @PathParam("topic") String topic, + @ApiParam(value="Fact Type", required=true) + @PathParam("factType") String factClass, + @ApiParam(value="Rule Name", required=true) + @PathParam("ruleName") String ruleName, + @ApiParam(value="Filter Rule", required=true) + FilterRule rule) { + + try { + DroolsController drools = getDroolsController(controllerName); + ProtocolCoderToolset decoder = EventProtocolCoder.manager.getDecoders + (drools.getGroupId(), drools.getArtifactId(), topic); + + CoderFilters filters = decoder.getCoder(factClass); + if (filters == null) + return Response.status(Response.Status.BAD_REQUEST). + entity(new Error(controllerName + ":" + topic + ":" + factClass + " does not exist")). + build(); + + JsonProtocolFilter filter = filters.getFilter(); + if (filter == null) + return Response.status(Response.Status.BAD_REQUEST). + entity(new Error(controllerName + ":" + topic + ":" + factClass + " no filters")). + build(); + + if (rule.getName() == null) + return Response.status(Response.Status.BAD_REQUEST). + entity(new Error(controllerName + ":" + topic + ":" + factClass + + " rule name request inconsistencies (" + rule.getName() + ")")). + build(); + + filter.addRule(rule.getName(), rule.getRegex()); + return Response.status(Response.Status.OK). + entity(filter.getRules()). + build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.NOT_FOUND). + entity(new Error(controllerName + ":" + topic + ":" + + factClass + " not found")). + build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.NOT_ACCEPTABLE). + entity(new Error(controllerName + ":" + topic + ":" + + factClass + " not acceptable")). + build(); + } + } + + @POST + @Path("engine/controllers/{controller}/decoders/{topic}") + @Consumes(MediaType.TEXT_PLAIN) + @ApiOperation( + value="Decodes a string into a fact object, and encodes it back into a string", + notes="Tests the decode/encode functions of a controller", + response=CodingResult.class + ) + @ApiResponses(value = { + @ApiResponse(code=400, message="Bad input has been provided"), + @ApiResponse(code=404, message="The controller cannot be found"), + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled") + }) + public Response decode(@ApiParam(value="Policy Controller Name", required=true) + @PathParam("controller") String controllerName, + @ApiParam(value="Topic Name", required=true) + @PathParam("topic") String topic, + @ApiParam(value="JSON String to decode", required=true) + String json) { + + PolicyController policyController; + try { + policyController = PolicyController.factory.get(controllerName); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.NOT_FOUND). + entity(new Error(controllerName + ":" + topic + ":" + + " not found")). + build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.NOT_ACCEPTABLE). + entity(new Error(controllerName + ":" + topic + ":" + + " not acceptable")). + build(); + } + + CodingResult result = new CodingResult(); + result.decoding = false; + result.encoding = false; + result.jsonEncoding = null; + + Object event; + try { + event = EventProtocolCoder.manager.decode + (policyController.getDrools().getGroupId(), + policyController.getDrools().getArtifactId(), + topic, + json); + result.decoding = true; + } catch (Exception e) { + return Response.status(Response.Status.BAD_REQUEST). + entity(new Error(e.getMessage())). + build(); + } + + try { + result.jsonEncoding = EventProtocolCoder.manager.encode(topic, event); + result.encoding = true; + } catch (Exception e) { + // continue so to propagate decoding results .. + } + + return Response.status(Response.Status.OK). + entity(result). + build(); + } + + @GET + @Path("engine/controllers/{controller}/encoders") + @ApiOperation( + value="Retrieves the encoder filters of a controller", + notes="The encoders serializes a fact object, typically for network transmission", + responseContainer="List", + response=CoderFilters.class + ) + @ApiResponses(value = { + @ApiResponse(code=400, message="Bad input has been provided"), + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled") + }) + public Response encoderFilters(@ApiParam(value="Policy Controller Name", required=true) + @PathParam("controller") String controllerName) { + List<CoderFilters> encoders; + try { + PolicyController controller = PolicyController.factory.get(controllerName); + DroolsController drools = controller.getDrools(); + encoders = EventProtocolCoder.manager.getEncoderFilters + (drools.getGroupId(), drools.getArtifactId()); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.BAD_REQUEST). + entity(new Error(controllerName + " not found: " + e.getMessage())). + build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.NOT_ACCEPTABLE). + entity(new Error(controllerName + " is not accepting the request")).build(); + } + + return Response.status(Response.Status.OK). + entity(encoders). + build(); + } + + @GET + @Path("engine/topics") + @ApiOperation( + value="Retrieves the managed topics", + notes="Network Topics Aggregation", + response=TopicEndpoint.class + ) + public Response topics() { + return Response.status(Response.Status.OK). + entity(TopicEndpoint.manager). + build(); + } + + @GET + @Path("engine/topics/switches") + @ApiOperation( + value="Topics Control Switches", + notes="List of the Topic Control Switches", + responseContainer="List" + ) + public Response topicSwitches() { + return Response.status(Response.Status.OK). + entity(Arrays.asList(Switches.values())). + build(); + } + + @PUT + @Path("engine/topics/switches/lock") + @ApiOperation( + value="Locks all the managed topics", + notes="The operation affects all managed sources and sinks", + response=TopicEndpoint.class + ) + @ApiResponses(value = { + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled") + }) + public Response topicsLock() { + boolean success = TopicEndpoint.manager.lock(); + if (success) + return Response.status(Status.OK). + entity(TopicEndpoint.manager). + build(); + else + return Response.status(Status.NOT_ACCEPTABLE). + entity(new Error("cannot perform operation")). + build(); + } + + @DELETE + @Path("engine/topics/switches/lock") + @ApiOperation( + value="Unlocks all the managed topics", + notes="The operation affects all managed sources and sinks", + response=TopicEndpoint.class + ) + @ApiResponses(value = { + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled") + }) + public Response topicsUnlock() { + boolean success = TopicEndpoint.manager.unlock(); + if (success) + return Response.status(Status.OK). + entity(TopicEndpoint.manager). + build(); + else + return Response.status(Status.NOT_ACCEPTABLE). + entity(new Error("cannot perform operation")). + build(); + } + + @GET + @Path("engine/topics/sources") + @ApiOperation( + value="Retrieves the managed topic sources", + notes="Network Topic Sources Agregation", + responseContainer="List", + response=TopicSource.class + ) + public Response sources() { + return Response.status(Response.Status.OK). + entity(TopicEndpoint.manager.getTopicSources()). + build(); + } + + @GET + @Path("engine/topics/sinks") + @ApiOperation( + value="Retrieves the managed topic sinks", + notes="Network Topic Sinks Agregation", + responseContainer="List", + response=TopicSink.class + ) + public Response sinks() { + return Response.status(Response.Status.OK). + entity(TopicEndpoint.manager.getTopicSinks()). + build(); + } + + @GET + @Path("engine/topics/sources/ueb") + @ApiOperation( + value="Retrieves the UEB managed topic sources", + notes="UEB Topic Sources Agregation", + responseContainer="List", + response=UebTopicSource.class + ) + public Response uebSources() { + return Response.status(Response.Status.OK). + entity(TopicEndpoint.manager.getUebTopicSources()). + build(); + } + + @GET + @Path("engine/topics/sinks/ueb") + @ApiOperation( + value="Retrieves the UEB managed topic sinks", + notes="UEB Topic Sinks Agregation", + responseContainer="List", + response=UebTopicSource.class + ) + public Response uebSinks() { + return Response.status(Response.Status.OK). + entity(TopicEndpoint.manager.getUebTopicSinks()). + build(); + } + + @GET + @Path("engine/topics/sources/dmaap") + @ApiOperation( + value="Retrieves the DMaaP managed topic sources", + notes="DMaaP Topic Sources Agregation", + responseContainer="List", + response=DmaapTopicSource.class + ) + public Response dmaapSources() { + return Response.status(Response.Status.OK). + entity(TopicEndpoint.manager.getDmaapTopicSources()). + build(); + } + + @GET + @Path("engine/topics/sinks/dmaap") + @ApiOperation( + value="Retrieves the DMaaP managed topic sinks", + notes="DMaaP Topic Sinks Agregation", + responseContainer="List", + response=DmaapTopicSink.class + ) + public Response dmaapSinks() { + return Response.status(Response.Status.OK). + entity(TopicEndpoint.manager.getDmaapTopicSinks()). + build(); + } + + @GET + @Path("engine/topics/sources/ueb/{topic}") + @ApiOperation( + value="Retrieves an UEB managed topic source", + notes="This is an UEB Network Communicaton Endpoint source of messages for the Engine", + response=UebTopicSource.class + ) + public Response uebSourceTopic(@ApiParam(value="Topic Name", required=true) + @PathParam("topic") String topic) { + return Response.status(Response.Status.OK). + entity(TopicEndpoint.manager.getUebTopicSource(topic)). + build(); + } + + @GET + @Path("engine/topics/sinks/ueb/{topic}") + @ApiOperation( + value="Retrieves an UEB managed topic sink", + notes="This is an UEB Network Communicaton Endpoint destination of messages from the Engine", + response=UebTopicSink.class + ) + public Response uebSinkTopic(@ApiParam(value="Topic Name", required=true) + @PathParam("topic") String topic) { + return Response.status(Response.Status.OK). + entity(TopicEndpoint.manager.getUebTopicSink(topic)). + build(); + } + + @GET + @Path("engine/topics/sources/dmaap/{topic}") + @ApiOperation( + value="Retrieves a DMaaP managed topic source", + notes="This is a DMaaP Network Communicaton Endpoint source of messages for the Engine", + response=DmaapTopicSource.class + ) + public Response dmaapSourceTopic(@ApiParam(value="Topic Name", required=true) + @PathParam("topic") String topic) { + return Response.status(Response.Status.OK). + entity(TopicEndpoint.manager.getDmaapTopicSource(topic)). + build(); + } + + @GET + @Path("engine/topics/sinks/dmaap/{topic}") + @ApiOperation( + value="Retrieves a DMaaP managed topic sink", + notes="This is a DMaaP Network Communicaton Endpoint destination of messages from the Engine", + response=DmaapTopicSink.class + ) + public Response dmaapSinkTopic(@ApiParam(value="Topic Name", required=true) + @PathParam("topic") String topic) { + return Response.status(Response.Status.OK). + entity(TopicEndpoint.manager.getDmaapTopicSink(topic)). + build(); + } + + @GET + @Path("engine/topics/sources/ueb/{topic}/events") + @ApiOperation( + value="Retrieves the latest events received by an UEB topic", + notes="This is a UEB Network Communicaton Endpoint source of messages for the Engine", + responseContainer="List" + ) + public Response uebSourceEvents(@ApiParam(value="Topic Name", required=true) + @PathParam("topic") String topic) { + return Response.status(Status.OK). + entity(Arrays.asList(TopicEndpoint.manager.getUebTopicSource(topic).getRecentEvents())). + build(); + } + + @GET + @Path("engine/topics/sinks/ueb/{topic}/events") + @ApiOperation( + value="Retrieves the latest events sent from a topic", + notes="This is a UEB Network Communicaton Endpoint sink of messages from the Engine", + responseContainer="List" + ) + public Response uebSinkEvents(@ApiParam(value="Topic Name", required=true) + @PathParam("topic") String topic) { + return Response.status(Status.OK). + entity(Arrays.asList(TopicEndpoint.manager.getUebTopicSink(topic).getRecentEvents())). + build(); + } + + @GET + @Path("engine/topics/sources/dmaap/{topic}/events") + @ApiOperation( + value="Retrieves the latest events received by a DMaaP topic", + notes="This is a DMaaP Network Communicaton Endpoint source of messages for the Engine", + responseContainer="List" + ) + public Response dmaapSourceEvents(@ApiParam(value="Topic Name", required=true) + @PathParam("topic") String topic) { + return Response.status(Status.OK). + entity(Arrays.asList(TopicEndpoint.manager.getDmaapTopicSource(topic).getRecentEvents())). + build(); + } + + @GET + @Path("engine/topics/sinks/dmaap/{topic}/events") + @ApiOperation( + value="Retrieves the latest events send through a DMaaP topic", + notes="This is a DMaaP Network Communicaton Endpoint destination of messages from the Engine", + responseContainer="List" + ) + public Response dmaapSinkEvents( + @PathParam("topic") String topic) { + return Response.status(Status.OK). + entity(Arrays.asList(TopicEndpoint.manager.getDmaapTopicSink(topic).getRecentEvents())). + build(); + } + + @GET + @Path("engine/topics/sources/ueb/{topic}/switches") + @ApiOperation( + value="UEB Topic Control Switches", + notes="List of the UEB Topic Control Switches", + responseContainer="List" + ) + public Response uebTopicSwitches() { + return Response.status(Response.Status.OK). + entity(Arrays.asList(Switches.values())). + build(); + } + + @PUT + @Path("engine/topics/sources/ueb/{topic}/switches/lock") + @ApiOperation( + value="Locks an UEB Source topic", + response=UebTopicSource.class + ) + @ApiResponses(value = { + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled") + }) + public Response uebTopicLock(@ApiParam(value="Topic Name", required=true) + @PathParam("topic") String topic) { + UebTopicSource source = TopicEndpoint.manager.getUebTopicSource(topic); + boolean success = source.lock(); + if (success) + return Response.status(Status.OK). + entity(source). + build(); + else + return Response.status(Status.NOT_ACCEPTABLE). + entity(new Error("cannot perform operation on " + topic)). + build(); + } + + @DELETE + @Path("engine/topics/sources/ueb/{topic}/switches/lock") + @ApiOperation( + value="Unlocks an UEB Source topic", + response=UebTopicSource.class + ) + @ApiResponses(value = { + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled") + }) + public Response uebTopicUnlock(@ApiParam(value="Topic Name", required=true) + @PathParam("topic") String topic) { + UebTopicSource source = TopicEndpoint.manager.getUebTopicSource(topic); + boolean success = source.unlock(); + if (success) + return Response.status(Status.OK). + entity(source). + build(); + else + return Response.status(Status.NOT_ACCEPTABLE). + entity(new Error("cannot perform operation on " + topic)). + build(); + } + + @GET + @Path("engine/topics/sources/dmaap/{topic}/switches") + @ApiOperation( + value="DMaaP Topic Control Switches", + notes="List of the DMaaP Topic Control Switches", + responseContainer="List" + ) + public Response dmaapTopicSwitches() { + return Response.status(Response.Status.OK). + entity(Arrays.asList(Switches.values())). + build(); + } + + @PUT + @Path("engine/topics/sources/dmaap/{topic}/switches/lock") + @ApiOperation( + value="Locks an DMaaP Source topic", + response=DmaapTopicSource.class + ) + @ApiResponses(value = { + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled") + }) + public Response dmmapTopicLock(@ApiParam(value="Topic Name", required=true) + @PathParam("topic") String topic) { + DmaapTopicSource source = TopicEndpoint.manager.getDmaapTopicSource(topic); + boolean success = source.lock(); + if (success) + return Response.status(Status.OK). + entity(source). + build(); + else + return Response.status(Status.NOT_ACCEPTABLE). + entity(new Error("cannot perform operation on " + topic)). + build(); + } + + @DELETE + @Path("engine/topics/sources/dmaap/{topic}/switches/lock") + @ApiOperation( + value="Unlocks an DMaaP Source topic", + response=DmaapTopicSource.class + ) + @ApiResponses(value = { + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled") + }) + public Response dmaapTopicUnlock(@ApiParam(value="Topic Name", required=true) + @PathParam("topic") String topic) { + DmaapTopicSource source = TopicEndpoint.manager.getDmaapTopicSource(topic); + boolean success = source.unlock(); + if (success) + return Response.status(Status.OK). + entity(source). + build(); + else + return Response.status(Status.SERVICE_UNAVAILABLE). + entity(new Error("cannot perform operation on " + topic)). + build(); + } + + @PUT + @Path("engine/topics/sources/ueb/{topic}/events") + @Consumes(MediaType.TEXT_PLAIN) + @ApiOperation( + value="Offers an event to an UEB topic for internal processing by the engine", + notes="The offered event is treated as it was incoming from the network", + responseContainer="List" + ) + @ApiResponses(value = { + @ApiResponse(code=404, message="The topic information cannot be found"), + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled"), + @ApiResponse(code=500, message="A server error has occurred processing this request") + }) + public Response uebOffer(@ApiParam(value="Topic Name", required=true) + @PathParam("topic") String topic, + @ApiParam(value="Network Message", required=true) + String json) { + try { + UebTopicSource uebReader = TopicEndpoint.manager.getUebTopicSource(topic); + boolean success = uebReader.offer(json); + if (success) + return Response.status(Status.OK). + entity(Arrays.asList(TopicEndpoint.manager.getUebTopicSource(topic).getRecentEvents())). + build(); + else + return Response.status(Status.NOT_ACCEPTABLE). + entity(new Error("Failure to inject event over " + topic)). + build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.NOT_FOUND). + entity(new Error(topic + " not found")). + build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.NOT_ACCEPTABLE). + entity(new Error(topic + " not acceptable due to current state")). + build(); + } catch (Exception e) { + return Response.status(Response.Status.INTERNAL_SERVER_ERROR). + entity(new Error(e.getMessage())). + build(); + } + } + + @PUT + @Path("engine/topics/sources/dmaap/{topic}/events") + @Consumes(MediaType.TEXT_PLAIN) + @ApiOperation( + value="Offers an event to a DMaaP topic for internal processing by the engine", + notes="The offered event is treated as it was incoming from the network", + responseContainer="List" + ) + @ApiResponses(value = { + @ApiResponse(code=404, message="The topic information cannot be found"), + @ApiResponse(code=406, message="The system is an administrative state that prevents " + + "this request to be fulfilled"), + @ApiResponse(code=500, message="A server error has occurred processing this request") + }) + public Response dmaapOffer(@ApiParam(value="Topic Name", required=true) + @PathParam("topic") String topic, + @ApiParam(value="Network Message", required=true) + String json) { + try { + DmaapTopicSource dmaapReader = TopicEndpoint.manager.getDmaapTopicSource(topic); + boolean success = dmaapReader.offer(json); + if (success) + return Response.status(Status.OK). + entity(Arrays.asList(TopicEndpoint.manager.getDmaapTopicSource(topic).getRecentEvents())). + build(); + else + return Response.status(Status.NOT_ACCEPTABLE). + entity(new Error("Failure to inject event over " + topic)). + build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.NOT_FOUND). + entity(new Error(topic + " not found")). + build(); + } catch (IllegalStateException e) { + return Response.status(Response.Status.NOT_ACCEPTABLE). + entity(new Error(topic + " not acceptable due to current state")). + build(); + } catch (Exception e) { + return Response.status(Response.Status.INTERNAL_SERVER_ERROR). + entity(new Error(e.getMessage())). + build(); + } + } + + @GET + @Path("engine/tools/uuid") + @ApiOperation( + value="Produces an UUID", + notes="UUID generation utility" + ) + @Produces(MediaType.TEXT_PLAIN) + public Response uuid() { + return Response.status(Status.OK). + entity(UUID.randomUUID().toString()). + build(); + } + + @GET + @Path("engine/tools/loggers") + @ApiOperation( + value="all active loggers", + responseContainer="List" + ) + @ApiResponses(value = { + @ApiResponse(code=500, message="logging misconfiguration") + }) + public Response loggers() { + List<String> names = new ArrayList<String>(); + if (!(LoggerFactory.getILoggerFactory() instanceof LoggerContext)) { + logger.warn("The SLF4J logger factory is not configured for logback"); + return Response.status(Status.INTERNAL_SERVER_ERROR). + entity(names).build(); + } + + LoggerContext context = + (LoggerContext) LoggerFactory.getILoggerFactory(); + for (Logger logger: context.getLoggerList()) { + names.add(logger.getName()); + } + + return Response.status(Status.OK). + entity(names). + build(); + } + + @GET + @Path("engine/tools/loggers/{logger}") + @ApiOperation( + value="logging level of a logger" + ) + @ApiResponses(value = { + @ApiResponse(code=500, message="logging misconfiguration"), + @ApiResponse(code=404, message="logger not found") + }) + public Response loggerName(@ApiParam(value="Logger Name", required=true) + @PathParam("logger") String loggerName) { + if (!(LoggerFactory.getILoggerFactory() instanceof LoggerContext)) { + logger.warn("The SLF4J logger factory is not configured for logback"); + return Response.status(Status.INTERNAL_SERVER_ERROR).build(); + } + + LoggerContext context = + (LoggerContext) LoggerFactory.getILoggerFactory(); + ch.qos.logback.classic.Logger logger = context.getLogger(loggerName); + if (logger == null) { + return Response.status(Status.NOT_FOUND).build(); + } + + String loggerLevel = (logger.getLevel() != null) ? logger.getLevel().toString() : ""; + return Response.status(Status.OK).entity(loggerLevel).build(); + } + + @PUT + @Path("engine/tools/loggers/{logger}/{level}") + @ApiOperation( + value="sets the logger level", + notes="Please use the SLF4J logger levels" + ) + @ApiResponses(value = { + @ApiResponse(code=500, message="logging misconfiguration"), + @ApiResponse(code=404, message="logger not found") + }) + public Response loggerName(@ApiParam(value="Logger Name", required=true) + @PathParam("logger") String loggerName, + @ApiParam(value="Logger Level", required=true) + @PathParam("level") String loggerLevel) { + if (!(LoggerFactory.getILoggerFactory() instanceof LoggerContext)) { + logger.warn("The SLF4J logger factory is not configured for logback"); + return Response.status(Status.INTERNAL_SERVER_ERROR).build(); + } + + LoggerContext context = + (LoggerContext) LoggerFactory.getILoggerFactory(); + ch.qos.logback.classic.Logger logger = context.getLogger(loggerName); + if (logger == null) { + return Response.status(Status.NOT_FOUND).build(); + } + + logger.setLevel(ch.qos.logback.classic.Level.toLevel(loggerLevel)); + return Response.status(Status.OK).entity(logger.getLevel().toString()).build(); + } + + /** + * gets the underlying drools controller from the named policy controller + * @param controllerName the policy controller name + * @return the underlying drools controller + * @throws IllegalArgumentException if an invalid controller name has been passed in + */ + protected DroolsController getDroolsController(String controllerName) throws IllegalArgumentException { + PolicyController controller = PolicyController.factory.get(controllerName); + if (controller == null) + throw new IllegalArgumentException(controllerName + " does not exist"); + + DroolsController drools = controller.getDrools(); + if (drools == null) + throw new IllegalArgumentException(controllerName + " has no drools configuration"); + + return drools; + } + + /* + * Helper classes for aggregation of results + */ + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("rest-telemetry-api []"); + return builder.toString(); + } + + /** + * Coding/Encoding Results Aggregation Helper class + */ + public static class CodingResult { + /** + * serialized output + */ + + public String jsonEncoding; + /** + * encoding result + */ + + public Boolean encoding; + + /** + * decoding result + */ + public Boolean decoding; + } + + /** + * Generic Error Reporting class + */ + public static class Error { + public String error; + + public Error(String error) { + this.error = error; + } + } + + /** + * Feed Ports into Resources + */ + public enum Inputs { + configuration, + } + + /** + * Resource Toggles + */ + public enum Switches { + activation, + lock, + } +} + diff --git a/policy-management/src/main/java/org/onap/policy/drools/system/Main.java b/policy-management/src/main/java/org/onap/policy/drools/system/Main.java new file mode 100644 index 00000000..fe0cc01f --- /dev/null +++ b/policy-management/src/main/java/org/onap/policy/drools/system/Main.java @@ -0,0 +1,135 @@ +/*- + * ============LICENSE_START======================================================= + * policy-management + * ================================================================================ + * Copyright (C) 2017 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.drools.system; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Properties; + +import org.onap.policy.drools.persistence.SystemPersistence; +import org.onap.policy.drools.utils.PropertyUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Programmatic entry point to the management layer + */ +public class Main { + + /** + * logback configuration file system property + */ + public static final String LOGBACK_CONFIGURATION_FILE_SYSTEM_PROPERTY = "logback.configurationFile"; + + /** + * logback configuration file system property + */ + public static final String LOGBACK_CONFIGURATION_FILE_DEFAULT = "config/logback.xml"; + + /** + * constructor (hides public default one) + */ + private Main() {} + + /** + * main + * + * @param args program arguments + */ + public static void main(String args[]) { + + /* logging defaults */ + + if (System.getProperty(LOGBACK_CONFIGURATION_FILE_SYSTEM_PROPERTY) == null) + System.setProperty(LOGBACK_CONFIGURATION_FILE_SYSTEM_PROPERTY, LOGBACK_CONFIGURATION_FILE_DEFAULT); + + /* 0. boot */ + + PolicyEngine.manager.boot(args); + + Logger logger = LoggerFactory.getLogger(Main.class); + + File configDir = new File(SystemPersistence.CONFIG_DIR_NAME); + + if (!configDir.isDirectory()) { + throw new IllegalArgumentException + ("config directory: " + configDir.getAbsolutePath() + + " not found"); + } + + /* 1. Configure the Engine */ + + try { + Path policyEnginePath = Paths.get(configDir.toPath().toString(), SystemPersistence.PROPERTIES_FILE_ENGINE); + Properties properties = PropertyUtil.getProperties(policyEnginePath.toFile()); + PolicyEngine.manager.configure(properties); + } catch (Exception e) { + logger.warn("Main: cannot initialize {} because of {}", PolicyEngine.manager, e.getMessage(), e); + } + + /* 2. Start the Engine with the basic services only (no Policy Controllers) */ + + try { + boolean success = PolicyEngine.manager.start(); + if (!success) { + logger.warn("Main: {} has been partially started", PolicyEngine.manager); + } + } catch (IllegalStateException e) { + logger.warn("Main: cannot start {} (bad state) because of {}", PolicyEngine.manager, e.getMessage(), e); + } catch (Exception e) { + logger.warn("Main: cannot start {} because of {}", PolicyEngine.manager, e.getMessage(), e); + System.exit(1); + } + + /* 3. Create and start the controllers */ + + File[] controllerFiles = configDir.listFiles(); + for (File config : controllerFiles) { + + if (config.getName().endsWith(SystemPersistence.PROPERTIES_FILE_CONTROLLER_SUFFIX)) { + int idxSuffix = + config.getName().indexOf(SystemPersistence.PROPERTIES_FILE_CONTROLLER_SUFFIX); + int lastIdxSuffix = + config.getName().lastIndexOf(SystemPersistence.PROPERTIES_FILE_CONTROLLER_SUFFIX); + if (idxSuffix != lastIdxSuffix) { + throw new IllegalArgumentException + ("Improper naming of controller properties file: " + + "Expected <controller-name>" + + SystemPersistence.PROPERTIES_FILE_CONTROLLER_SUFFIX); + } + + String name = + config.getName().substring(0, lastIdxSuffix); + try { + Properties properties = PropertyUtil.getProperties(config); + PolicyController controller = PolicyEngine.manager.createPolicyController(name, properties); + controller.start(); + } catch (Exception e) { + logger.error("Main: cannot instantiate policy-controller {} because of {}", name, e.getMessage(), e); + } catch (LinkageError e) { + logger.warn("Main: cannot instantiate policy-controller {} (linkage) because of {}", + name, e.getMessage(), e); + } + } + } + } +} diff --git a/policy-management/src/main/java/org/onap/policy/drools/system/PolicyController.java b/policy-management/src/main/java/org/onap/policy/drools/system/PolicyController.java new file mode 100644 index 00000000..e6e1ca7e --- /dev/null +++ b/policy-management/src/main/java/org/onap/policy/drools/system/PolicyController.java @@ -0,0 +1,110 @@ +/*- + * ============LICENSE_START======================================================= + * policy-management + * ================================================================================ + * Copyright (C) 2017 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.drools.system; + +import java.util.List; +import java.util.Properties; + +import org.onap.policy.drools.controller.DroolsController; +import org.onap.policy.drools.event.comm.TopicSource; +import org.onap.policy.drools.event.comm.Topic.CommInfrastructure; +import org.onap.policy.drools.event.comm.TopicSink; +import org.onap.policy.drools.properties.Lockable; +import org.onap.policy.drools.properties.Startable; +import org.onap.policy.drools.protocol.configuration.DroolsConfiguration; + +/** + * A Policy Controller is the higher level unit of control. It corresponds to + * the ncomp equivalent of a controller. It provides management of underlying + * resources associated with the policy controller, which is a) communication + * infrastructure, and b) policy-core (drools) session infrastructure + * + */ +public interface PolicyController extends Startable, Lockable { + + /** + * name of this Policy Controller + */ + public String getName(); + + /** + * Get the topic readers of interest for this controller + */ + public List<? extends TopicSource> getTopicSources(); + + /** + * Get the topic readers of interest for this controller + */ + public List<? extends TopicSink> getTopicSinks(); + + /** + * Get the Drools Controller + */ + public DroolsController getDrools(); + + /** + * update maven configuration + * + * @param newDroolsConfiguration new drools configuration + * @return true if the update was successful, false otherwise + */ + public boolean updateDrools(DroolsConfiguration newDroolsConfiguration); + + /** + * Get the Properties + */ + public Properties getProperties(); + + /** + * Attempts delivering of an String over communication + * infrastructure "busType" + * + * @param eventBus Communication infrastructure identifier + * @param topic topic + * @param event the event object to send + * + * @return true if successful, false if a failure has occurred. + * @throws IllegalArgumentException when invalid or insufficient + * properties are provided + * @throws IllegalStateException when the engine is in a state where + * this operation is not permitted (ie. locked or stopped). + * @throws UnsupportedOperationException when the engine cannot deliver due + * to the functionality missing (ie. communication infrastructure + * not supported. + */ + public boolean deliver(CommInfrastructure busType, String topic, + Object event) + throws IllegalArgumentException, IllegalStateException, + UnsupportedOperationException; + + /** + * halts and permanently releases all resources + * @throws IllegalStateException + */ + public void halt() throws IllegalStateException; + + /** + * Factory that tracks and manages Policy Controllers + */ + public static PolicyControllerFactory factory = + new IndexedPolicyControllerFactory(); + +} diff --git a/policy-management/src/main/java/org/onap/policy/drools/system/PolicyControllerFactory.java b/policy-management/src/main/java/org/onap/policy/drools/system/PolicyControllerFactory.java new file mode 100644 index 00000000..34c8589f --- /dev/null +++ b/policy-management/src/main/java/org/onap/policy/drools/system/PolicyControllerFactory.java @@ -0,0 +1,522 @@ +/*- + * ============LICENSE_START======================================================= + * policy-management + * ================================================================================ + * Copyright (C) 2017 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.drools.system; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Properties; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.onap.policy.drools.controller.DroolsController; +import org.onap.policy.drools.features.PolicyControllerFeatureAPI; +import org.onap.policy.drools.protocol.configuration.DroolsConfiguration; +import org.onap.policy.drools.system.internal.AggregatedPolicyController; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +/** + * Policy Controller Factory to manage controller creation, destruction, + * and retrieval for management interfaces + */ +public interface PolicyControllerFactory { + /** + * Build a controller from a properties file + * + * @param name the global name of this controller + * @param properties input parameters in form of properties for controller + * initialization. + * + * @return a Policy Controller + * + * @throws IllegalArgumentException invalid values provided in properties + */ + public PolicyController build(String name, Properties properties) + throws IllegalArgumentException; + + /** + * patches (updates) a controller from a critical configuration update. + * + * @param name + * @param configController + * + * @return a Policy Controller + */ + public PolicyController patch(String name, DroolsConfiguration configController); + + /** + * rebuilds (updates) a controller from a configuration update. + * + * @param controller + * @param configController + * + * @return a Policy Controller + */ + public PolicyController patch(PolicyController controller, + DroolsConfiguration configController); + + /** + * get PolicyController from DroolsController + * + * @param droolsController + * @return + * @throws IllegalArgumentException + * @throws IllegalStateException + */ + public PolicyController get(DroolsController droolsController) + throws IllegalArgumentException, IllegalStateException; + + /** + * Makes the Policy Controller identified by controllerName not operational, but + * does not delete its associated data + * + * @param controllerName name of the policy controller + * @throws IllegalArgumentException invalid arguments + */ + public void shutdown(String controllerName) throws IllegalArgumentException;; + + /** + * Makes the Policy Controller identified by controller not operational, but + * does not delete its associated data + * + * @param controller a Policy Controller + * @throws IllegalArgumentException invalid arguments + */ + public void shutdown(PolicyController controller) throws IllegalArgumentException; + + /** + * Releases all Policy Controllers from operation + */ + public void shutdown(); + + /** + * Destroys this Policy Controller + * + * @param controllerName name of the policy controller + * @throws IllegalArgumentException invalid arguments + */ + public void destroy(String controllerName) throws IllegalArgumentException;; + + /** + * Destroys this Policy Controller + * + * @param controller a Policy Controller + * @throws IllegalArgumentException invalid arguments + */ + public void destroy(PolicyController controller) throws IllegalArgumentException; + + /** + * Releases all Policy Controller resources + */ + public void destroy(); + + /** + * gets the Policy Controller identified by its name + * + * @param policyControllerName + * @return + * @throws IllegalArgumentException + * @throws IllegalStateException + */ + public PolicyController get(String policyControllerName) + throws IllegalArgumentException, IllegalStateException; + + /** + * gets the Policy Controller identified by group and artifact ids + * + * @param groupId group id + * @param artifactId artifact id + * @return + * @throws IllegalArgumentException + * @throws IllegalStateException + */ + public PolicyController get(String groupId, String artifactId) + throws IllegalArgumentException, IllegalStateException; + + /** + * get features attached to the Policy Controllers + * @return list of features + */ + public List<PolicyControllerFeatureAPI> getFeatureProviders(); + + /** + * get named feature attached to the Policy Controllers + * @return the feature + */ + public PolicyControllerFeatureAPI getFeatureProvider(String featureName) + throws IllegalArgumentException; + + /** + * get features attached to the Policy Controllers + * @return list of features + */ + public List<String> getFeatures(); + + /** + * returns the current inventory of Policy Controllers + * + * @return a list of Policy Controllers + */ + public List<PolicyController> inventory(); +} + +/** + * Factory of Policy Controllers indexed by the name of the Policy Controller + */ +class IndexedPolicyControllerFactory implements PolicyControllerFactory { + // get an instance of logger + private static Logger logger = LoggerFactory.getLogger(PolicyControllerFactory.class); + + /** + * Policy Controller Name Index + */ + protected HashMap<String,PolicyController> policyControllers = + new HashMap<String,PolicyController>(); + + /** + * Group/Artifact Ids Index + */ + protected HashMap<String,PolicyController> coordinates2Controller = + new HashMap<String,PolicyController>(); + + /** + * produces key for indexing controller names + * + * @param group group id + * @param artifactId artifact id + * @return index key + */ + protected String toKey(String groupId, String artifactId) { + return groupId + ":" + artifactId; + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized PolicyController build(String name, Properties properties) + throws IllegalArgumentException { + + if (this.policyControllers.containsKey(name)) { + return this.policyControllers.get(name); + } + + /* A PolicyController does not exist */ + + PolicyController controller = + new AggregatedPolicyController(name, properties); + + String coordinates = toKey(controller.getDrools().getGroupId(), + controller.getDrools().getArtifactId()); + + this.policyControllers.put(name, controller); + + + if (controller.getDrools().isBrained()) + this.coordinates2Controller.put(coordinates, controller); + + return controller; + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized PolicyController patch(String name, DroolsConfiguration droolsConfig) + throws IllegalArgumentException { + + if (name == null || name.isEmpty() || !this.policyControllers.containsKey(name)) { + throw new IllegalArgumentException("Invalid " + name); + } + + if (droolsConfig == null) + throw new IllegalArgumentException("Invalid Drools Configuration"); + + PolicyController controller = this.get(name); + + if (controller == null) { + logger.warn("A POLICY CONTROLLER of name " + name + + "does not exist for patch operation: " + droolsConfig); + + throw new IllegalArgumentException("Not a valid controller of name " + name); + } + + this.patch(controller, droolsConfig); + + if (logger.isInfoEnabled()) + logger.info("UPDATED drools configuration: " + droolsConfig + " on " + this); + + return controller; + } + + + /** + * {@inheritDoc} + */ + @Override + public PolicyController patch(PolicyController controller, DroolsConfiguration droolsConfig) + throws IllegalArgumentException { + + if (controller == null) + throw new IllegalArgumentException("Not a valid controller: null"); + + if (!controller.updateDrools(droolsConfig)) { + logger.warn("Cannot update drools configuration: " + droolsConfig + " on " + this); + throw new IllegalArgumentException("Cannot update drools configuration Drools Configuration"); + } + + if (logger.isInfoEnabled()) + logger.info("UPDATED drools configuration: " + droolsConfig + " on " + this); + + String coordinates = toKey(controller.getDrools().getGroupId(), + controller.getDrools().getArtifactId()); + + if (controller.getDrools().isBrained()) + this.coordinates2Controller.put(coordinates, controller); + + return controller; + } + + /** + * {@inheritDoc} + */ + @Override + public void shutdown(String controllerName) throws IllegalArgumentException { + + if (controllerName == null || controllerName.isEmpty()) { + throw new IllegalArgumentException("Invalid " + controllerName); + } + + synchronized(this) { + if (!this.policyControllers.containsKey(controllerName)) { + return; + } + + PolicyController controller = this.policyControllers.get(controllerName); + this.shutdown(controller); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void shutdown(PolicyController controller) throws IllegalArgumentException { + this.unmanage(controller); + controller.shutdown(); + } + + /** + * {@inheritDoc} + */ + @Override + public void shutdown() { + List<PolicyController> controllers = this.inventory(); + for (PolicyController controller: controllers) { + controller.shutdown(); + } + + synchronized(this) { + this.policyControllers.clear(); + this.coordinates2Controller.clear(); + } + } + + /** + * unmanage the controller + * + * @param controller + * @return + * @throws IllegalArgumentException + */ + protected void unmanage(PolicyController controller) throws IllegalArgumentException { + if (controller == null) { + throw new IllegalArgumentException("Invalid Controller"); + } + + synchronized(this) { + if (!this.policyControllers.containsKey(controller.getName())) { + return; + } + controller = this.policyControllers.remove(controller.getName()); + + String coordinates = toKey(controller.getDrools().getGroupId(), + controller.getDrools().getArtifactId()); + this.coordinates2Controller.remove(coordinates); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void destroy(String controllerName) throws IllegalArgumentException { + + if (controllerName == null || controllerName.isEmpty()) { + throw new IllegalArgumentException("Invalid " + controllerName); + } + + synchronized(this) { + if (!this.policyControllers.containsKey(controllerName)) { + return; + } + + PolicyController controller = this.policyControllers.get(controllerName); + this.destroy(controller); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void destroy(PolicyController controller) throws IllegalArgumentException { + this.unmanage(controller); + controller.halt(); + } + + /** + * {@inheritDoc} + */ + @Override + public void destroy() { + List<PolicyController> controllers = this.inventory(); + for (PolicyController controller: controllers) { + controller.halt(); + } + + synchronized(this) { + this.policyControllers.clear(); + this.coordinates2Controller.clear(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public PolicyController get(String name) throws IllegalArgumentException, IllegalStateException { + + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException("Invalid " + name); + } + + synchronized(this) { + if (this.policyControllers.containsKey(name)) { + return this.policyControllers.get(name); + } else { + throw new IllegalArgumentException("Invalid " + name); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public PolicyController get(String groupId, String artifactId) + throws IllegalArgumentException, IllegalStateException { + + if (groupId == null || groupId.isEmpty() || + artifactId == null || artifactId.isEmpty()) { + throw new IllegalArgumentException("Invalid group/artifact ids"); + } + + synchronized(this) { + String key = toKey(groupId,artifactId); + if (this.coordinates2Controller.containsKey(key)) { + return this.coordinates2Controller.get(key); + } else { + throw new IllegalArgumentException("Invalid " + key); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public PolicyController get(DroolsController droolsController) + throws IllegalArgumentException, IllegalStateException { + + if (droolsController == null) { + throw new IllegalArgumentException("No Drools Controller provided"); + } + + synchronized(this) { + String key = toKey(droolsController.getGroupId(), droolsController.getArtifactId()); + if (this.coordinates2Controller.containsKey(key)) { + return this.coordinates2Controller.get(key); + } else { + logger.error("Drools Controller not associated with Policy Controller " + droolsController + ":" + this); + throw new IllegalStateException("Drools Controller not associated with Policy Controller " + droolsController + ":" + this); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public List<PolicyController> inventory() { + List<PolicyController> controllers = + new ArrayList<PolicyController>(this.policyControllers.values()); + return controllers; + } + + /** + * {@inheritDoc} + */ + @Override + public List<String> getFeatures() { + List<String> features = new ArrayList<String>(); + for (PolicyControllerFeatureAPI feature : PolicyControllerFeatureAPI.providers.getList()) { + features.add(feature.getName()); + } + return features; + } + + /** + * {@inheritDoc} + */ + @JsonIgnore + @Override + public List<PolicyControllerFeatureAPI> getFeatureProviders() { + return PolicyControllerFeatureAPI.providers.getList(); + } + + /** + * {@inheritDoc} + */ + @Override + public PolicyControllerFeatureAPI getFeatureProvider(String featureName) throws IllegalArgumentException { + if (featureName == null || featureName.isEmpty()) + throw new IllegalArgumentException("A feature name must be provided"); + + for (PolicyControllerFeatureAPI feature : PolicyControllerFeatureAPI.providers.getList()) { + if (feature.getName().equals(featureName)) + return feature; + } + + throw new IllegalArgumentException("Invalid Feature Name: " + featureName); + } +} diff --git a/policy-management/src/main/java/org/onap/policy/drools/system/PolicyEngine.java b/policy-management/src/main/java/org/onap/policy/drools/system/PolicyEngine.java new file mode 100644 index 00000000..1ee12304 --- /dev/null +++ b/policy-management/src/main/java/org/onap/policy/drools/system/PolicyEngine.java @@ -0,0 +1,1461 @@ +/*- + * ============LICENSE_START======================================================= + * policy-management + * ================================================================================ + * Copyright (C) 2017 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.drools.system; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.onap.policy.drools.controller.DroolsController; +import org.onap.policy.drools.core.PolicyContainer; +import org.onap.policy.drools.core.jmx.PdpJmxListener; +import org.onap.policy.drools.event.comm.Topic; +import org.onap.policy.drools.event.comm.Topic.CommInfrastructure; +import org.onap.policy.drools.event.comm.TopicEndpoint; +import org.onap.policy.drools.event.comm.TopicListener; +import org.onap.policy.drools.event.comm.TopicSink; +import org.onap.policy.drools.event.comm.TopicSource; +import org.onap.policy.drools.features.PolicyControllerFeatureAPI; +import org.onap.policy.drools.features.PolicyEngineFeatureAPI; +import org.onap.policy.drools.http.server.HttpServletServer; +import org.onap.policy.drools.persistence.SystemPersistence; +import org.onap.policy.drools.properties.Lockable; +import org.onap.policy.drools.properties.PolicyProperties; +import org.onap.policy.drools.properties.Startable; +import org.onap.policy.drools.protocol.coders.EventProtocolCoder; +import org.onap.policy.drools.protocol.configuration.ControllerConfiguration; +import org.onap.policy.drools.protocol.configuration.PdpdConfiguration; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +/** + * Policy Engine, the top abstraction for the Drools PDP Policy Engine. + * It abstracts away a Drools PDP Engine from management purposes. + * This is the best place to looking at the code from a top down approach. + * Other managed entities can be obtained from the PolicyEngine, hierarchically. + * <br> + * PolicyEngine 1 --- * PolicyController 1 --- 1 DroolsController 1 --- 1 PolicyContainer 1 --- * PolicySession + * <br> + * PolicyEngine 1 --- 1 TopicEndpointManager 1 -- * TopicReader 1 --- 1 UebTopicReader + * <br> + * PolicyEngine 1 --- 1 TopicEndpointManager 1 -- * TopicReader 1 --- 1 DmaapTopicReader + * <br> + * PolicyEngine 1 --- 1 TopicEndpointManager 1 -- * TopicWriter 1 --- 1 DmaapTopicWriter + * <br> + * PolicyEngine 1 --- 1 TopicEndpointManager 1 -- * TopicReader 1 --- 1 RestTopicReader + * <br> + * PolicyEngine 1 --- 1 TopicEndpointManager 1 -- * TopicWriter 1 --- 1 RestTopicWriter + * <br> + * PolicyEngine 1 --- 1 ManagementServer + */ +public interface PolicyEngine extends Startable, Lockable, TopicListener { + + /** + * Default Config Server Port + */ + public static final int CONFIG_SERVER_DEFAULT_PORT = 9696; + + /** + * Default Config Server Hostname + */ + public static final String CONFIG_SERVER_DEFAULT_HOST = "localhost"; + + /** + * Boot the engine + * + * @param cliArgs command line arguments + */ + public void boot(String cliArgs[]); + + /** + * configure the policy engine according to the given properties + * + * @param properties Policy Engine properties + * @throws IllegalArgumentException when invalid or insufficient + * properties are provided + */ + public void configure(Properties properties) throws IllegalArgumentException; + + /** + * registers a new Policy Controller with the Policy Engine + * initialized per properties. + * + * @param controller name + * @param properties properties to initialize the Policy Controller + * @throws IllegalArgumentException when invalid or insufficient + * properties are provided + * @throws IllegalStateException when the engine is in a state where + * this operation is not permitted. + * @return the newly instantiated Policy Controller + */ + public PolicyController createPolicyController(String name, Properties properties) + throws IllegalArgumentException, IllegalStateException; + + /** + * updates the Policy Engine with the given configuration + * + * @param configuration the configuration + * @return success or failure + * @throws IllegalArgumentException if invalid argument provided + * @throws IllegalStateException if the system is in an invalid state + */ + public boolean configure(PdpdConfiguration configuration) + throws IllegalArgumentException, IllegalStateException; + + /** + * updates a set of Policy Controllers with configuration information + * + * @param configuration + * @return + * @throws IllegalArgumentException + * @throws IllegalStateException + */ + public List<PolicyController> updatePolicyControllers(List<ControllerConfiguration> configuration) + throws IllegalArgumentException, IllegalStateException; + + /** + * updates an already existing Policy Controller with configuration information + * + * @param configuration configuration + * + * @return the updated Policy Controller + * @throws IllegalArgumentException in the configuration is invalid + * @throws IllegalStateException if the controller is in a bad state + * @throws Exception any other reason + */ + public PolicyController updatePolicyController(ControllerConfiguration configuration) + throws Exception; + + /** + * removes the Policy Controller identified by its name from the Policy Engine + * + * @param name name of the Policy Controller + * @return the removed Policy Controller + */ + public void removePolicyController(String name); + + /** + * removes a Policy Controller from the Policy Engine + * @param controller the Policy Controller to remove from the Policy Engine + */ + public void removePolicyController(PolicyController controller); + + /** + * returns a list of the available Policy Controllers + * + * @return list of Policy Controllers + */ + public List<PolicyController> getPolicyControllers(); + + + /** + * get policy controller names + * + * @return list of controller names + */ + public List<String> getPolicyControllerIds(); + + /** + * get unmanaged sources + * + * @return unmanaged sources + */ + public List<TopicSource> getSources(); + + /** + * get unmanaged sinks + * + * @return unmanaged sinks + */ + public List<TopicSink> getSinks(); + + /** + * get unmmanaged http servers list + * @return http servers + */ + public List<HttpServletServer> getHttpServers(); + + /** + * get properties configuration + * + * @return properties objects + */ + public Properties getProperties(); + + /** + * get features attached to the Policy Engine + * @return list of features + */ + public List<PolicyEngineFeatureAPI> getFeatureProviders(); + + /** + * get named feature attached to the Policy Engine + * @return the feature + */ + public PolicyEngineFeatureAPI getFeatureProvider(String featureName) + throws IllegalArgumentException; + + /** + * get features attached to the Policy Engine + * @return list of features + */ + public List<String> getFeatures(); + + /** + * Attempts the dispatching of an "event" object + * + * @param topic topic + * @param event the event object to send + * + * @return true if successful, false if a failure has occurred. + * @throws IllegalArgumentException when invalid or insufficient + * properties are provided + * @throws IllegalStateException when the engine is in a state where + * this operation is not permitted (ie. locked or stopped). + */ + public boolean deliver(String topic, Object event) + throws IllegalArgumentException, IllegalStateException; + + /** + * Attempts the dispatching of an "event" object over communication + * infrastructure "busType" + * + * @param eventBus Communication infrastructure identifier + * @param topic topic + * @param event the event object to send + * + * @return true if successful, false if a failure has occurred. + * @throws IllegalArgumentException when invalid or insufficient + * properties are provided + * @throws IllegalStateException when the engine is in a state where + * this operation is not permitted (ie. locked or stopped). + * @throws UnsupportedOperationException when the engine cannot deliver due + * to the functionality missing (ie. communication infrastructure + * not supported. + */ + public boolean deliver(String busType, String topic, Object event) + throws IllegalArgumentException, IllegalStateException, + UnsupportedOperationException; + + /** + * Attempts the dispatching of an "event" object over communication + * infrastructure "busType" + * + * @param eventBus Communication infrastructure enum + * @param topic topic + * @param event the event object to send + * + * @return true if successful, false if a failure has occurred. + * @throws IllegalArgumentException when invalid or insufficient + * properties are provided + * @throws IllegalStateException when the engine is in a state where + * this operation is not permitted (ie. locked or stopped). + * @throws UnsupportedOperationException when the engine cannot deliver due + * to the functionality missing (ie. communication infrastructure + * not supported. + */ + public boolean deliver(CommInfrastructure busType, String topic, Object event) + throws IllegalArgumentException, IllegalStateException, + UnsupportedOperationException; + + /** + * Attempts delivering of an String over communication + * infrastructure "busType" + * + * @param eventBus Communication infrastructure identifier + * @param topic topic + * @param event the event object to send + * + * @return true if successful, false if a failure has occurred. + * @throws IllegalArgumentException when invalid or insufficient + * properties are provided + * @throws IllegalStateException when the engine is in a state where + * this operation is not permitted (ie. locked or stopped). + * @throws UnsupportedOperationException when the engine cannot deliver due + * to the functionality missing (ie. communication infrastructure + * not supported. + */ + public boolean deliver(CommInfrastructure busType, String topic, + String event) + throws IllegalArgumentException, IllegalStateException, + UnsupportedOperationException; + + /** + * Invoked when the host goes into the active state. + */ + public void activate(); + + /** + * Invoked when the host goes into the standby state. + */ + public void deactivate(); + + /** + * Policy Engine Manager + */ + public final static PolicyEngine manager = new PolicyEngineManager(); +} + +/** + * Policy Engine Manager Implementation + */ +class PolicyEngineManager implements PolicyEngine { + /** + * logger + */ + private static Logger logger = LoggerFactory.getLogger(PolicyEngineManager.class); + + /** + * Is the Policy Engine running? + */ + protected boolean alive = false; + + /** + * Is the engine locked? + */ + protected boolean locked = false; + + /** + * Properties used to initialize the engine + */ + protected Properties properties; + + /** + * Policy Engine Sources + */ + protected List<? extends TopicSource> sources = new ArrayList<>(); + + /** + * Policy Engine Sinks + */ + protected List<? extends TopicSink> sinks = new ArrayList<>(); + + /** + * Policy Engine HTTP Servers + */ + protected List<HttpServletServer> httpServers = new ArrayList<HttpServletServer>(); + + /** + * gson parser to decode configuration requests + */ + protected Gson decoder = new GsonBuilder().disableHtmlEscaping().create(); + + + /** + * {@inheritDoc} + */ + @Override + public synchronized void boot(String cliArgs[]) { + + for (PolicyEngineFeatureAPI feature : PolicyEngineFeatureAPI.providers.getList()) { + try { + if (feature.beforeBoot(this, cliArgs)) + return; + } catch (Exception e) { + logger.error("{}: feature {} before-boot failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + + try { + PolicyContainer.globalInit(cliArgs); + } catch (Exception e) { + logger.error("{}: cannot init policy-container because of {}", this, e.getMessage(), e); + } + + for (PolicyEngineFeatureAPI feature : PolicyEngineFeatureAPI.providers.getList()) { + try { + if (feature.afterBoot(this)) + return; + } catch (Exception e) { + logger.error("{}: feature {} after-boot failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void configure(Properties properties) throws IllegalArgumentException { + + if (properties == null) { + logger.warn("No properties provided"); + throw new IllegalArgumentException("No properties provided"); + } + + /* policy-engine dispatch pre configure hook */ + for (PolicyEngineFeatureAPI feature : PolicyEngineFeatureAPI.providers.getList()) { + try { + if (feature.beforeConfigure(this, properties)) + return; + } catch (Exception e) { + logger.error("{}: feature {} before-configure failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + + this.properties = properties; + + try { + this.sources = TopicEndpoint.manager.addTopicSources(properties); + for (TopicSource source: this.sources) { + source.register(this); + } + } catch (Exception e) { + logger.error("{}: add-sources failed", this, e); + } + + try { + this.sinks = TopicEndpoint.manager.addTopicSinks(properties); + } catch (IllegalArgumentException e) { + logger.error("{}: add-sinks failed", this, e); + } + + try { + this.httpServers = HttpServletServer.factory.build(properties); + } catch (IllegalArgumentException e) { + logger.error("{}: add-http-servers failed", this, e); + } + + /* policy-engine dispatch post configure hook */ + for (PolicyEngineFeatureAPI feature : PolicyEngineFeatureAPI.providers.getList()) { + try { + if (feature.afterConfigure(this)) + return; + } catch (Exception e) { + logger.error("{}: feature {} after-configure failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + + return; + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized PolicyController createPolicyController(String name, Properties properties) + throws IllegalArgumentException, IllegalStateException { + + // check if a PROPERTY_CONTROLLER_NAME property is present + // if so, override the given name + + String propertyControllerName = properties.getProperty(PolicyProperties.PROPERTY_CONTROLLER_NAME); + if (propertyControllerName != null && !propertyControllerName.isEmpty()) { + if (!propertyControllerName.equals(name)) { + throw new IllegalStateException("Proposed name (" + name + + ") and properties name (" + propertyControllerName + + ") don't match"); + } + name = propertyControllerName; + } + + PolicyController controller; + for (PolicyControllerFeatureAPI controllerFeature : PolicyControllerFeatureAPI.providers.getList()) { + try { + controller = controllerFeature.beforeCreate(name, properties); + if (controller != null) + return controller; + } catch (Exception e) { + logger.error("{}: feature {} before-controller-create failure because of {}", + this, controllerFeature.getClass().getName(), e.getMessage(), e); + } + } + + controller = PolicyController.factory.build(name, properties); + if (this.isLocked()) + controller.lock(); + + // feature hook + for (PolicyControllerFeatureAPI controllerFeature : PolicyControllerFeatureAPI.providers.getList()) { + try { + if (controllerFeature.afterCreate(controller)) + return controller; + } catch (Exception e) { + logger.error("{}: feature {} after-controller-create failure because of {}", + this, controllerFeature.getClass().getName(), e.getMessage(), e); + } + } + + return controller; + } + + + /** + * {@inheritDoc} + */ + @Override + public boolean configure(PdpdConfiguration config) throws IllegalArgumentException, IllegalStateException { + + if (config == null) + throw new IllegalArgumentException("No configuration provided"); + + String entity = config.getEntity(); + + switch (entity) { + case PdpdConfiguration.CONFIG_ENTITY_CONTROLLER: + /* only this one supported for now */ + List<ControllerConfiguration> configControllers = config.getControllers(); + if (configControllers == null || configControllers.isEmpty()) { + if (logger.isInfoEnabled()) + logger.info("No controller configuration provided: " + config); + return false; + } + List<PolicyController> policyControllers = this.updatePolicyControllers(config.getControllers()); + if (policyControllers == null || policyControllers.isEmpty()) + return false; + else if (policyControllers.size() == configControllers.size()) + return true; + + return false; + default: + String msg = "Configuration Entity is not supported: " + entity; + logger.warn(msg); + throw new IllegalArgumentException(msg); + } + } + + /** + * {@inheritDoc} + */ + @Override + public List<PolicyController> updatePolicyControllers(List<ControllerConfiguration> configControllers) + throws IllegalArgumentException, IllegalStateException { + + List<PolicyController> policyControllers = new ArrayList<PolicyController>(); + if (configControllers == null || configControllers.isEmpty()) { + if (logger.isInfoEnabled()) + logger.info("No controller configuration provided: " + configControllers); + return policyControllers; + } + + for (ControllerConfiguration configController: configControllers) { + try { + PolicyController policyController = this.updatePolicyController(configController); + policyControllers.add(policyController); + } catch (Exception e) { + logger.error("{}: cannot update-policy-controllers because of {}", this, e.getMessage(), e); + } + } + + return policyControllers; + } + + /** + * {@inheritDoc} + */ + @Override + public PolicyController updatePolicyController(ControllerConfiguration configController) + throws Exception { + + if (configController == null) + throw new IllegalArgumentException("No controller configuration has been provided"); + + String controllerName = configController.getName(); + if (controllerName == null || controllerName.isEmpty()) { + logger.warn("controller-name must be provided"); + throw new IllegalArgumentException("No controller configuration has been provided"); + } + + PolicyController policyController = null; + try { + String operation = configController.getOperation(); + if (operation == null || operation.isEmpty()) { + logger.warn("operation must be provided"); + throw new IllegalArgumentException("operation must be provided"); + } + + try { + policyController = PolicyController.factory.get(controllerName); + } catch (IllegalArgumentException e) { + // not found + logger.warn("Policy Controller " + controllerName + " not found"); + } + + if (policyController == null) { + + if (operation.equalsIgnoreCase(ControllerConfiguration.CONFIG_CONTROLLER_OPERATION_LOCK) || + operation.equalsIgnoreCase(ControllerConfiguration.CONFIG_CONTROLLER_OPERATION_UNLOCK)) { + throw new IllegalArgumentException(controllerName + " is not available for operation " + operation); + } + + /* Recovery case */ + + logger.warn("controller " + controllerName + " does not exist. " + + "Attempting recovery from disk"); + + Properties properties = + SystemPersistence.manager.getControllerProperties(controllerName); + + /* + * returned properties cannot be null (per implementation) + * assert (properties != null) + */ + + if (properties == null) { + throw new IllegalArgumentException(controllerName + " is invalid"); + } + + logger.warn("controller " + controllerName + " being recovered. " + + "Reset controller's bad maven coordinates to brainless"); + + /* + * try to bring up bad controller in brainless mode, + * after having it working, apply the new create/update operation. + */ + properties.setProperty(PolicyProperties.RULES_GROUPID, DroolsController.NO_GROUP_ID); + properties.setProperty(PolicyProperties.RULES_ARTIFACTID, DroolsController.NO_ARTIFACT_ID); + properties.setProperty(PolicyProperties.RULES_VERSION, DroolsController.NO_VERSION); + + policyController = PolicyEngine.manager.createPolicyController(controllerName, properties); + + /* fall through to do brain update operation*/ + } + + switch (operation) { + case ControllerConfiguration.CONFIG_CONTROLLER_OPERATION_CREATE: + PolicyController.factory.patch(policyController, configController.getDrools()); + break; + case ControllerConfiguration.CONFIG_CONTROLLER_OPERATION_UPDATE: + policyController.unlock(); + PolicyController.factory.patch(policyController, configController.getDrools()); + break; + case ControllerConfiguration.CONFIG_CONTROLLER_OPERATION_LOCK: + policyController.lock(); + break; + case ControllerConfiguration.CONFIG_CONTROLLER_OPERATION_UNLOCK: + policyController.unlock(); + break; + default: + String msg = "Controller Operation Configuration is not supported: " + + operation + " for " + controllerName; + logger.warn(msg); + throw new IllegalArgumentException(msg); + } + + return policyController; + } catch (Exception e) { + logger.error("{}: cannot update-policy-controller because of {}", this, e.getMessage(), e); + throw e; + } catch (LinkageError e) { + logger.error("{}: cannot update-policy-controllers (rules) because of {}", this, e.getMessage(), e); + throw new IllegalStateException(e); + } + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized boolean start() throws IllegalStateException { + + /* policy-engine dispatch pre start hook */ + for (PolicyEngineFeatureAPI feature : PolicyEngineFeatureAPI.providers.getList()) { + try { + if (feature.beforeStart(this)) + return true; + } catch (Exception e) { + logger.error("{}: feature {} before-start failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + + boolean success = true; + if (this.locked) + throw new IllegalStateException("Engine is locked"); + + this.alive = true; + + /* Start Policy Engine exclusively-owned (unmanaged) http servers */ + + for (HttpServletServer httpServer: this.httpServers) { + try { + if (!httpServer.waitedStart(5 * 1000L)) + success = false; + } catch (Exception e) { + logger.error("{}: cannot start http-server {} because of {}", this, + httpServer, e.getMessage(), e); + } + } + + /* Start Policy Engine exclusively-owned (unmanaged) sources */ + + for (TopicSource source: this.sources) { + try { + if (!source.start()) + success = false; + } catch (Exception e) { + logger.error("{}: cannot start topic-source {} because of {}", this, + source, e.getMessage(), e); + } + } + + /* Start Policy Engine owned (unmanaged) sinks */ + + for (TopicSink sink: this.sinks) { + try { + if (!sink.start()) + success = false; + } catch (Exception e) { + logger.error("{}: cannot start topic-sink {} because of {}", this, + sink, e.getMessage(), e); + } + } + + /* Start Policy Controllers */ + + List<PolicyController> controllers = PolicyController.factory.inventory(); + for (PolicyController controller : controllers) { + try { + if (!controller.start()) + success = false; + } catch (Exception e) { + logger.error("{}: cannot start policy-controller {} because of {}", this, + controller, e.getMessage(), e); + success = false; + } + } + + /* Start managed Topic Endpoints */ + + try { + if (!TopicEndpoint.manager.start()) + success = false; + } catch (IllegalStateException e) { + logger.warn("{}: Topic Endpoint Manager is in an invalid state because of {}", this, e.getMessage(), e); + } + + + // Start the JMX listener + + PdpJmxListener.start(); + + /* policy-engine dispatch after start hook */ + for (PolicyEngineFeatureAPI feature : PolicyEngineFeatureAPI.providers.getList()) { + try { + if (feature.afterStart(this)) + return success; + } catch (Exception e) { + logger.error("{}: feature {} after-start failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + + return success; + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized boolean stop() { + + /* policy-engine dispatch pre stop hook */ + for (PolicyEngineFeatureAPI feature : PolicyEngineFeatureAPI.providers.getList()) { + try { + if (feature.beforeStop(this)) + return true; + } catch (Exception e) { + logger.error("{}: feature {} before-stop failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + + /* stop regardless of the lock state */ + + boolean success = true; + if (!this.alive) + return true; + + this.alive = false; + + List<PolicyController> controllers = PolicyController.factory.inventory(); + for (PolicyController controller : controllers) { + try { + if (!controller.stop()) + success = false; + } catch (Exception e) { + logger.error("{}: cannot stop policy-controller {} because of {}", this, + controller, e.getMessage(), e); + success = false; + } + } + + /* Stop Policy Engine owned (unmanaged) sources */ + for (TopicSource source: this.sources) { + try { + if (!source.stop()) + success = false; + } catch (Exception e) { + logger.error("{}: cannot start topic-source {} because of {}", this, + source, e.getMessage(), e); + } + } + + /* Stop Policy Engine owned (unmanaged) sinks */ + for (TopicSink sink: this.sinks) { + try { + if (!sink.stop()) + success = false; + } catch (Exception e) { + logger.error("{}: cannot start topic-sink {} because of {}", this, + sink, e.getMessage(), e); + } + } + + /* stop all managed topics sources and sinks */ + if (!TopicEndpoint.manager.stop()) + success = false; + + /* stop all unmanaged http servers */ + for (HttpServletServer httpServer: this.httpServers) { + try { + if (!httpServer.stop()) + success = false; + } catch (Exception e) { + logger.error("{}: cannot start http-server {} because of {}", this, + httpServer, e.getMessage(), e); + } + } + + /* policy-engine dispatch pre stop hook */ + for (PolicyEngineFeatureAPI feature : PolicyEngineFeatureAPI.providers.getList()) { + try { + if (feature.afterStop(this)) + return success; + } catch (Exception e) { + logger.error("{}: feature {} after-stop failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + + return success; + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void shutdown() throws IllegalStateException { + + /* policy-engine dispatch pre shutdown hook */ + for (PolicyEngineFeatureAPI feature : PolicyEngineFeatureAPI.providers.getList()) { + try { + if (feature.beforeShutdown(this)) + return; + } catch (Exception e) { + logger.error("{}: feature {} before-shutdown failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + + this.alive = false; + + /* Shutdown Policy Engine owned (unmanaged) sources */ + for (TopicSource source: this.sources) { + try { + source.shutdown(); + } catch (Exception e) { + logger.error("{}: cannot shutdown topic-source {} because of {}", this, + source, e.getMessage(), e); + } + } + + /* Shutdown Policy Engine owned (unmanaged) sinks */ + for (TopicSink sink: this.sinks) { + try { + sink.shutdown(); + } catch (Exception e) { + logger.error("{}: cannot shutdown topic-sink {} because of {}", this, + sink, e.getMessage(), e); + } + } + + /* Shutdown managed resources */ + PolicyController.factory.shutdown(); + TopicEndpoint.manager.shutdown(); + HttpServletServer.factory.destroy(); + + // Stop the JMX listener + + PdpJmxListener.stop(); + + /* policy-engine dispatch post shutdown hook */ + for (PolicyEngineFeatureAPI feature : PolicyEngineFeatureAPI.providers.getList()) { + try { + if (feature.afterShutdown(this)) + return; + } catch (Exception e) { + logger.error("{}: feature {} after-shutdown failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + + + new Thread(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(5000L); + } catch (InterruptedException e) { + logger.warn("{}: interrupted-exception while shutting down management server: ", this); + } + + /* shutdown all unmanaged http servers */ + for (HttpServletServer httpServer: getHttpServers()) { + try { + httpServer.shutdown(); + } catch (Exception e) { + logger.error("{}: cannot shutdown http-server {} because of {}", this, + httpServer, e.getMessage(), e); + } + } + + try { + Thread.sleep(5000L); + } catch (InterruptedException e) { + logger.warn("{}: interrupted-exception while shutting down management server: ", this); + } + + System.exit(0); + } + }).start(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized boolean isAlive() { + return this.alive; + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized boolean lock() { + + /* policy-engine dispatch pre lock hook */ + for (PolicyEngineFeatureAPI feature : PolicyEngineFeatureAPI.providers.getList()) { + try { + if (feature.beforeLock(this)) + return true; + } catch (Exception e) { + logger.error("{}: feature {} before-lock failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + + if (this.locked) + return true; + + this.locked = true; + + boolean success = true; + List<PolicyController> controllers = PolicyController.factory.inventory(); + for (PolicyController controller : controllers) { + try { + success = controller.lock() && success; + } catch (Exception e) { + logger.error("{}: cannot lock policy-controller {} because of {}", this, + controller, e.getMessage(), e); + success = false; + } + } + + success = TopicEndpoint.manager.lock() && success; + + /* policy-engine dispatch post lock hook */ + for (PolicyEngineFeatureAPI feature : PolicyEngineFeatureAPI.providers.getList()) { + try { + if (feature.afterLock(this)) + return success; + } catch (Exception e) { + logger.error("{}: feature {} after-lock failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + + return success; + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized boolean unlock() { + + /* policy-engine dispatch pre unlock hook */ + for (PolicyEngineFeatureAPI feature : PolicyEngineFeatureAPI.providers.getList()) { + try { + if (feature.beforeUnlock(this)) + return true; + } catch (Exception e) { + logger.error("{}: feature {} before-unlock failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + + if (!this.locked) + return true; + + this.locked = false; + + boolean success = true; + List<PolicyController> controllers = PolicyController.factory.inventory(); + for (PolicyController controller : controllers) { + try { + success = controller.unlock() && success; + } catch (Exception e) { + logger.error("{}: cannot unlock policy-controller {} because of {}", this, + controller, e.getMessage(), e); + success = false; + } + } + + success = TopicEndpoint.manager.unlock() && success; + + /* policy-engine dispatch after unlock hook */ + for (PolicyEngineFeatureAPI feature : PolicyEngineFeatureAPI.providers.getList()) { + try { + if (feature.afterUnlock(this)) + return success; + } catch (Exception e) { + logger.error("{}: feature {} after-unlock failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + + return success; + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized boolean isLocked() { + return this.locked; + } + + /** + * {@inheritDoc} + */ + @Override + public void removePolicyController(String name) { + PolicyController.factory.destroy(name); + } + + /** + * {@inheritDoc} + */ + @Override + public void removePolicyController(PolicyController controller) { + PolicyController.factory.destroy(controller); + } + + /** + * {@inheritDoc} + */ + @JsonIgnore + @Override + public List<PolicyController> getPolicyControllers() { + return PolicyController.factory.inventory(); + } + + /** + * {@inheritDoc} + */ + @JsonProperty("controllers") + @Override + public List<String> getPolicyControllerIds() { + List<String> controllerNames = new ArrayList<String>(); + for (PolicyController controller: PolicyController.factory.inventory()) { + controllerNames.add(controller.getName()); + } + return controllerNames; + } + + /** + * {@inheritDoc} + */ + @Override + @JsonIgnore + public Properties getProperties() { + return this.properties; + } + + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + public List<TopicSource> getSources() { + return (List<TopicSource>) this.sources; + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + public List<TopicSink> getSinks() { + return (List<TopicSink>) this.sinks; + } + + /** + * {@inheritDoc} + */ + @Override + public List<HttpServletServer> getHttpServers() { + return this.httpServers; + } + + + /** + * {@inheritDoc} + */ + @Override + public List<String> getFeatures() { + List<String> features = new ArrayList<String>(); + for (PolicyEngineFeatureAPI feature : PolicyEngineFeatureAPI.providers.getList()) { + features.add(feature.getName()); + } + return features; + } + + /** + * {@inheritDoc} + */ + @JsonIgnore + @Override + public List<PolicyEngineFeatureAPI> getFeatureProviders() { + return PolicyEngineFeatureAPI.providers.getList(); + } + + /** + * {@inheritDoc} + */ + @Override + public PolicyEngineFeatureAPI getFeatureProvider(String featureName) throws IllegalArgumentException { + if (featureName == null || featureName.isEmpty()) + throw new IllegalArgumentException("A feature name must be provided"); + + for (PolicyEngineFeatureAPI feature : PolicyEngineFeatureAPI.providers.getList()) { + if (feature.getName().equals(featureName)) + return feature; + } + + throw new IllegalArgumentException("Invalid Feature Name: " + featureName); + } + + /** + * {@inheritDoc} + */ + @Override + public void onTopicEvent(CommInfrastructure commType, String topic, String event) { + /* configuration request */ + try { + PdpdConfiguration configuration = this.decoder.fromJson(event, PdpdConfiguration.class); + this.configure(configuration); + } catch (Exception e) { + logger.error("{}: configuration-error due to {} because of {}", + this, event, e.getMessage(), e); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean deliver(String topic, Object event) + throws IllegalArgumentException, IllegalStateException { + + /* + * Note this entry point is usually from the DRL + */ + + if (topic == null || topic.isEmpty()) + throw new IllegalArgumentException("Invalid Topic"); + + if (event == null) + throw new IllegalArgumentException("Invalid Event"); + + if (!this.isAlive()) + throw new IllegalStateException("Policy Engine is stopped"); + + if (this.isLocked()) + throw new IllegalStateException("Policy Engine is locked"); + + List<? extends TopicSink> sinks = + TopicEndpoint.manager.getTopicSinks(topic); + if (sinks == null || sinks.isEmpty() || sinks.size() > 1) + throw new IllegalStateException + ("Cannot ensure correct delivery on topic " + topic + ": " + sinks); + + return this.deliver(sinks.get(0).getTopicCommInfrastructure(), + topic, event); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean deliver(String busType, String topic, Object event) + throws IllegalArgumentException, IllegalStateException, + UnsupportedOperationException { + + /* + * Note this entry point is usually from the DRL (one of the reasons + * busType is String. + */ + + if (busType == null || busType.isEmpty()) + throw new IllegalArgumentException + ("Invalid Communication Infrastructure"); + + if (topic == null || topic.isEmpty()) + throw new IllegalArgumentException("Invalid Topic"); + + if (event == null) + throw new IllegalArgumentException("Invalid Event"); + + boolean valid = false; + for (Topic.CommInfrastructure comm: Topic.CommInfrastructure.values()) { + if (comm.name().equals(busType)) { + valid = true; + } + } + + if (!valid) + throw new IllegalArgumentException + ("Invalid Communication Infrastructure: " + busType); + + + if (!this.isAlive()) + throw new IllegalStateException("Policy Engine is stopped"); + + if (this.isLocked()) + throw new IllegalStateException("Policy Engine is locked"); + + + return this.deliver(Topic.CommInfrastructure.valueOf(busType), + topic, event); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean deliver(Topic.CommInfrastructure busType, + String topic, Object event) + throws IllegalArgumentException, IllegalStateException, + UnsupportedOperationException { + + if (topic == null || topic.isEmpty()) + throw new IllegalArgumentException("Invalid Topic"); + + if (event == null) + throw new IllegalArgumentException("Invalid Event"); + + if (!this.isAlive()) + throw new IllegalStateException("Policy Engine is stopped"); + + if (this.isLocked()) + throw new IllegalStateException("Policy Engine is locked"); + + /* Try to send through the controller, this is the + * preferred way, since it may want to apply additional + * processing + */ + try { + DroolsController droolsController = + EventProtocolCoder.manager.getDroolsController(topic, event); + PolicyController controller = PolicyController.factory.get(droolsController); + if (controller != null) + return controller.deliver(busType, topic, event); + } catch (Exception e) { + logger.warn("{}: cannot find policy-controller to deliver {} over {}:{} because of {}", + this, event, busType, topic, e.getMessage(), e); + + /* continue (try without routing through the controller) */ + } + + /* + * cannot route through the controller, send directly through + * the topic sink + */ + try { + String json = EventProtocolCoder.manager.encode(topic, event); + return this.deliver(busType, topic, json); + + } catch (Exception e) { + logger.warn("{}: cannot deliver {} over {}:{} because of {}", + this, event, busType, topic, e.getMessage(), e); + throw e; + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean deliver(Topic.CommInfrastructure busType, + String topic, String event) + throws IllegalArgumentException, IllegalStateException, + UnsupportedOperationException { + + if (topic == null || topic.isEmpty()) + throw new IllegalArgumentException("Invalid Topic"); + + if (event == null || event.isEmpty()) + throw new IllegalArgumentException("Invalid Event"); + + if (!this.isAlive()) + throw new IllegalStateException("Policy Engine is stopped"); + + if (this.isLocked()) + throw new IllegalStateException("Policy Engine is locked"); + + try { + TopicSink sink = + TopicEndpoint.manager.getTopicSink + (busType, topic); + + if (sink == null) + throw new IllegalStateException("Inconsistent State: " + this); + + return sink.send(event); + + } catch (Exception e) { + logger.warn("{}: cannot deliver {} over {}:{} because of {}", + this, event, busType, topic, e.getMessage(), e); + throw e; + } + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void activate() { + + /* policy-engine dispatch pre activate hook */ + for (PolicyEngineFeatureAPI feature : PolicyEngineFeatureAPI.providers.getList()) { + try { + if (feature.beforeActivate(this)) + return; + } catch (Exception e) { + logger.error("{}: feature {} before-activate failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + + // activate 'policy-management' + for (PolicyController policyController : getPolicyControllers()) { + try { + policyController.unlock(); + policyController.start(); + } catch (Exception e) { + logger.error("{}: cannot activate of policy-controller {} because of {}", + this, policyController,e.getMessage(), e); + } catch (LinkageError e) { + logger.error("{}: cannot activate (rules compilation) of policy-controller {} because of {}", + this, policyController,e.getMessage(), e); + } + } + + this.unlock(); + + /* policy-engine dispatch post activate hook */ + for (PolicyEngineFeatureAPI feature : PolicyEngineFeatureAPI.providers.getList()) { + try { + if (feature.afterActivate(this)) + return; + } catch (Exception e) { + logger.error("{}: feature {} after-activate failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void deactivate() { + + /* policy-engine dispatch pre deactivate hook */ + for (PolicyEngineFeatureAPI feature : PolicyEngineFeatureAPI.providers.getList()) { + try { + if (feature.beforeDeactivate(this)) + return; + } catch (Exception e) { + logger.error("{}: feature {} before-deactivate failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + + this.lock(); + + for (PolicyController policyController : getPolicyControllers()) { + try { + policyController.stop(); + } catch (Exception e) { + logger.error("{}: cannot deactivate (stop) policy-controller {} because of {}", + this, policyController, e.getMessage(), e); + } catch (LinkageError e) { + logger.error("{}: cannot deactivate (stop) policy-controller {} because of {}", + this, policyController, e.getMessage(), e); + } + } + + /* policy-engine dispatch post deactivate hook */ + for (PolicyEngineFeatureAPI feature : PolicyEngineFeatureAPI.providers.getList()) { + try { + if (feature.afterDeactivate(this)) + return; + } catch (Exception e) { + logger.error("{}: feature {} after-deactivate failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("PolicyEngineManager [alive=").append(alive).append(", locked=").append(locked).append("]"); + return builder.toString(); + } + +} + + diff --git a/policy-management/src/main/java/org/onap/policy/drools/system/internal/AggregatedPolicyController.java b/policy-management/src/main/java/org/onap/policy/drools/system/internal/AggregatedPolicyController.java new file mode 100644 index 00000000..e67e542f --- /dev/null +++ b/policy-management/src/main/java/org/onap/policy/drools/system/internal/AggregatedPolicyController.java @@ -0,0 +1,626 @@ +/*- + * ============LICENSE_START======================================================= + * policy-management + * ================================================================================ + * Copyright (C) 2017 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.drools.system.internal; + +import java.util.HashMap; +import java.util.List; +import java.util.Properties; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.onap.policy.drools.controller.DroolsController; +import org.onap.policy.drools.event.comm.Topic; +import org.onap.policy.drools.event.comm.TopicEndpoint; +import org.onap.policy.drools.event.comm.TopicListener; +import org.onap.policy.drools.event.comm.TopicSink; +import org.onap.policy.drools.event.comm.TopicSource; +import org.onap.policy.drools.features.PolicyControllerFeatureAPI; +import org.onap.policy.drools.persistence.SystemPersistence; +import org.onap.policy.drools.properties.PolicyProperties; +import org.onap.policy.drools.protocol.configuration.DroolsConfiguration; +import org.onap.policy.drools.system.PolicyController; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +/** + * This implementation of the Policy Controller merely aggregates and tracks for + * management purposes all underlying resources that this controller depends upon. + */ +public class AggregatedPolicyController implements PolicyController, + TopicListener { + + /** + * Logger + */ + private static Logger logger = LoggerFactory.getLogger(AggregatedPolicyController.class); + + /** + * identifier for this policy controller + */ + protected final String name; + + /** + * Abstracted Event Sources List regardless communication + * technology + */ + protected final List<? extends TopicSource> sources; + + /** + * Abstracted Event Sinks List regardless communication + * technology + */ + protected final List<? extends TopicSink> sinks; + + /** + * Mapping topics to sinks + */ + @JsonIgnore + protected final HashMap<String, TopicSink> topic2Sinks = + new HashMap<String, TopicSink>(); + + /** + * Is this Policy Controller running (alive) ? + * reflects invocation of start()/stop() only + */ + protected volatile boolean alive; + + /** + * Is this Policy Controller locked ? + * reflects if i/o controller related operations and start + * are permitted, + * more specifically: start(), deliver() and onTopicEvent(). + * It does not affect the ability to stop the + * underlying drools infrastructure + */ + protected volatile boolean locked; + + /** + * Policy Drools Controller + */ + protected volatile DroolsController droolsController; + + /** + * Properties used to initialize controller + */ + protected final Properties properties; + + /** + * Constructor version mainly used for bootstrapping at initialization time + * a policy engine controller + * + * @param name controller name + * @param properties + * + * @throws IllegalArgumentException when invalid arguments are provided + */ + public AggregatedPolicyController(String name, Properties properties) + throws IllegalArgumentException { + + this.name = name; + + /* + * 1. Register read topics with network infrastructure (ueb, dmaap, rest) + * 2. Register write topics with network infrastructure (ueb, dmaap, rest) + * 3. Register with drools infrastructure + */ + + // Create/Reuse Readers/Writers for all event sources endpoints + + this.sources = TopicEndpoint.manager.addTopicSources(properties); + this.sinks = TopicEndpoint.manager.addTopicSinks(properties); + + initDrools(properties); + initSinks(); + + /* persist new properties */ + SystemPersistence.manager.storeController(name, properties); + this.properties = properties; + } + + /** + * initialize drools layer + * @throws IllegalArgumentException if invalid parameters are passed in + */ + protected void initDrools(Properties properties) throws IllegalArgumentException { + try { + // Register with drools infrastructure + this.droolsController = DroolsController.factory.build(properties, sources, sinks); + } catch (Exception | LinkageError e) { + logger.error("{}: cannot init-drools because of {}", this, e.getMessage(), e); + throw new IllegalArgumentException(e); + } + } + + /** + * initialize sinks + * @throws IllegalArgumentException if invalid parameters are passed in + */ + protected void initSinks() throws IllegalArgumentException { + this.topic2Sinks.clear(); + for (TopicSink sink: sinks) { + this.topic2Sinks.put(sink.getTopic(), sink); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean updateDrools(DroolsConfiguration newDroolsConfiguration) { + + DroolsConfiguration oldDroolsConfiguration = + new DroolsConfiguration(this.droolsController.getArtifactId(), + this.droolsController.getGroupId(), + this.droolsController.getVersion()); + + if (oldDroolsConfiguration.getGroupId().equalsIgnoreCase(newDroolsConfiguration.getGroupId()) && + oldDroolsConfiguration.getArtifactId().equalsIgnoreCase(newDroolsConfiguration.getArtifactId()) && + oldDroolsConfiguration.getVersion().equalsIgnoreCase(newDroolsConfiguration.getVersion())) { + logger.warn("{}: cannot update-drools: identical configuration {} vs {}", + this, oldDroolsConfiguration, newDroolsConfiguration); + return true; + } + + try { + /* Drools Controller created, update initialization properties for restarts */ + + this.properties.setProperty(PolicyProperties.RULES_GROUPID, newDroolsConfiguration.getGroupId()); + this.properties.setProperty(PolicyProperties.RULES_ARTIFACTID, newDroolsConfiguration.getArtifactId()); + this.properties.setProperty(PolicyProperties.RULES_VERSION, newDroolsConfiguration.getVersion()); + + SystemPersistence.manager.storeController(name, this.properties); + + this.initDrools(this.properties); + + /* set drools controller to current locked status */ + + if (this.isLocked()) + this.droolsController.lock(); + else + this.droolsController.unlock(); + + /* set drools controller to current alive status */ + + if (this.isAlive()) + this.droolsController.start(); + else + this.droolsController.stop(); + + } catch (IllegalArgumentException e) { + logger.error("{}: cannot update-drools because of {}", this, e.getMessage(), e); + return false; + } + + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public String getName() { + return this.name; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean start() throws IllegalStateException { + logger.info("{}: start", this); + + for (PolicyControllerFeatureAPI feature : PolicyControllerFeatureAPI.providers.getList()) { + try { + if (feature.beforeStart(this)) + return true; + } catch (Exception e) { + logger.error("{}: feature {} before-start failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + + if (this.isLocked()) + throw new IllegalStateException("Policy Controller " + name + " is locked"); + + synchronized(this) { + if (this.alive) + return true; + + this.alive = true; + } + + boolean success = this.droolsController.start(); + + // register for events + + for (TopicSource source: sources) { + source.register(this); + } + + for (TopicSink sink: sinks) { + try { + sink.start(); + } catch (Exception e) { + logger.error("{}: cannot start {} because of {}", + this, sink, e.getMessage(), e); + } + } + + for (PolicyControllerFeatureAPI feature : PolicyControllerFeatureAPI.providers.getList()) { + try { + if (feature.afterStart(this)) + return true; + } catch (Exception e) { + logger.error("{}: feature {} after-start failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + + return success; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean stop() { + logger.info("{}: stop", this); + + for (PolicyControllerFeatureAPI feature : PolicyControllerFeatureAPI.providers.getList()) { + try { + if (feature.beforeStop(this)) + return true; + } catch (Exception e) { + logger.error("{}: feature {} before-stop failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + + /* stop regardless locked state */ + + synchronized(this) { + if (!this.alive) + return true; + + this.alive = false; + } + + // 1. Stop registration + + for (TopicSource source: sources) { + source.unregister(this); + } + + boolean success = this.droolsController.stop(); + + for (PolicyControllerFeatureAPI feature : PolicyControllerFeatureAPI.providers.getList()) { + try { + if (feature.afterStop(this)) + return true; + } catch (Exception e) { + logger.error("{}: feature {} after-stop failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + + return success; + } + + /** + * {@inheritDoc} + */ + @Override + public void shutdown() throws IllegalStateException { + logger.info("{}: shutdown", this); + + for (PolicyControllerFeatureAPI feature : PolicyControllerFeatureAPI.providers.getList()) { + try { + if (feature.beforeShutdown(this)) + return; + } catch (Exception e) { + logger.error("{}: feature {} before-shutdown failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + + this.stop(); + + DroolsController.factory.shutdown(this.droolsController); + + for (PolicyControllerFeatureAPI feature : PolicyControllerFeatureAPI.providers.getList()) { + try { + if (feature.afterShutdown(this)) + return; + } catch (Exception e) { + logger.error("{}: feature {} after-shutdown failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void halt() throws IllegalStateException { + logger.info("{}: halt", this); + + for (PolicyControllerFeatureAPI feature : PolicyControllerFeatureAPI.providers.getList()) { + try { + if (feature.beforeHalt(this)) + return; + } catch (Exception e) { + logger.error("{}: feature {} before-halt failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + + this.stop(); + DroolsController.factory.destroy(this.droolsController); + SystemPersistence.manager.deleteController(this.name); + + for (PolicyControllerFeatureAPI feature : PolicyControllerFeatureAPI.providers.getList()) { + try { + if (feature.afterHalt(this)) + return; + } catch (Exception e) { + logger.error("{}: feature {} after-halt failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onTopicEvent(Topic.CommInfrastructure commType, + String topic, String event) { + + if (logger.isDebugEnabled()) + logger.debug("{}: event offered from {}:{}: {}", this, commType, topic, event); + + for (PolicyControllerFeatureAPI feature : PolicyControllerFeatureAPI.providers.getList()) { + try { + if (feature.beforeOffer(this, commType, topic, event)) + return; + } catch (Exception e) { + logger.error("{}: feature {} before-offer failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + + if (this.locked) + return; + + if (!this.alive) + return; + + boolean success = this.droolsController.offer(topic, event); + + for (PolicyControllerFeatureAPI feature : PolicyControllerFeatureAPI.providers.getList()) { + try { + if (feature.afterOffer(this, commType, topic, event, success)) + return; + } catch (Exception e) { + logger.error("{}: feature {} after-offer failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean deliver(Topic.CommInfrastructure commType, + String topic, Object event) + throws IllegalArgumentException, IllegalStateException, + UnsupportedOperationException { + + if (logger.isDebugEnabled()) + logger.debug("{}: deliver event to {}:{}: {}", this, commType, topic, event); + + for (PolicyControllerFeatureAPI feature : PolicyControllerFeatureAPI.providers.getList()) { + try { + if (feature.beforeDeliver(this, commType, topic, event)) + return true; + } catch (Exception e) { + logger.error("{}: feature {} before-deliver failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + + if (topic == null || topic.isEmpty()) + throw new IllegalArgumentException("Invalid Topic"); + + if (event == null) + throw new IllegalArgumentException("Invalid Event"); + + if (!this.isAlive()) + throw new IllegalStateException("Policy Engine is stopped"); + + if (this.isLocked()) + throw new IllegalStateException("Policy Engine is locked"); + + if (!this.topic2Sinks.containsKey(topic)) { + logger.warn("{}: cannot deliver event because the sink {}:{} is not registered: {}", + this, commType, topic, event); + throw new IllegalArgumentException("Unsuported topic " + topic + " for delivery"); + } + + boolean success = this.droolsController.deliver(this.topic2Sinks.get(topic), event); + + for (PolicyControllerFeatureAPI feature : PolicyControllerFeatureAPI.providers.getList()) { + try { + if (feature.afterDeliver(this, commType, topic, event, success)) + return success; + } catch (Exception e) { + logger.error("{}: feature {} after-deliver failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + + return success; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isAlive() { + return this.alive; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean lock() { + logger.info("{}: lock", this); + + for (PolicyControllerFeatureAPI feature : PolicyControllerFeatureAPI.providers.getList()) { + try { + if (feature.beforeLock(this)) + return true; + } catch (Exception e) { + logger.error("{}: feature {} before-lock failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + + synchronized(this) { + if (this.locked) + return true; + + this.locked = true; + } + + // it does not affect associated sources/sinks, they are + // autonomous entities + + boolean success = this.droolsController.lock(); + + for (PolicyControllerFeatureAPI feature : PolicyControllerFeatureAPI.providers.getList()) { + try { + if (feature.afterLock(this)) + return true; + } catch (Exception e) { + logger.error("{}: feature {} after-lock failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + + return success; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean unlock() { + + logger.info("{}: unlock", this); + + for (PolicyControllerFeatureAPI feature : PolicyControllerFeatureAPI.providers.getList()) { + try { + if (feature.beforeUnlock(this)) + return true; + } catch (Exception e) { + logger.error("{}: feature {} before-unlock failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + + synchronized(this) { + if (!this.locked) + return true; + + this.locked = false; + } + + boolean success = this.droolsController.unlock(); + + for (PolicyControllerFeatureAPI feature : PolicyControllerFeatureAPI.providers.getList()) { + try { + if (feature.afterUnlock(this)) + return true; + } catch (Exception e) { + logger.error("{}: feature {} after-unlock failure because of {}", + this, feature.getClass().getName(), e.getMessage(), e); + } + } + + return success; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isLocked() { + return this.locked; + } + + /** + * {@inheritDoc} + */ + @Override + public List<? extends TopicSource> getTopicSources() { + return this.sources; + } + + /** + * {@inheritDoc} + */ + @Override + public List<? extends TopicSink> getTopicSinks() { + return this.sinks; + } + + /** + * {@inheritDoc} + */ + @Override + public DroolsController getDrools() { + return this.droolsController; + } + + + /** + * {@inheritDoc} + */ + @Override + @JsonIgnore + public Properties getProperties() { + return this.properties; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("AggregatedPolicyController [name=").append(name).append(", alive=").append(alive).append(", locked=").append(locked) + .append(", droolsController=").append(droolsController).append("]"); + return builder.toString(); + } + +} + |