001package com.identityworksllc.iiq.common.plugin;
002
003import com.identityworksllc.iiq.common.service.ServiceUtils;
004import org.apache.commons.logging.Log;
005import org.apache.commons.logging.LogFactory;
006import sailpoint.api.SailPointContext;
007import sailpoint.object.Attributes;
008import sailpoint.object.Filter;
009import sailpoint.object.QueryOptions;
010import sailpoint.object.Server;
011import sailpoint.object.ServiceDefinition;
012import sailpoint.server.ServicerUtil;
013import sailpoint.tools.GeneralException;
014import sailpoint.tools.Util;
015
016import java.util.List;
017import java.util.Map;
018import java.util.TreeMap;
019
020/**
021 * Some utilities to avoid boilerplate
022 */
023@SuppressWarnings("unused")
024public class CommonPluginUtils {
025        /**
026         * The executor passed to {@link #singleServerExecute(SailPointContext, ServiceDefinition, SingleServerExecute)}
027         * mainly so that we can extend it with those default methods and throw an exception
028         * from run(). None of the out of box functional interfaces have exception throwing.
029         *
030         * You can use this either as a lambda or by implementing this interface in
031         * your Service class.
032         */
033        @FunctionalInterface
034        public interface SingleServerExecute {
035                /**
036                 * Wraps the implementation in start/stop timeout tracking code, saving
037                 * those timestamps and the last run host on the ServiceDefinition after
038                 * completion. This may be used for recurring services that need to know
039                 * when they last ran (e.g., to do an incremental action).
040                 *
041                 * @param target The target ServiceDefinition to update
042                 * @return The wrapped functional interface object
043                 */
044                @Deprecated
045                default SingleServerExecute andSaveTimestamps(ServiceDefinition target) {
046                        return (context) -> {
047                                long lastStart = System.currentTimeMillis();
048                                try {
049                                        this.singleServerExecute(context);
050                                } finally {
051                                        ServiceUtils.storeTimestamps(context, target, lastStart);
052                                }
053                        };
054                }
055
056                /**
057                 * The main implementation of this service
058                 * @param context The sailpoint context for the current run
059                 * @throws GeneralException if any failures occur
060                 */
061                void singleServerExecute(SailPointContext context) throws GeneralException;
062        }
063        /**
064         * Log
065         */
066        private static final Log log = LogFactory.getLog(CommonPluginUtils.class);
067
068        /**
069         * Executes the task given by the functional {@link SingleServerExecute} if this
070         * server is the alphabetically lowest server on which this Service is allowed
071         * to run. Server names are sorted by the database using an 'order by' on query.
072         *
073         * This is intended to be used as the bulk of the execute() method of a Service
074         * class. You can either pass a lambda/closure to this method or implement the
075         * SingleServerExecute interface in your Service class (in which case you'd
076         * simply pass 'this').
077         *
078         * @param context The context
079         * @param self The current ServiceDefinition
080         * @param executor The executor to run
081         * @throws GeneralException if any failures occur
082         */
083        public static void singleServerExecute(SailPointContext context, ServiceDefinition self, SingleServerExecute executor) throws GeneralException {
084                Server target = null;
085                QueryOptions qo = new QueryOptions();
086                qo.addOrdering("name", true);
087                if (!Util.nullSafeCaseInsensitiveEq(self.getHosts(), "global")) {
088                        qo.addFilter(Filter.in("name", Util.csvToList(self.getHosts())));
089                }
090                List<Server> servers = context.getObjects(Server.class, qo);
091                for(Server s : servers) {
092                        if (!s.isInactive() && ServicerUtil.isServiceAllowedOnServer(context, self, s.getName())) {
093                                target = s;
094                                break;
095                        }
096                }
097                if (target == null) {
098                        // This would be VERY strange, since we are, in fact, running the service
099                        // right now, in this very code
100                        log.warn("There does not appear to be an active server allowed to run service " + self.getName());
101                }
102                String hostname = Util.getHostName();
103                if (target == null || target.getName().equals(hostname)) {
104                        executor.singleServerExecute(context);
105                }
106        }
107
108        /**
109         * Gets a map / JSON object indicating a status response
110         * @param message The message to associate with the response
111         * @return The response object
112         */
113        public static Map<String, String> toStatusResponse(String message) {
114                return toStatusResponse(message, null);
115        }
116        
117        /**
118         * Gets a map / JSON object indicating a status response with an optional error 
119         * @param message The message to associate with the response
120         * @param error The error to associate with the response
121         * @return The response object
122         */
123        public static Map<String, String> toStatusResponse(String message, Throwable error) {
124                Map<String, String> result = new TreeMap<>();
125                result.put("message", message);
126                if (error != null) {
127                        result.put("exception", error.toString());
128                        if (error.getCause() != null) {
129                                result.put("exceptionImmediateCause", error.getCause().toString());
130                        }
131                }
132                return result;
133        }
134
135        /**
136         * Utility classes should not be constructed
137         */
138        private CommonPluginUtils() {
139
140        }
141}