/*- * ============LICENSE_START======================================================= * ONAP : CCSDK * ================================================================================ * 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.ccsdk.sli.core.slipluginutils; import java.io.File; import java.io.FileOutputStream; import java.io.PrintStream; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Properties; import java.util.Set; import java.util.UUID; import org.apache.commons.lang3.StringUtils; import org.onap.ccsdk.sli.core.sli.SvcLogicContext; import org.onap.ccsdk.sli.core.sli.SvcLogicException; import org.onap.ccsdk.sli.core.sli.SvcLogicJavaPlugin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A utility class used to streamline the interface between Java plugins, * the Service Logic Context, and Directed Graphs. * @version 7.0.1 * @see org.onap.ccsdk.sli.core.sli.SvcLogicContext */ public class SliPluginUtils implements SvcLogicJavaPlugin { public enum LogLevel { TRACE, DEBUG, INFO, WARN, ERROR; } private static final Logger LOG = LoggerFactory.getLogger(SliPluginUtils.class); // ========== CONSTRUCTORS ========== public SliPluginUtils() {} // ========== CONTEXT MEMORY FUNCTIONS ========== /** * Removes 1 or more elements from a list in context memory. *

* Values are removed based on either the index in the list, a key-value * pair, or a list of key-value pairs that all must match in the element. * @param parameters * @param ctx Reference to context memory * @throws SvcLogicException All exceptions are wrapped in * SvcLogicException for compatibility with SLI. * @since 7.0.1 */ public void ctxListRemove( Map parameters, SvcLogicContext ctx ) throws SvcLogicException { try{ LOG.debug( "ENTERING Execute Node \"ctxListRemove\"" ); // Validate, Log, & read parameters checkParameters(parameters, new String[]{"list_pfx"}, LOG); logExecuteNodeParameters(parameters, LOG, LogLevel.DEBUG); String list_pfx = parameters.get("list_pfx"); String param_index = parameters.get("index"); String param_key = parameters.get("key"); String param_value = parameters.get("value"); String param_keys_length = parameters.get("keys_length"); // Initialize context memory list mimic SvcLogicContextList list; // Process based on input parameters: // index: remove object at specific index // key & value: remove all objects with key-value pair // keys_length: remove all objects that match all key-value pairs // in list if( param_index != null ) { // Parse index LOG.trace("executing remove by index logic"); int index; try { index = Integer.parseInt(param_index); } catch( NumberFormatException e ) { throw new IllegalArgumentException("\"index\" parameter is not a number. index = " + param_index, e); } // Extract list from context memory & remove object @ index LOG.trace("extracting list from context memory"); list = SvcLogicContextList.extract(ctx, list_pfx); LOG.trace("removing elements from list"); list.remove(index); } else if( param_value != null ) { if( param_key == null ) { param_key = ""; } // Extract list from context memory & remove objects with // key-value pair LOG.trace("executing remove by key-value pair logic"); LOG.trace("extracting list from context memory"); list = SvcLogicContextList.extract(ctx, list_pfx); LOG.trace("removing elements from list"); list.remove( param_key, param_value ); } else if( param_keys_length != null ) { // Parse keys_length LOG.trace("executing remove by key-value pair list logic"); int keys_length; try { keys_length = Integer.parseInt(param_keys_length); } catch( NumberFormatException e ) { throw new IllegalArgumentException("\"keys_length\" parameters is not a number. keys_length = " + param_keys_length, e); } // Obtain key-value pairs to check from parameters LOG.trace("reading keys parameter list"); HashMap keys_values = new HashMap<>(); for( int i = 0; i < keys_length; i++ ) { keys_values.put(parameters.get("keys[" + i + "].key"), parameters.get("keys[" + i + "].value")); } // Extract list from context memory & remove objects with all // key-value pairs matching LOG.trace("extracting list from context memory"); list = SvcLogicContextList.extract(ctx, list_pfx); LOG.trace("removing elements from list"); list.remove(keys_values); } else { throw new IllegalArgumentException("Required parameters missing. Requires one of: index, key & value, or keys_length array"); } // Remove index from list LOG.trace("writing list back into context memory"); list.writeToContext(ctx); } catch( Exception e ) { throw new SvcLogicException( "An error occurred in the ctxListRemove Execute node", e ); } finally { LOG.debug( "EXITING Execute Node \"ctxListRemove\"" ); } } /** * ctxSortList * @param parameters - the set of required parameters must contain list and delimiter. * @param ctx Reference to context memory * @throws SvcLogicException if a required parameter is missing an exception is thrown */ public void ctxSortList( Map parameters, SvcLogicContext ctx ) throws SvcLogicException { checkParameters(parameters, new String[]{"list","delimiter"}, LOG); ArrayList list = new ArrayList<>(); String[] sort_fields = null; if( parameters.containsKey("sort-fields") ) { sort_fields = parameters.get("sort-fields").split(parameters.get("delimiter"), 0); } String ctx_list_str = parameters.get("list"); int listSz = getArrayLength(ctx, ctx_list_str); for( int i = 0; i < listSz; i++ ) { list.add( new SortableCtxListElement(ctx, ctx_list_str + '[' + i + ']', sort_fields) ); } Collections.sort(list); ctxBulkErase(ctx, ctx_list_str); int i = 0; for( SortableCtxListElement list_element : list ) { for( Map.Entry entry : list_element.child_elements.entrySet() ) { if( sort_fields == null ) { ctx.setAttribute(ctx_list_str + '[' + i + ']', entry.getValue()); } else { ctx.setAttribute(ctx_list_str + '[' + i + "]." + entry.getKey(), entry.getValue()); } } i++; } // Reset list length (removed by ctxBulkErase above) ctx.setAttribute(ctx_list_str+"_length", Integer.toString(listSz)); } /** * generates a UUID and writes it to context memory * @param parameters - ctx-destination is a required parameter * @param ctx Reference to context memory * @throws SvcLogicException thrown if a UUID cannot be generated or if ctx-destination is missing or null */ public void generateUUID( Map parameters, SvcLogicContext ctx ) throws SvcLogicException { checkParameters(parameters, new String[]{"ctx-destination"}, LOG); ctx.setAttribute(parameters.get("ctx-destination"), UUID.randomUUID().toString() ); } /** * Provides substring functionality to Directed Graphs. *

* Calls either String.substring(String beginIndex) or * String.substring(String beginInded, String endIndex) if the end-index * is present or not. * @param parameters HashMap of parameters passed by the DG to this function * * * * * * * * *
parameterMandatory/Optionaldescription
stringMandatoryString to perform substring on
resultMandatoryKey in context memory to populate the resulting string in
begin-indexMandatoryBeginning index to pass to Java substring function
end-indexOptionalEnding index to pass to Java substring function. If not included, String.substring(begin) will be called.
* @param ctx Reference to context memory * @throws SvcLogicException * @since 8.0.1 * @see SliPluginUtils#substring(Map, SvcLogicContext) */ @Deprecated public void substring( Map parameters, SvcLogicContext ctx ) throws SvcLogicException { try { checkParameters( parameters, new String[]{"string","begin-index","result"}, LOG ); final String string = parameters.get("string"); final String result = parameters.get("result"); final String begin = parameters.get("begin-index"); final String end = parameters.get("end-index"); if( StringUtils.isEmpty(end) ) { ctx.setAttribute( result, string.substring(Integer.parseInt(begin)) ); } else { ctx.setAttribute( result, string.substring(Integer.parseInt(begin), Integer.parseInt(end)) ); } } catch( Exception e ) { throw new SvcLogicException( "An error occurred while the Directed Graph was performing a substring", e ); } } // ========== PUBLIC STATIC UTILITY FUNCTIONS ========== /** * Throws an exception and writes an error to the log file if a required * parameters is not found in the parametersMap. *

* Use at the beginning of functions that can be called by Directed Graphs * and can take parameters to verify that all parameters have been provided * by the Directed Graph. * @param parametersMap parameters Map passed to this node * @param requiredParams Array of parameters required by the calling function * @param log Reference to Logger to log to * @throws SvcLogicException if a String in the requiredParams array is * not a key in parametersMap. * @since 1.0 */ public static final void checkParameters(Map parametersMap, String[] requiredParams, Logger log) throws SvcLogicException { if( requiredParams == null || requiredParams.length < 1){ log.debug("required parameters was empty, exiting early."); return; } if (parametersMap == null || parametersMap.keySet().isEmpty()){ String errorMessage = "This method requires the parameters [" + StringUtils.join(requiredParams,",") + "], but no parameters were passed in."; log.error(errorMessage); throw new SvcLogicException(errorMessage); } for (String param : requiredParams) { if (!parametersMap.containsKey(param)) { String errorMessage = "Required parameter \"" + param + "\" was not found in parameter list."; log.error(errorMessage); log.error("Total list of required parameters is [" + StringUtils.join(requiredParams, ",") + "]."); throw new SvcLogicException(errorMessage); } } } /** * Removes all key-value pairs with keys that begin with pfx * @param ctx Reference to context memory * @param pfx Prefix of key-value pairs to remove * @since 1.0 */ public static final void ctxBulkErase( SvcLogicContext ctx, String pfx ) { ArrayList Keys = new ArrayList<>(ctx.getAttributeKeySet()); for( String key : Keys ) { if( key.startsWith( pfx ) ) { ctx.setAttribute( pfx + key.substring(pfx.length()) , null); } } } /** * Copies all context memory key-value pairs that start with src_pfx to * the keys that start with dest_pfx + suffix, where suffix is the result * of {@code key.substring(src_pfx.length())}. *

* Does NOT guarantee removal of all keys at the destination before * copying, but will overwrite any destination keys that have a * corresponding source key. Use {@link #ctxBulkErase(SvcLogicContext, String) ctxBulkErase} * before copy to erase destination root before copying from source. * @param ctx Reference to context memory. * @param src_pfx Prefix of the keys to copy values from. * @param dest_pfx Prefix of the keys to copy values to. * @since 1.0 */ public static final void ctxBulkCopy( SvcLogicContext ctx, String src_pfx, String dest_pfx ) { // Remove trailing period from dest_pfx if( dest_pfx.charAt(dest_pfx.length()-1) == '.' ) { dest_pfx = dest_pfx.substring(0,dest_pfx.length()-1); } // For each context key that begins with src_pfx, set the value of the // key dest_pfx + the suffix of the key to the key's value ArrayList Keys = new ArrayList<>(ctx.getAttributeKeySet()); for( String key : Keys ) { if( key.startsWith(src_pfx) ) { // Get suffix (no leading period) String suffix = key.substring(src_pfx.length()); if( suffix.charAt(0) == '.') { suffix = suffix.substring(1); } // Set destination's value to key's value ctx.setAttribute(dest_pfx + '.' + suffix, ctx.getAttribute(key)); } } } /** * Creates and returns a {@code Map} that is a subset of * context memory where all keys begin with the prefix. * @param ctx Reference to context memory. * @param prefix Returned map's keys should all begin with this value. * @return A {@code Map} containing all the key-value pairs * in ctx whose key begins with prefix. */ public static final Map ctxGetBeginsWith( SvcLogicContext ctx, String prefix ) { Map prefixMap = new HashMap<>(); for( String key : ctx.getAttributeKeySet() ) { if( key.startsWith(prefix) ) { prefixMap.put( key, ctx.getAttribute(key) ); } } return prefixMap; } /** * Returns true if key's value in context memory is "" or if it doesn't * exist in context memory. * @param ctx Reference to context memory. * @param key Key to search for. * @return true if key's value in context memory is "" or if it doesn't * exist in context memory. * @since 1.0 */ public static final boolean ctxKeyEmpty( SvcLogicContext ctx, String key ) { String value = ctx.getAttribute(key); return value == null || value.isEmpty(); } /** * Adds all key-value pairs in the entries Map to context memory. * @param ctx Reference to context memory. Value's {@code toString()} * function is used to add it. * @param entries {@code Map} of key-value pairs to add to * context memory. Value's {@code toString()} function is used to add it. * @return Reference to context memory to be used for function chaining. */ public static final SvcLogicContext ctxPutAll( SvcLogicContext ctx, Map entries ) { for( Map.Entry entry : entries.entrySet() ) { ctxSetAttribute( ctx, entry.getKey(), entry.getValue() ); //ctx.setAttribute(entry.getKey(), entry.getValue().toString()); } return ctx; } /** * Sets a key in context memory to the output of object's toString(). The * key is deleted from context memory if object is null. * @param ctx Reference to context memory. * @param key Key to set. * @param object Object whose toString() will be the value set */ public static final void ctxSetAttribute( SvcLogicContext ctx, String key, Object object ) { if( object == null ) { ctx.setAttribute(key, null); } else { ctx.setAttribute(key, object.toString()); } } /** * Sets a key in context memory to the output of object's toString(). *

* The key is deleted from context memory if object is null. The key and * value set in context memory are logged to the Logger at the provided * logLevel level. * @param Any Java object * @param ctx Reference to context memory. * @param key Key to set. * @param obj Object whose toString() will be the value set * @param LOG Logger to log to * @param logLevel level to log at in Logger */ public static final void ctxSetAttribute( SvcLogicContext ctx, String key, O obj, Logger LOG, LogLevel logLevel ) { String value = Objects.toString( obj, null ); ctx.setAttribute( key, value ); if( logLevelIsEnabled(LOG, logLevel ) ) { if( value == null ) { logMessageAtLevel( LOG, logLevel, "Deleting " + key ); } else { logMessageAtLevel( LOG, logLevel, "Setting " + key + " = " + value ); } } } /** * Utility function used to get an array's length from context memory. * Will return 0 if key doesn't exist in context memory or isn't numeric. *

* Use to obtain a context memory array length without having to worry * about throwing a NumberFormatException. * @param ctx Reference to context memory * @param key Key in context memory whose value is the array's length. If * the key doesn't end in "_length", then "_length is appended. * @param log Reference to Logger to log to * @return The array length or 0 if the key is not found in context memory. * @since 1.0 */ public static final int getArrayLength( SvcLogicContext ctx, String key ) { return getArrayLength(ctx, key, null, null, null); } /** * Utility function used to get an array's length from context memory. * Will return 0 if key doesn't exist in context memory or isn't numeric * and print the provided log message to the configured log file. *

* Use to obtain a context memory array length without having to worry * about throwing a NumberFormatException. * @param ctx Reference to context memory. * @param key Key in context memory whose value is the array's length. If * the key doesn't end in "_length", then "_length is appended. * @param log Reference to Logger to log to. Doesn't log if null. * @param logLevel Logging level to log the message at if the context * memory key isn't found. Doesn't log if null. * @param log_message Message to log if the context memory key isn't found. * Doesn't log if null. * @return The array length or 0 if the key is not found in context memory. * @since 1.0 */ public static final int getArrayLength( SvcLogicContext ctx, String key, Logger log, LogLevel logLevel, String log_message ) { String ctxKey = key.endsWith("_length") ? key : key + "_length"; try { return Integer.parseInt(ctx.getAttribute(ctxKey)); } catch( NumberFormatException e ) { if( log != null && logLevel != null && log_message != null ) { switch( logLevel ) { case TRACE: log.trace(log_message); break; case DEBUG: log.debug(log_message); break; case INFO: log.info(log_message); break; case WARN: log.warn(log_message); break; case ERROR: log.error(log_message); break; } } } return 0; } /** * Prints sorted context memory key-value pairs to the log file at the log * level. Returns immediately if the log level isn't enabled. *

* O(n log(n)) time where n = size of context memory * @param ctx Reference to context memory * @param log Reference to Logger to log to * @param logLevel Logging level to log the context memory key-value pairs * at. * @since 1.0 */ public static final void logContextMemory( SvcLogicContext ctx, Logger log, LogLevel logLevel ) { logLevelIsEnabled( log, logLevel ); // Print sorted context memory key-value pairs to the log ArrayList keys = new ArrayList<>(ctx.getAttributeKeySet()); Collections.sort(keys); for( String key : keys ) { logMessageAtLevel( log, logLevel, key + " = " + ctx.getAttribute(key) ); } } // ========== PRIVATE FUNCTIONS ========== // TODO: javadoc /** * * @param parameters * @param log * @param loglevel * @since 7.0.1 */ public static final void logExecuteNodeParameters( Map parameters, Logger log, LogLevel loglevel ) { logLevelIsEnabled( log, loglevel ); for( Map.Entry param : parameters.entrySet() ) { logMessageAtLevel( log, loglevel, "PARAM: " + param.getKey() + " = " + param.getValue() ); } } // TODO: javadoc /** * Returns true if the loglevel is enabled. Otherwise, returns false. * @param log Reference to logger * @param loglevel Log level to check if enabled * @return True if the loglevel is enabled. Otherwise, false * @since 7.0.1 */ private static final boolean logLevelIsEnabled( Logger log, LogLevel loglevel ) { // Return immediately if logging level isn't enabled switch( loglevel ) { case TRACE: if( log.isTraceEnabled() ) { return true; } return false; case DEBUG: if( log.isDebugEnabled() ) { return true; } return false; case INFO: if( log.isInfoEnabled() ) { return true; } return false; case WARN: if( log.isWarnEnabled() ) { return true; } return false; case ERROR: if( log.isErrorEnabled() ) { return true; } return false; default: throw new IllegalArgumentException("Unknown LogLevel: " + loglevel.toString()); } } // TODO: javadoc /** * * @param log * @param loglevel * @param msg * @since 7.0.1 */ private static final void logMessageAtLevel( Logger log, LogLevel loglevel, String msg ) { switch( loglevel ) { case TRACE: log.trace(msg); return; case DEBUG: log.debug(msg); return; case INFO: log.info(msg); return; case WARN: log.warn(msg); return; case ERROR: log.error(msg); return; } } // ========== LOCAL CLASSES ========== private class SortableCtxListElement implements Comparable { HashMap child_elements = new HashMap<>(); String[] sort_fields; public SortableCtxListElement( SvcLogicContext ctx, String root, String[] sort_fields ) { this.sort_fields = sort_fields; for( String key : ctx.getAttributeKeySet() ) { if( key.startsWith(root) ) { if( key.length() == root.length() ) { child_elements.put("", ctx.getAttribute(key)); break; } else { child_elements.put(key.substring(root.length()+1), ctx.getAttribute(key)); } } } } @Override public int compareTo(SortableCtxListElement arg0) { if( sort_fields == null ) { return this.child_elements.get("").compareTo(arg0.child_elements.get("")); } for( String field : this.sort_fields ) { int result = this.child_elements.get(field).compareTo(arg0.child_elements.get(field)); if( result != 0 ) { return result; } } return 0; } @Override public boolean equals(Object object) { if (this == object) { return true; } if (!(object instanceof SortableCtxListElement)) { return false; } if (!super.equals(object)) { return false; } SortableCtxListElement that = (SortableCtxListElement) object; if (child_elements != null ? !child_elements.equals(that.child_elements) : that.child_elements != null) { return false; } // Probably incorrect - comparing Object[] arrays with Arrays.equals if (!Arrays.equals(sort_fields, that.sort_fields)) { return false; } return true; } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + (child_elements != null ? child_elements.hashCode() : 0); result = 31 * result + Arrays.hashCode(sort_fields); return result; } } /** * Creates a file that contains the content of context memory. * @param parameters - must contain the parameter filename * @param ctx Reference to context memory * @throws SvcLogicException thrown if file cannot be created or if parameters are missing */ public static void printContext(Map parameters, SvcLogicContext ctx) throws SvcLogicException { if (parameters == null || parameters.isEmpty()) { throw new SvcLogicException("no parameters passed"); } checkParameters(parameters, new String[]{"filename"}, LOG); String fileName = parameters.get("filename"); try (FileOutputStream fstr = new FileOutputStream(new File(fileName)); PrintStream pstr = new PrintStream(fstr, true);) { pstr.println("#######################################"); for (String attr : ctx.getAttributeKeySet()) { pstr.println(attr + " = " + ctx.getAttribute(attr)); } } catch (Exception e) { throw new SvcLogicException("Cannot write context to file " + fileName, e); } } /** * Checks context memory for a set of required parameters * Every parameter aside from prefix will be treated as mandatory * @param parameters HashMap of parameters passed by the DG to this function * * * * * *
parameterMandatory/Optionaldescription
prefixOptionalthe prefix will be added to each parameter
* @param ctx Reference to context memory * @throws SvcLogicException * @since 11.0.2 */ public static void requiredParameters(Map parameters, SvcLogicContext ctx) throws SvcLogicException { if (parameters == null || parameters.keySet().isEmpty()) { String errorMessage = "requiredParameters should not be called if the parameters hashmap is null or empty!"; LOG.error(errorMessage); throw new SvcLogicException(errorMessage); } String prefixValue = null; String prefix = "prefix"; if(parameters.containsKey(prefix)){ prefixValue = parameters.get(prefix); parameters.remove(prefix); } checkParameters(prefixValue, ctx.getAttributeKeySet(), parameters.keySet(), LOG); } private static void checkParameters(String prefixValue, Set ctx, Set parameters, Logger log) throws SvcLogicException { for (String param : parameters) { if (prefixValue != null) { param = prefixValue + param; } if (!ctx.contains(param)) { String errorMessage = "This method requires the parameters [" + StringUtils.join(parameters, ",") + "], but " + param + " was not passed in."; log.error(errorMessage); throw new SvcLogicException(errorMessage); } } } /** * is in a different DG invocation just before/after we call NCS and set the state to InProgress */ /** * setTime write the current date time to a string located at outputPath * @param parameters - requires outputPath to not be null * @param ctx Reference to context memory * @throws SvcLogicException if a required parameter is missing an exception is thrown */ public static void setTime(Map parameters, SvcLogicContext ctx) throws SvcLogicException { checkParameters(parameters, new String[] { "outputPath" }, LOG); // Set the DateFormat // "2015-03-16T12:18:35.138Z" SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); // Parse the date String ctxVariable = parameters.get("outputPath"); try { String dateTime = format.format(new Date()); ctx.setAttribute(ctxVariable, dateTime); } catch (Exception ex) { throw new SvcLogicException("problem with setTime", ex); } } }