b8e88f652757cffc86f1eb53bf1cf1239cf456f6
[ccsdk/sli/core.git] / sliPluginUtils / provider / src / main / java / org / onap / ccsdk / sli / core / slipluginutils / SliPluginUtils.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP : CCSDK
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights
6  *                                              reserved.
7  * ================================================================================
8  * Modifications Copyright (C) 2018 IBM.
9  * ================================================================================
10  * Licensed under the Apache License, Version 2.0 (the "License");
11  * you may not use this file except in compliance with the License.
12  * You may obtain a copy of the License at
13  *
14  *      http://www.apache.org/licenses/LICENSE-2.0
15  *
16  * Unless required by applicable law or agreed to in writing, software
17  * distributed under the License is distributed on an "AS IS" BASIS,
18  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19  * See the License for the specific language governing permissions and
20  * limitations under the License.
21  * ============LICENSE_END=========================================================
22  */
23
24 package org.onap.ccsdk.sli.core.slipluginutils;
25
26 import java.io.File;
27 import java.io.FileOutputStream;
28 import java.io.PrintStream;
29 import java.text.SimpleDateFormat;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.Collections;
33 import java.util.Date;
34 import java.util.HashMap;
35 import java.util.HashSet;
36 import java.util.Map;
37 import java.util.Map.Entry;
38 import java.util.Objects;
39 import java.util.Properties;
40 import java.util.Set;
41 import java.util.UUID;
42 import org.apache.commons.lang3.StringUtils;
43 import org.apache.commons.text.StringEscapeUtils;
44 import org.onap.ccsdk.sli.core.sli.SvcLogicContext;
45 import org.onap.ccsdk.sli.core.sli.SvcLogicException;
46 import org.onap.ccsdk.sli.core.sli.SvcLogicJavaPlugin;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49 import com.google.gson.JsonArray;
50 import com.google.gson.JsonElement;
51 import com.google.gson.JsonObject;
52 import com.google.gson.JsonParser;
53
54 /**
55  * A utility class used to streamline the interface between Java plugins,
56  * the Service Logic Context, and Directed Graphs.
57  * @version 7.0.1
58  * @see org.onap.ccsdk.sli.core.sli.SvcLogicContext
59  */
60 public class SliPluginUtils implements SvcLogicJavaPlugin {
61         public enum LogLevel {
62                 TRACE, DEBUG, INFO, WARN, ERROR;
63         }
64
65         private static final Logger LOG = LoggerFactory.getLogger(SliPluginUtils.class);
66         private static final String LOG_MSG="extracting list from context memory";
67         private static final String LOG_MSG1="removing elements from list";
68         private static final String LENGTH="_length";
69
70
71         // ========== CONSTRUCTORS ==========
72
73         public SliPluginUtils() {}
74
75         public SliPluginUtils( Properties props ) {}
76
77
78         // ========== CONTEXT MEMORY FUNCTIONS ==========
79
80         /**
81          * Removes 1 or more elements from a list in context memory.
82          * <p>
83          * Values are removed based on either the index in the list, a key-value
84          * pair, or a list of key-value pairs that all must match in the element.
85          * @param parameters
86          * @param ctx Reference to context memory
87          * @throws SvcLogicException All exceptions are wrapped in
88          * SvcLogicException for compatibility with SLI.
89          * @since 7.0.1
90          */
91         public void ctxListRemove( Map<String,String> parameters, SvcLogicContext ctx ) throws SvcLogicException {
92                 try{
93                         LOG.debug( "ENTERING Execute Node \"ctxListRemove\"" );
94
95                         // Validate, Log, & read parameters
96                         checkParameters(parameters, new String[]{"list_pfx"}, LOG);
97                         logExecuteNodeParameters(parameters, LOG, LogLevel.DEBUG);
98                         String list_pfx = parameters.get("list_pfx");
99                         String param_index = parameters.get("index");
100                         String param_key = parameters.get("key");
101                         String param_value = parameters.get("value");
102                         String param_keys_length = parameters.get("keys_length");
103
104                         // Initialize context memory list mimic
105                         SvcLogicContextList list;
106
107                         // Process based on input parameters:
108                         //   index: remove object at specific index
109                         //   key & value: remove all objects with key-value pair
110                         //   keys_length: remove all objects that match all key-value pairs
111                         //                in list
112                         if( param_index != null ) {
113                                 // Parse index
114                                 LOG.trace("executing remove by index logic");
115                                 int index;
116                                 try {
117                                         index = Integer.parseInt(param_index);
118                                 }
119                                 catch( NumberFormatException e ) {
120                                         throw new IllegalArgumentException("\"index\" parameter is not a number. index = " + param_index, e);
121                                 }
122
123                                 // Extract list from context memory & remove object @ index
124                                 LOG.trace(LOG_MSG);
125                                 list = SvcLogicContextList.extract(ctx, list_pfx);
126                                 LOG.trace(LOG_MSG1);
127                                 list.remove(index);
128                         }
129                         else if( param_value != null ) {
130                                 if( param_key == null ) { param_key = ""; }
131
132                                 // Extract list from context memory & remove objects with
133                                 // key-value pair
134                                 LOG.trace("executing remove by key-value pair logic");
135                                 LOG.trace(LOG_MSG);
136                                 list = SvcLogicContextList.extract(ctx, list_pfx);
137                                 LOG.trace(LOG_MSG1);
138                                 list.remove( param_key, param_value );
139                         }
140                         else if( param_keys_length != null ) {
141                                 // Parse keys_length
142                                 LOG.trace("executing remove by key-value pair list logic");
143                                 int keys_length;
144                                 try {
145                                         keys_length = Integer.parseInt(param_keys_length);
146                                 }
147                                 catch( NumberFormatException e ) {
148                                         throw new IllegalArgumentException("\"keys_length\" parameters is not a number. keys_length = " + param_keys_length, e);
149                                 }
150
151                                 // Obtain key-value pairs to check from parameters
152                                 LOG.trace("reading keys parameter list");
153                                 HashMap<String,String> keys_values = new HashMap<>();
154                                 for( int i = 0; i < keys_length; i++ ) {
155                                         keys_values.put(parameters.get("keys[" + i + "].key"), parameters.get("keys[" + i + "].value"));
156                                 }
157
158                                 // Extract list from context memory & remove objects with all
159                                 // key-value pairs matching
160                                 LOG.trace(LOG_MSG);
161                                 list = SvcLogicContextList.extract(ctx, list_pfx);
162                                 LOG.trace(LOG_MSG1);
163                                 list.remove(keys_values);
164                         }
165                         else {
166                                 throw new IllegalArgumentException("Required parameters missing. Requires one of: index, key & value, or keys_length array");
167                         }
168
169                         // Remove index from list
170                         LOG.trace("writing list back into context memory");
171                         list.writeToContext(ctx);
172                 }
173                 catch( Exception e ) {
174                         throw new SvcLogicException( "An error occurred in the ctxListRemove Execute node", e );
175                 }
176                 finally {
177                         LOG.debug( "EXITING Execute Node \"ctxListRemove\"" );
178                 }
179         }
180
181     /**
182      * ctxSortList
183      * @param parameters - the set of required parameters must contain list and delimiter.
184      * @param ctx Reference to context memory
185      * @throws SvcLogicException if a required parameter is missing an exception is thrown
186      */
187         public void ctxSortList( Map<String, String> parameters, SvcLogicContext ctx ) throws SvcLogicException {
188                 checkParameters(parameters, new String[]{"list","delimiter"}, LOG);
189                 ArrayList<SortableCtxListElement> list = new ArrayList<>();
190
191                 String[] sort_fields = null;
192                 if( parameters.containsKey("sort-fields") ) {
193                         sort_fields = parameters.get("sort-fields").split(parameters.get("delimiter"), 0);
194                 }
195
196                 String ctx_list_str = parameters.get("list");
197                 int listSz = getArrayLength(ctx, ctx_list_str);
198
199
200
201                 for( int i = 0; i < listSz; i++ ) {
202                         list.add( new SortableCtxListElement(ctx, ctx_list_str + '[' + i + ']', sort_fields) );
203                 }
204                 Collections.sort(list);
205
206                 ctxBulkErase(ctx, ctx_list_str);
207                 int i = 0;
208                 for( SortableCtxListElement list_element : list ) {
209                         for( Map.Entry<String,String> entry : list_element.child_elements.entrySet() ) {
210                                 if( sort_fields == null ) {
211                                         ctx.setAttribute(ctx_list_str + '[' + i + ']', entry.getValue());
212                                 }
213                                 else {
214                                         ctx.setAttribute(ctx_list_str + '[' + i + "]." + entry.getKey(), entry.getValue());
215                                 }
216                         }
217                         i++;
218                 }
219                 // Reset list length (removed by ctxBulkErase above)
220                 ctx.setAttribute(ctx_list_str+LENGTH, Integer.toString(listSz));
221         }
222
223     /**
224      * generates a UUID and writes it to context memory
225      * @param parameters - ctx-destination is a required parameter
226      * @param ctx Reference to context memory
227      * @throws SvcLogicException thrown if a UUID cannot be generated or if ctx-destination is missing or null
228      */
229         public void generateUUID( Map<String, String> parameters, SvcLogicContext ctx )  throws SvcLogicException {
230                 checkParameters(parameters, new String[]{"ctx-destination"}, LOG);
231                 ctx.setAttribute(parameters.get("ctx-destination"), UUID.randomUUID().toString() );
232         }
233
234         /**
235          * Provides substring functionality to Directed Graphs.
236          * <p>
237          * Calls either String.substring(String beginIndex) or
238          * String.substring(String beginInded, String endIndex) if the end-index
239          * is present or not.
240          * @param parameters HashMap<String,String> of parameters passed by the DG to this function
241          * <table border="1">
242          *      <thead><th>parameter</th><th>Mandatory/Optional</th><th>description</th></thead>
243          *      <tbody>
244          *              <tr><td>string</td><td>Mandatory</td><td>String to perform substring on</td></tr>
245          *              <tr><td>result</td><td>Mandatory</td><td>Key in context memory to populate the resulting string in</td></tr>
246          *              <tr><td>begin-index</td><td>Mandatory</td><td>Beginning index to pass to Java substring function</td></tr>
247          *              <tr><td>end-index</td><td>Optional</td><td>Ending index to pass to Java substring function. If not included, String.substring(begin) will be called.</td></tr>
248          *      </tbody>
249          * </table>
250          * @param ctx Reference to context memory
251          * @throws SvcLogicException
252          * @since 8.0.1
253          * @see SliPluginUtils#substring(Map, SvcLogicContext)
254          */
255         @Deprecated
256         public void substring( Map<String, String> parameters, SvcLogicContext ctx ) throws SvcLogicException {
257                 try {
258                         checkParameters( parameters, new String[]{"string","begin-index","result"}, LOG );
259                         final String string = parameters.get("string");
260                         final String result = parameters.get("result");
261                         final String begin = parameters.get("begin-index");
262                         final String end = parameters.get("end-index");
263
264                         if( StringUtils.isEmpty(end) ) {
265                                 ctx.setAttribute( result, string.substring(Integer.parseInt(begin)) );
266                         }
267                         else {
268                                 ctx.setAttribute( result, string.substring(Integer.parseInt(begin), Integer.parseInt(end)) );
269                         }
270                 }
271                 catch( Exception e ) {
272                         throw new SvcLogicException( "An error occurred while the Directed Graph was performing a substring", e );
273                 }
274         }
275
276
277
278         // ========== PUBLIC STATIC UTILITY FUNCTIONS ==========
279
280         /**
281          * Throws an exception and writes an error to the log file if a required
282          * parameters is not found in the parametersMap.
283          * <p>
284          * Use at the beginning of functions that can be called by Directed Graphs
285          * and can take parameters to verify that all parameters have been provided
286          * by the Directed Graph.
287          * @param parametersMap parameters Map passed to this node
288          * @param requiredParams Array of parameters required by the calling function
289          * @param log Reference to Logger to log to
290          * @throws SvcLogicException if a String in the requiredParams array is
291          * not a key in parametersMap.
292          * @since 1.0
293          */
294         public static final void checkParameters(Map<String, String> parametersMap, String[] requiredParams, Logger log) throws SvcLogicException {
295         if( requiredParams == null || requiredParams.length < 1){
296             log.debug("required parameters was empty, exiting early.");
297             return;
298         }
299         if (parametersMap == null || parametersMap.keySet().isEmpty()){
300             String errorMessage = "This method requires the parameters [" +   StringUtils.join(requiredParams,",") + "], but no parameters were passed in.";
301             log.error(errorMessage);
302             throw new SvcLogicException(errorMessage);
303         }
304
305         for (String param : requiredParams) {
306             if (!parametersMap.containsKey(param)) {
307                 String errorMessage = "Required parameter \"" + param + "\" was not found in parameter list.";
308                 log.error(errorMessage);
309                 log.error("Total list of required parameters is [" + StringUtils.join(requiredParams, ",") + "].");
310                 throw new SvcLogicException(errorMessage);
311             }
312         }
313         }
314
315         /**
316          * Removes all key-value pairs with keys that begin with pfx
317          * @param ctx Reference to context memory
318          * @param pfx Prefix of key-value pairs to remove
319          * @since 1.0
320          */
321         public static final void ctxBulkErase( SvcLogicContext ctx, String pfx ) {
322                 ArrayList<String> Keys = new ArrayList<>(ctx.getAttributeKeySet());
323                 for( String key : Keys ) {
324                         if( key.startsWith( pfx ) ) {
325                                 ctx.setAttribute( pfx + key.substring(pfx.length()) , null);
326                         }
327                 }
328         }
329
330         /**
331          * Copies all context memory key-value pairs that start with src_pfx to
332          * the keys that start with dest_pfx + suffix, where suffix is the result
333          * of {@code key.substring(src_pfx.length())}.
334          * <p>
335          * Does NOT guarantee removal of all keys at the destination before
336          * copying, but will overwrite any destination keys that have a
337          * corresponding source key. Use {@link #ctxBulkErase(SvcLogicContext, String) ctxBulkErase}
338          * before copy to erase destination root before copying from source.
339          * @param ctx Reference to context memory.
340          * @param src_pfx Prefix of the keys to copy values from.
341          * @param dest_pfx Prefix of the keys to copy values to.
342          * @since 1.0
343          */
344         public static final void ctxBulkCopy( SvcLogicContext ctx, String src_pfx, String dest_pfx ) {
345                 // Remove trailing period from dest_pfx
346                 if( dest_pfx.charAt(dest_pfx.length()-1) == '.' ) {
347                         dest_pfx = dest_pfx.substring(0,dest_pfx.length()-1);
348                 }
349
350                 // For each context key that begins with src_pfx, set the value of the
351                 // key dest_pfx + the suffix of the key to the key's value
352                 ArrayList<String> Keys = new ArrayList<>(ctx.getAttributeKeySet());
353                 for( String key : Keys ) {
354                         if( key.startsWith(src_pfx) ) {
355                                 // Get suffix (no leading period)
356                                 String suffix = key.substring(src_pfx.length());
357                                 if( suffix.charAt(0) == '.') {
358                                         suffix = suffix.substring(1);
359                                 }
360
361                                 // Set destination's value to key's value
362                                 ctx.setAttribute(dest_pfx + '.' + suffix, ctx.getAttribute(key));
363                         }
364                 }
365         }
366
367         /**
368          * Creates and returns a {@code Map<String, String>} that is a subset of
369          * context memory where all keys begin with the prefix.
370          * @param ctx Reference to context memory.
371          * @param prefix Returned map's keys should all begin with this value.
372          * @return A {@code Map<String, String>} containing all the key-value pairs
373          * in ctx whose key begins with prefix.
374          */
375         public static final Map<String, String> ctxGetBeginsWith( SvcLogicContext ctx, String prefix ) {
376                 Map<String, String> prefixMap = new HashMap<>();
377
378                 for( String key : ctx.getAttributeKeySet() ) {
379                         if( key.startsWith(prefix) ) {
380                                 prefixMap.put( key, ctx.getAttribute(key) );
381                         }
382                 }
383
384                 return prefixMap;
385         }
386
387         /**
388          * Returns true if key's value in context memory is "" or if it doesn't
389          * exist in context memory.
390          * @param ctx Reference to context memory.
391          * @param key Key to search for.
392          * @return true if key's value in context memory is "" or if it doesn't
393          * exist in context memory.
394          * @since 1.0
395          */
396         public static final boolean ctxKeyEmpty( SvcLogicContext ctx, String key ) {
397                 String value = ctx.getAttribute(key);
398                 return value == null || value.isEmpty();
399         }
400
401         /**
402          * Adds all key-value pairs in the entries Map to context memory.
403          * @param ctx Reference to context memory. Value's {@code toString()}
404          * function is used to add it.
405          * @param entries {@code Map<String, ?>} of key-value pairs to add to
406          * context memory. Value's {@code toString()} function is used to add it.
407          * @return Reference to context memory to be used for function chaining.
408          */
409         public static final SvcLogicContext ctxPutAll( SvcLogicContext ctx, Map<String, ?> entries ) {
410                 for( Map.Entry<String, ?> entry : entries.entrySet() ) {
411                         ctxSetAttribute( ctx, entry.getKey(), entry.getValue() );
412                         //ctx.setAttribute(entry.getKey(), entry.getValue().toString());
413                 }
414
415                 return ctx;
416         }
417
418         /**
419          * Sets a key in context memory to the output of object's toString(). The
420          * key is deleted from context memory if object is null.
421          * @param ctx Reference to context memory.
422          * @param key Key to set.
423          * @param object Object whose toString() will be the value set
424          */
425         public static final void ctxSetAttribute( SvcLogicContext ctx, String key, Object object ) {
426                 if( object == null ) {
427                         ctx.setAttribute(key, null);
428                 }
429                 else {
430                         ctx.setAttribute(key, object.toString());
431                 }
432         }
433
434         /**
435          * Sets a key in context memory to the output of object's toString().
436          * <p>
437          * The key is deleted from context memory if object is null. The key and
438          * value set in context memory are logged to the Logger at the provided
439          * logLevel level.
440          * @param <O> Any Java object
441          * @param ctx Reference to context memory.
442          * @param key Key to set.
443          * @param obj Object whose toString() will be the value set
444          * @param LOG Logger to log to
445          * @param logLevel level to log at in Logger
446          */
447         public static final <O extends Object> void ctxSetAttribute( SvcLogicContext ctx, String key, O obj, Logger LOG, LogLevel logLevel ) {
448                 String value = Objects.toString( obj, null );
449                 ctx.setAttribute( key, value );
450                 if( logLevelIsEnabled(LOG, logLevel ) ) {
451                         if( value == null ) {
452                                 logMessageAtLevel( LOG, logLevel, "Deleting " + key );
453                         }
454                         else {
455                                 logMessageAtLevel( LOG, logLevel, "Setting " + key + " = " + value );
456                         }
457                 }
458         }
459
460         /**
461          * Utility function used to get an array's length from context memory.
462          * Will return 0 if key doesn't exist in context memory or isn't numeric.
463          * <p>
464          * Use to obtain a context memory array length without having to worry
465          * about throwing a NumberFormatException.
466          * @param ctx Reference to context memory
467          * @param key Key in context memory whose value is the array's length. If
468          * the key doesn't end in "_length", then "_length is appended.
469          * @param log Reference to Logger to log to
470          * @return The array length or 0 if the key is not found in context memory.
471          * @since 1.0
472          */
473         public static final int getArrayLength( SvcLogicContext ctx, String key ) {
474                 return getArrayLength(ctx, key, null, null, null);
475         }
476
477         /**
478          * Utility function used to get an array's length from context memory.
479          * Will return 0 if key doesn't exist in context memory or isn't numeric
480          * and print the provided log message to the configured log file.
481          * <p>
482          * Use to obtain a context memory array length without having to worry
483          * about throwing a NumberFormatException.
484          * @param ctx Reference to context memory.
485          * @param key Key in context memory whose value is the array's length. If
486          * the key doesn't end in "_length", then "_length is appended.
487          * @param log Reference to Logger to log to. Doesn't log if null.
488          * @param logLevel Logging level to log the message at if the context
489          * memory key isn't found. Doesn't log if null.
490          * @param log_message Message to log if the context memory key isn't found.
491          * Doesn't log if null.
492          * @return The array length or 0 if the key is not found in context memory.
493          * @since 1.0
494          */
495         public static final int getArrayLength( SvcLogicContext ctx, String key, Logger log, LogLevel logLevel, String log_message ) {
496                 String ctxKey = key.endsWith(LENGTH) ? key : key + LENGTH;
497                 try {
498                         return Integer.parseInt(ctx.getAttribute(ctxKey));
499                 }
500                 catch( NumberFormatException e ) {
501                         if( log != null && logLevel != null && log_message != null ) {
502                                 switch( logLevel ) {
503                                         case TRACE:
504                                                 log.trace(log_message);
505                                                 break;
506                                         case DEBUG:
507                                                 log.debug(log_message);
508                                                 break;
509                                         case INFO:
510                                                 log.info(log_message);
511                                                 break;
512                                         case WARN:
513                                                 log.warn(log_message);
514                                                 break;
515                                         case ERROR:
516                                                 log.error(log_message);
517                                                 break;
518                                 }
519                         }
520                 }
521
522                 return 0;
523         }
524
525         /**
526          * Prints sorted context memory key-value pairs to the log file at the log
527          * level. Returns immediately if the log level isn't enabled.
528          * <p>
529          * O(n log(n)) time where n = size of context memory
530          * @param ctx Reference to context memory
531          * @param log Reference to Logger to log to
532          * @param logLevel Logging level to log the context memory key-value pairs
533          * at.
534          * @since 1.0
535          */
536         public static final void logContextMemory( SvcLogicContext ctx, Logger log, LogLevel logLevel ) {
537                 logLevelIsEnabled( log, logLevel );
538
539                 // Print sorted context memory key-value pairs to the log
540                 ArrayList<String> keys = new ArrayList<>(ctx.getAttributeKeySet());
541                 Collections.sort(keys);
542                 for( String key : keys ) {
543                         logMessageAtLevel( log, logLevel, key + " = " + ctx.getAttribute(key) );
544                 }
545         }
546
547
548
549         // ========== PRIVATE FUNCTIONS ==========
550
551         // TODO: javadoc
552         /**
553          *
554          * @param parameters
555          * @param log
556          * @param loglevel
557          * @since 7.0.1
558          */
559         public static final void logExecuteNodeParameters( Map<String,String> parameters, Logger log, LogLevel loglevel ) {
560                 logLevelIsEnabled( log, loglevel );
561
562                 for( Map.Entry<String,String> param : parameters.entrySet() ) {
563                         logMessageAtLevel( log, loglevel, "PARAM: " + param.getKey() + " = " + param.getValue() );
564                 }
565         }
566
567         // TODO: javadoc
568         /**
569          * Returns true if the loglevel is enabled. Otherwise, returns false.
570          * @param log Reference to logger
571          * @param loglevel Log level to check if enabled
572          * @return True if the loglevel is enabled. Otherwise, false
573          * @since 7.0.1
574          */
575         private static final boolean logLevelIsEnabled( Logger log, LogLevel loglevel ) {
576                 // Return immediately if logging level isn't enabled
577                 switch( loglevel ) {
578                         case TRACE:
579                                 if( log.isTraceEnabled() ) { return true; }
580                                 return false;
581                         case DEBUG:
582                                 if( log.isDebugEnabled() ) { return true; }
583                                 return false;
584                         case INFO:
585                                 if( log.isInfoEnabled() ) { return true; }
586                                 return false;
587                         case WARN:
588                                 if( log.isWarnEnabled() ) { return true; }
589                                 return false;
590                         case ERROR:
591                                 if( log.isErrorEnabled() ) { return true; }
592                                 return false;
593                         default:
594                                 throw new IllegalArgumentException("Unknown LogLevel: " + loglevel.toString());
595                 }
596         }
597
598         // TODO: javadoc
599         /**
600          *
601          * @param log
602          * @param loglevel
603          * @param msg
604          * @since 7.0.1
605          */
606         private static final void logMessageAtLevel( Logger log, LogLevel loglevel, String msg ) {
607                 switch( loglevel ) {
608                         case TRACE:
609                                 log.trace(msg);
610                                 return;
611                         case DEBUG:
612                                 log.debug(msg);
613                                 return;
614                         case INFO:
615                                 log.info(msg);
616                                 return;
617                         case WARN:
618                                 log.warn(msg);
619                                 return;
620                         case ERROR:
621                                 log.error(msg);
622                                 return;
623                 }
624         }
625
626
627
628         // ========== LOCAL CLASSES ==========
629
630         private class SortableCtxListElement implements Comparable<SortableCtxListElement> {
631                 HashMap<String,String> child_elements = new HashMap<>();
632                 String[] sort_fields;
633
634                 public SortableCtxListElement( SvcLogicContext ctx, String root, String[] sort_fields ) {
635                         this.sort_fields = sort_fields;
636
637                         for( String key : ctx.getAttributeKeySet() ) {
638                                 if( key.startsWith(root) ) {
639                                         if( key.length() == root.length() ) {
640                                                 child_elements.put("", ctx.getAttribute(key));
641                                                 break;
642                                         }
643                                         else {
644                                                 child_elements.put(key.substring(root.length()+1), ctx.getAttribute(key));
645                                         }
646                                 }
647                         }
648                 }
649
650                 @Override
651                 public int compareTo(SortableCtxListElement arg0) {
652                         if( sort_fields == null ) {
653                                 return this.child_elements.get("").compareTo(arg0.child_elements.get(""));
654                         }
655
656                         for( String field : this.sort_fields ) {
657                                 int result = this.child_elements.get(field).compareTo(arg0.child_elements.get(field));
658                                 if( result != 0 ) {
659                                         return result;
660                                 }
661                         }
662
663                         return 0;
664                 }
665
666                 @Override
667                 public boolean equals(Object object) {
668                         if (this == object) {
669                                 return true;
670                         }
671                         if (!(object instanceof SortableCtxListElement)) {
672                                 return false;
673                         }
674                         if (!super.equals(object)) {
675                                 return false;
676                         }
677
678                         SortableCtxListElement that = (SortableCtxListElement) object;
679
680                         if (child_elements != null ? !child_elements.equals(that.child_elements)
681                                         : that.child_elements != null) {
682                                 return false;
683                         }
684                         // Probably incorrect - comparing Object[] arrays with Arrays.equals
685                         if (!Arrays.equals(sort_fields, that.sort_fields)) {
686                                 return false;
687                         }
688
689                         return true;
690                 }
691
692                 @Override
693                 public int hashCode() {
694                         int result = super.hashCode();
695                         result = 31 * result + (child_elements != null ? child_elements.hashCode() : 0);
696                         result = 31 * result + Arrays.hashCode(sort_fields);
697                         return result;
698                 }
699         }
700
701         /**
702      * Creates a file that contains the content of context memory.
703      * @param parameters - must contain the parameter filename
704      * @param ctx Reference to context memory
705      * @throws SvcLogicException thrown if file cannot be created or if parameters are missing
706      */
707         public static void printContext(Map<String, String> parameters, SvcLogicContext ctx) throws SvcLogicException {
708                 if (parameters == null || parameters.isEmpty()) {
709                         throw new SvcLogicException("no parameters passed");
710                 }
711
712                 checkParameters(parameters, new String[]{"filename"}, LOG);
713
714                 String fileName = parameters.get("filename");
715
716
717                 try (FileOutputStream fstr = new FileOutputStream(new File(fileName));
718                          PrintStream pstr = new PrintStream(fstr, true);)
719                 {
720                         pstr.println("#######################################");
721                         for (String attr : ctx.getAttributeKeySet()) {
722                                 pstr.println(attr + " = " + ctx.getAttribute(attr));
723                         }
724                 } catch (Exception e) {
725                         throw new SvcLogicException("Cannot write context to file " + fileName, e);
726                 }
727
728
729         }
730
731          /**
732      * Checks context memory for a set of required parameters
733      * Every parameter aside from prefix will be treated as mandatory
734      * @param parameters HashMap<String,String> of parameters passed by the DG to this function
735      * <table border="1">
736      *  <thead><th>parameter</th><th>Mandatory/Optional</th><th>description</th></thead>
737      *  <tbody>
738      *      <tr><td>prefix</td><td>Optional</td><td>the prefix will be added to each parameter</td></tr>
739      *  </tbody>
740      * </table>
741      * @param ctx Reference to context memory
742      * @throws SvcLogicException
743      * @since 11.0.2
744      */
745         public static void requiredParameters(Map<String, String> parameters, SvcLogicContext ctx) throws SvcLogicException {
746                 if (parameters == null || parameters.keySet().isEmpty()) {
747             String errorMessage = "requiredParameters should not be called if the parameters hashmap is null or empty!";
748             LOG.error(errorMessage);
749             throw new SvcLogicException(errorMessage);
750                 }
751                 String prefixValue = null;
752                 String prefix = "prefix";
753                 if(parameters.containsKey(prefix)){
754                     prefixValue = parameters.get(prefix);
755                     parameters.remove(prefix);
756                 }
757                 checkParameters(prefixValue, ctx.getAttributeKeySet(), parameters.keySet(), LOG);
758         }
759
760     private static void checkParameters(String prefixValue, Set<String> ctx, Set<String> parameters, Logger log) throws SvcLogicException {
761         for (String param : parameters) {
762             if (prefixValue != null) {
763                 param = prefixValue + param;
764             }
765             if (!ctx.contains(param)) {
766                 String errorMessage = "This method requires the parameters [" + StringUtils.join(parameters, ",")
767                         + "], but " + param + " was not passed in.";
768                 log.error(errorMessage);
769                 throw new SvcLogicException(errorMessage);
770             }
771         }
772     }
773
774     /**
775      *  is in a different DG invocation just before/after we call NCS and set the state to InProgress
776      */
777     /**
778     * setTime write the current date time to a string located at outputPath
779     * @param parameters - requires outputPath to not be null
780     * @param ctx Reference to context memory
781     * @throws SvcLogicException if a required parameter is missing an exception is thrown
782     */
783     public static void setTime(Map<String, String> parameters, SvcLogicContext ctx) throws SvcLogicException
784     {
785         checkParameters(parameters, new String[] { "outputPath" }, LOG);
786
787         // Set the DateFormat
788         // "2015-03-16T12:18:35.138Z"
789         SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
790
791         // Parse the date
792         String ctxVariable = parameters.get("outputPath");
793         try {
794             String dateTime = format.format(new Date());
795             ctx.setAttribute(ctxVariable, dateTime);
796         } catch (Exception ex) {
797             throw new SvcLogicException("problem with setTime", ex);
798         }
799     }
800
801     /**
802     * jsonStringToCtx takes a json string stored as a single property in context memory and breaks it into individual properties
803     * @param parameters - requires source, outputPath and isEscaped to not be null.
804     * @param ctx Reference to context memory
805     * @throws SvcLogicException if a required parameter is missing an exception is thrown
806     */
807     public static void jsonStringToCtx(Map<String, String> parameters, SvcLogicContext ctx) throws SvcLogicException
808     {
809         checkParameters(parameters, new String[] { "source","outputPath","isEscaped" }, LOG);
810         try {
811             String source = ctx.getAttribute(parameters.get("source"));
812             if("true".equals(parameters.get("isEscaped"))){
813                 source = StringEscapeUtils.unescapeJson(source);
814             }
815             writeJsonToCtx(source, ctx,parameters.get("outputPath"));
816         } catch (Exception ex) {
817             throw new SvcLogicException("problem with jsonStringToCtx", ex);
818         }
819     }
820
821     protected static void writeJsonToCtx(String resp, SvcLogicContext ctx, String prefix){
822         JsonParser jp = new JsonParser();
823         JsonElement element = jp.parse(resp);
824         writeJsonObject(element.getAsJsonObject(), ctx, prefix + ".");
825     }
826
827     protected static void writeJsonObject(JsonObject obj, SvcLogicContext ctx, String root) {
828         for (Entry<String, JsonElement> entry : obj.entrySet()) {
829             if (entry.getValue().isJsonObject()) {
830                 writeJsonObject(entry.getValue().getAsJsonObject(), ctx, root + entry.getKey() + ".");
831             } else if (entry.getValue().isJsonArray()) {
832                 JsonArray array = entry.getValue().getAsJsonArray();
833                 ctx.setAttribute(root + entry.getKey() + LENGTH, String.valueOf(array.size()));
834                 Integer arrayIdx = 0;
835                 for (JsonElement element : array) {
836                     if (element.isJsonObject()) {
837                         writeJsonObject(element.getAsJsonObject(), ctx, root + entry.getKey() + "[" + arrayIdx + "].");
838                     } else if (element.isJsonPrimitive()) {
839                         ctx.setAttribute(root + entry.getKey() + "[" + arrayIdx + "]", element.getAsString());
840                     }
841                     arrayIdx++;
842                 }
843             } else {
844                 //Handles when a JSON obj is nested within a JSON obj
845                 if(!root.endsWith(".")){
846                     root = root + ".";
847                 }
848                 ctx.setAttribute(root + entry.getKey(), entry.getValue().getAsString());
849             }
850         }
851     }
852
853     /**
854      * getAttributeValue takes a ctx memory path as a string, gets the value stored at this path and set this value in context memory at
855      * outputPath
856      * @param parameters - requires source and outputPath
857      * @param ctx Reference to context memory
858      * @throws SvcLogicException if a required parameter is missing an exception is thrown
859      */
860      public static void getAttributeValue(Map<String, String> parameters, SvcLogicContext ctx) throws SvcLogicException {
861          checkParameters(parameters, new String[] { "source", "outputPath" }, LOG);
862          String source = ctx.getAttribute(parameters.get("source"));
863          ctx.setAttribute(parameters.get("outputPath"), source);
864      }
865
866         /**
867          * ctxListContains provides a way to see if a context memory list contains a key value
868          * @param parameters - requires list, keyName, keyValue, outputPath to all not be null.
869          * @param ctx        Reference to context memory
870          * @throws SvcLogicException if a required parameter is missing an exception is thrown
871          */
872         public static String ctxListContains(Map<String, String> parameters, SvcLogicContext ctx) throws SvcLogicException {
873                 checkParameters(parameters, new String[]{"list", "keyName", "keyValue"}, LOG);
874
875                 try {
876                         String ctxList = parameters.get("list");
877                         ctxList = (ctxList.endsWith(LENGTH)) ? ctxList : ctxList + LENGTH;
878                         int listLength = getArrayLength(ctx, ctxList);
879
880                         if (listLength == 0) {
881                                 LOG.debug("List is not in context memory");
882                                 return "false";
883                         } else {
884                                 Set<String> keys = new HashSet<String>();
885
886                                 String listPrefix = ctxList.substring(0, ctxList.lastIndexOf("_")) + "[";
887                                 String listSuffix = "]." + parameters.get("keyName");
888
889                                 for (int i = 0; i < listLength; i++) {
890                                         String keyLocation = listPrefix + i + listSuffix;
891                                         keys.add(ctx.getAttribute(keyLocation));
892                                 }
893
894                                 if (keys.contains(parameters.get("keyValue"))) {
895                                         LOG.debug("List " + parameters.get("list") + " contains " + parameters.get("keyValue"));
896                                         return "true";
897                                 } else {
898                                         LOG.debug("List " + parameters.get("list") + " do not contains " + parameters.get("keyValue"));
899                                         return "false";
900                                 }
901                         }
902                 } catch (Exception ex) {
903                         throw new SvcLogicException("ctxListContains failed", ex);
904                 }
905         }
906
907         /**
908          * set properties in context memory for a container </br>
909          * parameters with a null or empty key or value are ignored </br>
910          * required parameter root - root + "." + parameters.key
911          * is the key to set the value too value in context memory </br>
912          * optional parameter valueRoot - if set: valueRoot + "." + parameters.value
913          * is the key to get the value from context memory
914          *
915          * @param parameters - root (required), valueRoot (optional), properties names and values to be set
916          * @param ctx        Reference to context memory
917          * @return success or failure of operation
918          */
919         public static String setPropertiesForRoot(Map<String, String> parameters, SvcLogicContext ctx) {
920                 LOG.debug("Execute Node \"setPropertiesForRoot\"");
921                 try {
922                         checkParameters(parameters, new String[]{"root"}, LOG);
923                 } catch (Exception ex) {
924                         return "failure";
925                 }
926
927                 String root = parameters.get("root");
928
929                 if (StringUtils.isEmpty(root)) {
930                         return "failure";
931                 }
932
933                 // set context memory to the the properties passed with root as prefix
934                 setParameterValuesToRoot(parameters, ctx, root);
935
936                 return "success";
937         }
938
939         private static boolean setParameterValuesToRoot(Map<String, String> parameters, SvcLogicContext ctx, String root) {
940                 boolean changeFlag = false;
941                 String valueRoot = parameters.get("valueRoot");
942
943                 for (Map.Entry<String, String> entry : parameters.entrySet()) {
944                         // ignore if it's the root parameter
945                         if (!entry.getKey().equals("root")) {
946                                 String keyToBeSet = root + "." + entry.getKey();
947                                 String valueToBeSet = "";
948
949                                 if (StringUtils.isEmpty(valueRoot)) {
950                                         valueToBeSet = entry.getValue();
951                                 } else {
952                                         valueToBeSet = ctx.getAttribute(valueRoot + "." + entry.getValue());
953                                 }
954
955                                 LOG.debug("Setting context memory: " + keyToBeSet + " = " + valueToBeSet);
956
957                                 if (!StringUtils.isEmpty(entry.getKey()) && !StringUtils.isEmpty(valueToBeSet)) {
958                                         ctxSetAttribute(ctx, keyToBeSet, valueToBeSet);
959                                         changeFlag = true;
960                                 }
961                         }
962                 }
963
964                 return changeFlag;
965         }
966
967         /**
968          * takes container list and set the properties with the value provided </br>
969          * parameters with a null or empty key or value are ignored </br>
970          * required parameters </br>
971          * prefixKey + "." + parameters.key is the key to set the value too value in context memory </br>
972          * prefixKey + "[index]." + keyName is the key of the entry in the list in context memory </br>
973          * keyValue is the value of the key of the list entry in context memory (must be actual value)</br>
974          * optional parameter valuePrefixKey - if set: valuePrefixKey + "." + parameters.value
975          * is the key to get the value from context memory
976          *
977          * @param parameters </br>
978          *                                       - prefixKey e.g "service-data.universal-cpe-ft.l2-switch-interfaces" </br>
979          *                   - keyName e.g "name" </br>
980          *                   - keyValue e.g "WAN1" (must be actual value and not use the prefixKey as root) </br>
981          *                   - valuePrefixKey (optional) e.g "input.universal-cpe-ft.l2-switch-interfaces[1] </br>
982          *                   - properties to be set, values for the properties </br>
983          * @param ctx        reference to context memory
984          * @return success or failure of operation
985          */
986         public static String setPropertiesForList(Map<String, String> parameters, SvcLogicContext ctx) {
987                 LOG.debug("Execute Node \"setPropertiesForList\"");
988                 try {
989                         checkParameters(parameters, new String[]{"prefixKey", "keyName", "keyValue"}, LOG);
990                 } catch (Exception e) {
991                         LOG.error("a required parameter is missing");
992                         return "failure";
993                 }
994
995                 String prefixKey = parameters.get("prefixKey");
996                 String keyName = parameters.get("keyName");
997                 String keyValue = parameters.get("keyValue");
998
999                 if (StringUtils.isEmpty(keyName) || StringUtils.isEmpty(keyValue) || StringUtils.isEmpty(prefixKey)) {
1000                         LOG.error("a required parameters value is empty or null");
1001                         return "failure";
1002                 }
1003
1004                 int listLength = getArrayLength(ctx, prefixKey);
1005
1006                 Map<String, String> containParams = new HashMap<>();
1007                 containParams.put("list", prefixKey);
1008                 containParams.put("keyName", keyName);
1009                 containParams.put("keyValue", keyValue);
1010
1011                 String valuePrefixKey = parameters.get("valuePrefixKey");
1012
1013                 try {
1014                         // create new list in context memory
1015                         if (listLength == 0) {
1016                                 // since there's no length found make sure there's no current data at prefixKey in context memory
1017                                 Map<String, String> map = ctxGetBeginsWith(ctx, prefixKey);
1018
1019                                 if (map.size() == 0) {
1020                                         setNewEntryInList(parameters, ctx, keyName, keyValue, prefixKey, valuePrefixKey, listLength);
1021                                 } else {
1022                                         LOG.error("there was no length for the list parameter set in context memory "
1023                                                         + "but " + map.size() + " entries were found in context memory "
1024                                                         + "where the key begins with: " + prefixKey);
1025
1026                                         return "failure";
1027                                 }
1028                         } else if (ctxListContains(containParams, ctx) == "false") {
1029                                 setNewEntryInList(parameters, ctx, keyName, keyValue, prefixKey, valuePrefixKey, listLength);
1030                         } else if (ctxListContains(containParams, ctx) == "true") {
1031                                 // else update the context memory with the properties passed in at the right index level
1032                                 String listPrefix = prefixKey + "[";
1033                                 String listSuffix = "].";
1034
1035                                 for (int i = 0; i < listLength; i++) {
1036                                         String listRootWithIndex = listPrefix + i + listSuffix;
1037                                         String listKeyName = listRootWithIndex + keyName;
1038                                         String valueAtListIndexKey = ctx.getAttribute(listKeyName);
1039
1040                                         if (valueAtListIndexKey.equals(keyValue)) {
1041                                                 setParametersToCtxList(parameters, ctx, listRootWithIndex, valuePrefixKey);
1042                                         }
1043                                 }
1044                         }
1045                 } catch (SvcLogicException e) {
1046                         LOG.error("Call to ctxListContains failed: " + e.getMessage());
1047
1048                         return "failure";
1049                 }
1050
1051                 return "success";
1052         }
1053
1054         private static void setNewEntryInList(Map<String, String> parameters, SvcLogicContext ctx, String keyName,
1055                                                                                   String keyValue, String prefixKey, String valuePrefixKey, int listLength) {
1056                 String prefixKeyWithIndex = prefixKey + "[" + listLength + "].";
1057                 String listKeyName = prefixKeyWithIndex + keyName;
1058
1059                 // set list key
1060                 LOG.debug("Setting context memory, new list entry with key:  " + listKeyName + " = " + keyValue);
1061                 ctxSetAttribute(ctx, listKeyName, keyValue);
1062
1063                 // set the other parameters
1064                 setParametersToCtxList(parameters, ctx, prefixKeyWithIndex, valuePrefixKey);
1065
1066                 // set length of list
1067                 String ListLengthKeyName = prefixKey + LENGTH;
1068
1069                 ctxSetAttribute(ctx, prefixKey + LENGTH, listLength + 1);
1070                 LOG.debug("Updated _length: " + prefixKey + "_length is now " + ctx.getAttribute(ListLengthKeyName));
1071         }
1072
1073         /**
1074          * helper function to set the parameter properties for list at the provided prefix key
1075          *
1076          * @param parameters
1077          * @param ctx
1078          * @param prefixKey
1079          * @return true if any new context memory was added and or modified
1080          */
1081         private static boolean setParametersToCtxList(Map<String, String> parameters, SvcLogicContext ctx, String prefixKeyWithIndex,
1082                                                                                                   String valuePrefixKey) {
1083                 boolean changeFlag = false;
1084
1085                 for (Map.Entry<String, String> entry : parameters.entrySet()) {
1086                         if (! (entry.getKey().equals("prefixKey") ||
1087                                         entry.getKey().equals("keyName") ||
1088                                         entry.getKey().equals("keyValue")) ||
1089                                         entry.getKey().equals("valuePrefixKey")) {
1090
1091                                 String keyToBeSet = prefixKeyWithIndex + entry.getKey();
1092                                 String valueToBeSet = "";
1093
1094                                 if (StringUtils.isEmpty(valuePrefixKey)) {
1095                                         valueToBeSet = entry.getValue();
1096                                 } else {
1097                                         valueToBeSet = ctx.getAttribute(valuePrefixKey + "." + entry.getValue());
1098                                 }
1099
1100                                 LOG.debug("Setting context memory: " + keyToBeSet + " = " + valueToBeSet);
1101
1102                                 // only set context memory if properties key and value are not empty or null
1103                                 if (!StringUtils.isEmpty(entry.getKey()) && !StringUtils.isEmpty(valueToBeSet)) {
1104                                         ctxSetAttribute(ctx, keyToBeSet, valueToBeSet);
1105                                         changeFlag = true;
1106                                 }
1107                         }
1108                 }
1109
1110                 return changeFlag;
1111         }
1112
1113     public static String containsKey(Map<String, String> parameters, SvcLogicContext ctx) throws SvcLogicException {
1114         String key = parameters.get("key");
1115         Boolean keyFound = ctx.getAttributeKeySet().contains(key);
1116         if (keyFound) {
1117             return "true";
1118         }
1119         return "false";
1120     }
1121
1122 }