View Javadoc
1   package edu.internet2.middleware.grouper.pspng;
2   
3   /*******************************************************************************
4    * Copyright 2015 Internet2
5    * 
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    * 
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   * 
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   ******************************************************************************/
18  
19  import java.util.*;
20  import java.util.logging.Level;
21  
22  import edu.internet2.middleware.grouper.changeLog.*;
23  import org.apache.commons.lang.builder.ToStringBuilder;
24  import org.joda.time.DateTime;
25  import org.slf4j.Logger;
26  import org.slf4j.LoggerFactory;
27  
28  import edu.internet2.middleware.subject.Subject;
29  
30  
31  /**
32   * This is a wrapper for the event-triggering data item used to drive PSP.
33   * Besides insulating PSP code from whether it is being triggered by Grouper's
34   * ChangeLog process or by a Messaging process, this wrapper also keeps track
35   * of the status and additional data associated with this trigger event.
36   * 
37   * For instance, LDAP-based provisioners store the ldap modifications that are
38   * necessary to process a trigger event within the events themselves. At the end
39   * of a batch of events, all the ldap modifications are pulled together into as
40   * few ldap modification operations as possible.
41   *  
42   * 
43   * @author Bert Bee-Lindgren
44   *
45   */
46  public class ProvisioningWorkItem {
47    final private static Logger LOG = LoggerFactory.getLogger(Provisioner.class);
48  
49    public enum WORK_ITEM_COMMAND {FULL_SYNC_GROUP, REMOVE_EXTRA_GROUPS, HANDLE_CHANGELOG_ENTRY};
50  
51    protected final WORK_ITEM_COMMAND command;
52    protected final ChangeLogEntry work;
53    protected String groupName;
54  
55    protected DateTime asOfDate;
56    protected Boolean success=null;
57    protected String status=null;
58    protected String statusMessage=null;
59  
60  
61    /**
62     * A place where information can be cached between the start/provision/finish 
63     * phases of a provisioning batch
64     */
65    protected Map<String, Object> provisioningData = new HashMap<String,Object>();
66    
67  
68    /**
69     * Create a work item that just holds the groupName without the overhead of  
70     * a changelog item.
71     * 
72     * This is used when Provisioner code is used in FullSync processes.
73     * @param command - What command does this Item represent
74     * @param group
75     */
76    protected ProvisioningWorkItem(WORK_ITEM_COMMAND command, GrouperGroupInfo group, DateTime asOfDate) {
77      this.command = command;
78      this.work = null;
79      this.asOfDate = asOfDate;
80  
81      if ( group != null )
82        this.groupName=group.getName();
83      else
84        this.groupName=null;
85    }
86    
87    
88    public ProvisioningWorkItem(ChangeLogEntry work) {
89      this.command = WORK_ITEM_COMMAND.HANDLE_CHANGELOG_ENTRY;
90      this.work=work;
91      this.asOfDate = new DateTime(work.getCreatedOn().getTime());
92    }
93  
94    public static ProvisioningWorkItem createForFullSync(GrouperGroupInfo grouperGroupInfo, DateTime asOfDate) {
95      return new ProvisioningWorkItem(WORK_ITEM_COMMAND.FULL_SYNC_GROUP, grouperGroupInfo, asOfDate);
96    }
97  
98    public static ProvisioningWorkItem createForGroupCleanup(DateTime asOfDate) {
99      return new ProvisioningWorkItem(WORK_ITEM_COMMAND.REMOVE_EXTRA_GROUPS, null, asOfDate);
100   }
101 
102 
103 
104   public ChangeLogEntry getChangelogEntry() {
105     return work;
106   }
107   
108   /**
109    * Update the status of a work item.
110    * 
111    * @param logLevel A java.util.logging Level (INFO, WARNING, SEVERE (aka error)). This is 
112    * used because slf4j 1.6.1 does not define such constants
113    * @param status
114    * @param statusMessageFormat
115    * @param statusMessageArgs
116    */
117   private void setStatus(Level logLevel, String status, String statusMessageFormat, Object... statusMessageArgs)
118   {
119     this.status=status;
120     this.statusMessage = String.format(statusMessageFormat, statusMessageArgs);
121     
122     String msg;
123     if ( success ) {
124       msg = "Work item handled: {}";
125     }
126     else {
127       msg = "Work item not handled; {}";
128     }
129       
130     // Convert the j.u.logging constants into slf4j log methods
131     if ( logLevel == Level.INFO ) {
132       LOG.info(msg, this);
133     }
134     else if ( logLevel == Level.WARNING ) {
135       LOG.warn(msg, this);
136     }
137     else if ( logLevel == Level.SEVERE ) {
138       LOG.error(msg, this);
139     }
140     else {
141       LOG.debug(msg, this);
142     }
143   }
144 
145   public void markAsSkippedAndWarn(String statusMessageFormat, Object... statusMessageArgs)
146   {
147     success = true;
148     setStatus(Level.WARNING, "done", statusMessageFormat, statusMessageArgs);
149   }
150 
151   public void markAsSkipped(String statusMessageFormat, Object... statusMessageArgs)
152   {
153     if ( success != null ) {
154       return;
155     }
156 
157     success = true;
158     setStatus(Level.FINE, "done", statusMessageFormat, statusMessageArgs);
159   }
160 
161   public void markAsSuccess(String statusMessageFormat, Object... statusMessageArgs)
162   {
163     success = true;
164     setStatus(Level.INFO, "done", statusMessageFormat, statusMessageArgs);
165   }
166   
167   public void markAsFailure(String statusMessageFormat, Object... statusMessageArgs)
168   {
169     success = false;
170     setStatus(Level.SEVERE, "failed", statusMessageFormat, statusMessageArgs);
171   }
172   
173   /** 
174    * Has this work item been processed?
175    * @return true when this item should not be processed further
176    */
177   public boolean hasBeenProcessed() {
178     // success ivar remains null until work item is processed and marked as success/failure
179     return success != null;
180   }
181   
182   
183   private String getGroupName() {
184     if ( groupName != null )
185       return groupName;
186     else if ( getChangelogEntry() == null )
187       return null;
188     
189     groupName = ChangelogHandlingConfig.getGroupName(getChangelogEntry());
190 
191     return groupName;
192   }
193   
194   public GrouperGroupInfo getGroupInfo(Provisioner provisioner) {
195 	  String groupName = getGroupName();
196 	  if ( groupName == null ) {
197 	    LOG.debug("Group name not found in work item: {}", this);
198             return null;
199           }
200 
201 	  GrouperGroupInfo result = provisioner.getGroupInfo(this);
202 
203 	  LOG.debug("WorkItem {} is related to Group: {}", this, result);
204 	  return result;
205   }
206 
207   /**
208    * Return the idIndex from the changelog entry
209    * @return
210    */
211   public Long getGroupIdIndex() {
212     return  ChangelogHandlingConfig.getGroupId(getChangelogEntry());
213   }
214 
215   public String getAttributeName() {
216     return ChangelogHandlingConfig.getAttributeName(getChangelogEntry());
217   }
218 
219   
220   private String getSubjectId() {
221     return ChangelogHandlingConfig.getSubjectId(getChangelogEntry());
222   }
223 
224   private String getSubjectSourceId() {
225     return ChangelogHandlingConfig.getSubjectSource(getChangelogEntry());
226   }
227   
228   public Subject getSubject(Provisioner provisioner) {
229     if ( getChangelogEntry() == null )
230       return null;
231     
232     final String subjectId = getSubjectId();
233     final String sourceId = getSubjectSourceId();
234 
235     if ( subjectId == null || sourceId == null )
236       return null;
237     
238     Subject subject = provisioner.getSubject(subjectId, sourceId);
239 
240     return subject;
241   }
242 
243   public boolean isSubjectUnresolvable(Provisioner provisioner) {
244     if ( getSubjectId()==null || getSubjectSourceId()==null ) {
245       // This work item does not contain a subject reference at all
246       return false;
247     }
248 
249     // We know that this work item is supposed to reference a subject.
250     // Can we find it??
251     if ( getSubject(provisioner)==null ) {
252       LOG.warn("Subject not found: {}@{}", getSubjectId(), getSubjectSourceId());
253       return true;
254     }
255 
256     // The subject was found. All is good.
257     return false;
258   }
259 
260 
261   public void putProvisioningData(String key, Object value)
262   {
263     provisioningData.put(key, value);
264   }
265   
266   public Object getProvisioningDataValue(String key)
267   {
268     return provisioningData.get(key);
269   }
270 
271   public void addValueToProvisioningData(String key, Object value) {
272     List<Object> valueArray = (List) provisioningData.get(key);
273     
274     if ( valueArray == null )
275     {
276       synchronized (provisioningData) {
277         // Double check to see if another thread might have created the array
278         if ( !provisioningData.containsKey(key) )
279           provisioningData.put(key, new ArrayList<Object>());
280         
281         valueArray = (List) provisioningData.get(key);
282       }
283     }
284     
285     valueArray.add(value);
286   }
287   
288   public List<Object> getProvisioningDataValues(String key) {
289     return (List<Object>) provisioningData.get(key);
290   }
291 
292   public boolean wasSuccessful() {
293     if ( success == null )
294       return false;
295     
296     return success;
297   }
298   
299   public boolean wasError() {
300     return !wasSuccessful();
301   }
302 
303   public String getStatusMessage() {
304     return statusMessage;
305   }
306   
307   @Override
308   public String toString() {
309     ToStringBuilder tsb = new ToStringBuilder(this)
310     .append("done", hasBeenProcessed() )
311     .append("successful", success)
312     .append("msg", statusMessage);
313     
314     if ( work == null )
315       tsb.append("cmd", command);
316     else {
317       String groupName = getGroupName();
318       String subjectId = getSubjectId();
319       String subjectSourceId = getSubjectSourceId();
320       
321       tsb.append("clog", String.format("clog #%d / %s", work.getSequenceNumber(), work.getChangeLogType()));
322       if ( groupName != null )
323         tsb.append("group", groupName);
324       if ( subjectId != null )
325         tsb.append("subject", String.format("%s@%s", subjectId, subjectSourceId));
326     }
327     return tsb.toString();
328   }
329 
330 
331   public String getMdcLabel() {
332     if ( work != null )
333       return String.format("%d/", work.getSequenceNumber());
334     else
335       return String.format("%s/", command);
336   }
337 
338 
339   /**
340    * Does the embedded changelog entry match the given type?
341    * @param type
342    * @return
343    */
344   public boolean matchesChangelogType(ChangeLogTypeBuiltin type) {
345     if ( getChangelogEntry() == null ) {
346       return false;
347     }
348 
349     return getChangelogEntry().getChangeLogType().equalsCategoryAndAction(type);
350   }
351 
352 
353   /**
354    * Does the embedded changelog entry have a type contained in the given collection of types?
355    * @param types
356    * @return
357    */
358   public boolean matchesChangelogType(Collection<ChangeLogTypeBuiltin> types) {
359     return ChangelogHandlingConfig.containsChangelogEntryType(types, getChangelogEntry());
360   }
361 }