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}