View Javadoc
1   /**
2    * Copyright 2014 Internet2
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  /*
17   * @author mchyzer
18   * $Id: ChangeLogEntry.java,v 1.10 2009-11-03 14:18:59 shilen Exp $
19   */
20  package edu.internet2.middleware.grouper.changeLog;
21  
22  import java.sql.Timestamp;
23  import java.text.SimpleDateFormat;
24  import java.util.Collection;
25  import java.util.LinkedHashSet;
26  import java.util.List;
27  import java.util.Set;
28  
29  import net.sf.json.JSONArray;
30  import net.sf.json.JSONObject;
31  import net.sf.json.JSONSerializer;
32  
33  import org.apache.commons.lang.StringUtils;
34  import org.apache.commons.lang.builder.EqualsBuilder;
35  import org.apache.commons.lang.builder.HashCodeBuilder;
36  
37  import edu.internet2.middleware.grouper.GrouperAPI;
38  import edu.internet2.middleware.grouper.cfg.GrouperConfig;
39  import edu.internet2.middleware.grouper.hibernate.GrouperContext;
40  import edu.internet2.middleware.grouper.hibernate.HibernateSession;
41  import edu.internet2.middleware.grouper.internal.util.GrouperUuid;
42  import edu.internet2.middleware.grouper.misc.GrouperDAOFactory;
43  import edu.internet2.middleware.grouper.util.GrouperUtil;
44  
45  
46  /**
47   * <pre>
48   * represents a user change log record.  This is a change to a record in the DB (insert/update/delete).
49   * 
50   * note: if this object is headed for the temp table, then the getters in the composite key will not be null, will be empty.
51   * this is a hibernate constraint
52   * 
53   * </pre>
54   */
55  @SuppressWarnings("serial")
56  public class ChangeLogEntry extends GrouperAPI {
57    
58    /** column */
59    public static final String COLUMN_ID = "id";
60  
61    /** column */
62    public static final String COLUMN_CHANGE_LOG_TYPE_ID = "change_log_type_id";
63  
64    /** column */
65    public static final String COLUMN_CONTEXT_ID = "context_id";
66  
67    /** column */
68    public static final String COLUMN_CREATED_ON = "created_on";
69  
70    /** column */
71    public static final String COLUMN_STRING01 = "string01";
72  
73    /** column */
74    public static final String COLUMN_STRING02 = "string02";
75  
76    /** column */
77    public static final String COLUMN_STRING03 = "string03";
78  
79    /** column */
80    public static final String COLUMN_STRING04 = "string04";
81  
82    /** column */
83    public static final String COLUMN_STRING05 = "string05";
84  
85    /** column */
86    public static final String COLUMN_STRING06 = "string06";
87  
88    /** column */
89    public static final String COLUMN_STRING07 = "string07";
90  
91    /** column */
92    public static final String COLUMN_STRING08 = "string08";
93  
94    /** column */
95    public static final String COLUMN_STRING09 = "string09";
96  
97    /** column */
98    public static final String COLUMN_STRING10 = "string10";
99  
100   /** column */
101   public static final String COLUMN_STRING11 = "string11";
102 
103   /** column */
104   public static final String COLUMN_STRING12 = "string12";
105 
106   /** column */
107   public static final String COLUMN_SEQUENCE_NUMBER = "sequence_number";
108 
109   /**
110    * 
111    * @param changeLogTypeIdentifier
112    * @param theObject
113    * @param dbVersion
114    * @param labelNamesAndValues
115    * @param objectPropertyNames
116    * @param changeLogPropertyNames
117    */
118   public static void saveTempUpdates(ChangeLogTypeIdentifier changeLogTypeIdentifier, 
119       Object theObject, Object dbVersion,
120       List<String> labelNamesAndValues,
121       List<String> objectPropertyNames,
122       List<String> changeLogPropertyNames) {
123     
124     if (GrouperUtil.length(objectPropertyNames) != GrouperUtil.length(changeLogPropertyNames)) {
125       throw new RuntimeException("Object property names length if not equal " +
126       		"to changeLog property names length: " + GrouperUtil.length(objectPropertyNames) 
127       		+ " != " +  GrouperUtil.length(changeLogPropertyNames));
128     }
129     
130     //since this is an update, why would either be null???
131     if (theObject == null || dbVersion == null) {
132       throw new RuntimeException("theObject and dbVersion cannot be null: "
133           + (theObject == null) + ", " + (dbVersion == null));
134     }
135     
136     int index = 0;
137     for (String objectPropertyName: objectPropertyNames) {
138       
139       //get the values of the property
140       Object propertyValue = GrouperUtil.propertyValue(theObject, objectPropertyName);
141       Object dbPropertyValue = GrouperUtil.propertyValue(dbVersion, objectPropertyName);
142       
143       //see if different
144       if (!GrouperUtil.equals(propertyValue, dbPropertyValue)) {
145         
146         String[] labelsAndValuesArray = new String[labelNamesAndValues.size() + 6];
147         
148         for (int i=0;i<labelNamesAndValues.size();i++) {
149           labelsAndValuesArray[i] = labelNamesAndValues.get(i);
150         }
151         //last two cols are twhats different, and the old value
152         labelsAndValuesArray[labelsAndValuesArray.length-6] = "propertyChanged";
153         labelsAndValuesArray[labelsAndValuesArray.length-5] = changeLogPropertyNames.get(index);
154         labelsAndValuesArray[labelsAndValuesArray.length-4] = "propertyOldValue";
155         labelsAndValuesArray[labelsAndValuesArray.length-3] = GrouperUtil.stringValue(dbPropertyValue);
156         labelsAndValuesArray[labelsAndValuesArray.length-2] = "propertyNewValue";
157         labelsAndValuesArray[labelsAndValuesArray.length-1] = GrouperUtil.stringValue(propertyValue);
158         
159         //if so, add a change log entry to temp table
160         new ChangeLogEntry(true, changeLogTypeIdentifier, labelsAndValuesArray).save();
161 
162       }
163       
164       index++;
165     }
166     
167   }
168   
169   /**
170    * 
171    * @see java.lang.Object#equals(java.lang.Object)
172    */
173   @Override
174   public boolean equals(Object obj) {
175     
176     if (!(obj instanceof ChangeLogEntry)) {
177       return false;
178     }
179     
180     ChangeLogEntryinternet2/middleware/grouper/changeLog/ChangeLogEntry.html#ChangeLogEntry">ChangeLogEntry objChangeLogEntry = (ChangeLogEntry)obj;
181     
182     //if there is a sequence, then it is a ChangeLogEntryEntity
183     if (this.sequenceNumber != null || objChangeLogEntry.sequenceNumber != null) {
184       return new EqualsBuilder().append(this.sequenceNumber, objChangeLogEntry.sequenceNumber).isEquals();
185     } 
186     //else it is a ChangeLogEntryTemp
187     return new EqualsBuilder()
188       .append(this.changeLogTypeId, objChangeLogEntry.changeLogTypeId)
189       .append(this.contextId, objChangeLogEntry.contextId)
190       .append(this.createdOnDb, objChangeLogEntry.createdOnDb)
191       .append(this.string01, objChangeLogEntry.string01)
192       .append(this.string02, objChangeLogEntry.string02)
193       .append(this.string03, objChangeLogEntry.string03)
194       .append(this.string04, objChangeLogEntry.string04)
195       .append(this.string05, objChangeLogEntry.string05)
196       .append(this.string06, objChangeLogEntry.string06)
197       .append(this.string07, objChangeLogEntry.string07)
198       .append(this.string08, objChangeLogEntry.string08)
199       .append(this.string09, objChangeLogEntry.string09)
200       .append(this.string10, objChangeLogEntry.string10)
201       .append(this.string11, objChangeLogEntry.string11)
202       .append(this.string12, objChangeLogEntry.string12)
203       .isEquals();
204   }
205 
206   /**
207    * 
208    * @see java.lang.Object#hashCode()
209    */
210   @Override
211   public int hashCode() {
212     
213     //if there is a sequence, then it is a ChangeLogEntryEntity
214     if (this.sequenceNumber != null) {
215       return new HashCodeBuilder().append(this.sequenceNumber).toHashCode();
216     } 
217     //else it is a ChangeLogEntryTemp
218     return new HashCodeBuilder()
219       .append(this.changeLogTypeId)
220       .append(this.contextId)
221       .append(this.createdOnDb)
222       .append(this.string01)
223       .append(this.string02)
224       .append(this.string03)
225       .append(this.string04)
226       .append(this.string05)
227       .append(this.string06)
228       .append(this.string07)
229       .append(this.string08)
230       .append(this.string09)
231       .append(this.string10)
232       .append(this.string11)
233       .append(this.string12)
234       .toHashCode();
235   }
236 
237   /** entity name for change log temp */
238   public static final String CHANGE_LOG_ENTRY_TEMP_ENTITY_NAME = "ChangeLogEntryTemp";
239   
240   /** entity name for change log */
241   public static final String CHANGE_LOG_ENTRY_ENTITY_NAME = "ChangeLogEntryEntity";
242   
243   //*****  START GENERATED WITH GenerateFieldConstants.java *****//
244 
245   /** constant for field name for: changeLogTypeId */
246   public static final String FIELD_CHANGE_LOG_TYPE_ID = "changeLogTypeId";
247 
248   /** constant for field name for: id */
249   public static final String FIELD_ID = "id";
250   
251   /** constant for field name for: contextId */
252   public static final String FIELD_CONTEXT_ID = "contextId";
253 
254   /** constant for field name for: createdOnDb */
255   public static final String FIELD_CREATED_ON_DB = "createdOnDb";
256 
257   /** constant for field name for: string01 */
258   public static final String FIELD_STRING01 = "string01";
259 
260   /** constant for field name for: string02 */
261   public static final String FIELD_STRING02 = "string02";
262 
263   /** constant for field name for: string03 */
264   public static final String FIELD_STRING03 = "string03";
265 
266   /** constant for field name for: string04 */
267   public static final String FIELD_STRING04 = "string04";
268 
269   /** constant for field name for: string05 */
270   public static final String FIELD_STRING05 = "string05";
271 
272   /** constant for field name for: string06 */
273   public static final String FIELD_STRING06 = "string06";
274 
275   /** constant for field name for: string07 */
276   public static final String FIELD_STRING07 = "string07";
277 
278   /** constant for field name for: string08 */
279   public static final String FIELD_STRING08 = "string08";
280 
281   /** constant for field name for: string09 */
282   public static final String FIELD_STRING09 = "string09";
283 
284   /** constant for field name for: string10 */
285   public static final String FIELD_STRING10 = "string10";
286 
287   /** constant for field name for: string11 */
288   public static final String FIELD_STRING11 = "string11";
289 
290   /** constant for field name for: string12 */
291   public static final String FIELD_STRING12 = "string12";
292 
293   //*****  END GENERATED WITH GenerateFieldConstants.java *****//
294 
295   /** to string deep fields */
296   private static final Set<String> TO_STRING_DEEP_FIELDS = GrouperUtil.toSet(
297       FIELD_CHANGE_LOG_TYPE_ID, FIELD_CONTEXT_ID, FIELD_CREATED_ON_DB, FIELD_HIBERNATE_VERSION_NUMBER, 
298       FIELD_STRING01, FIELD_STRING02, FIELD_STRING03, FIELD_STRING04, 
299       FIELD_STRING05, FIELD_STRING06, FIELD_STRING07, FIELD_STRING08, 
300       FIELD_STRING09, FIELD_STRING10, FIELD_STRING11, FIELD_STRING12);
301 
302   /**
303    * get the changeLog type, it better be there
304    * @return the changeLog type
305    */
306   public ChangeLogType getChangeLogType() {
307     return ChangeLogTypeFinder.find(this.changeLogTypeId, true);
308   }
309   
310   /**
311    * 
312    * @param extended if all fields should be printed
313    * @return the report
314    */
315   public String toStringReport(boolean extended) {
316     StringBuilder result = new StringBuilder();
317     ChangeLogType changeLogType = this.getChangeLogType();
318     Timestamp createdOn = this.getCreatedOn();
319     
320     SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
321     String createdOnString = simpleDateFormat.format(createdOn);
322     
323     result.append(createdOnString).append(" ").append(StringUtils.rightPad(changeLogType.getChangeLogCategory(), 12))
324       .append(" - ").append(StringUtils.rightPad(changeLogType.getActionName(), 20)).append("\n");
325     
326     for (String label: changeLogType.labels()) {
327       
328       //see if there is data
329       String fieldName = changeLogType.retrieveChangeLogEntryFieldForLabel(label);
330       Object value = GrouperUtil.fieldValue(this, fieldName);
331       String valueString = GrouperUtil.stringValue(value);
332       
333       //abbreviate if not extended
334       if (!extended) {
335         valueString = StringUtils.abbreviate(valueString, 50);
336       }
337       
338       if (!StringUtils.isBlank(valueString)) {
339         
340         result.append("  ").append(StringUtils.rightPad(StringUtils.capitalize(label) + ":", 20)).append(value).append("\n");
341         
342       }
343     }
344     
345     return result.toString();
346   }
347   
348   /**
349    * construct
350    */
351   public ChangeLogEntry() {
352     
353   }
354 
355   /**
356    * save this object (insert) to the temp table if configured to do so, and set context id and other things
357    * save (insert) this object
358    */
359   public void save() {
360     if (isTempObject() || GrouperConfig.retrieveConfig().propertyValueBoolean("changeLog.enabled", true)) {
361       
362       GrouperDAOFactory.getFactory().getChangeLogEntry().save(this);
363     }
364   }
365 
366   /**
367    * update this object to the temp or entity table if configured to do so, and set context id and other things
368    * save (insert) this object
369    */
370   public void update() {
371     if (isTempObject() || GrouperConfig.retrieveConfig().propertyValueBoolean("changeLog.enabled", true)) {
372       
373       GrouperDAOFactory.getFactory().getChangeLogEntry().update(this);
374     }
375   }
376   
377   /**
378    * delete the change log entry from either the temp table or the entity table
379    */
380   public void delete() {
381     GrouperDAOFactory.getFactory().getChangeLogEntry().delete(this);
382   }
383   
384   /**
385    * construct, assign an id
386    * @param tempObject1 if this is a temp object, or a normal change log entry
387    * @param changeLogTypeIdentifier points to changeLog type
388    * @param labelNamesAndValues alternate label name and value
389    */
390   public ChangeLogEntry(boolean tempObject1, ChangeLogTypeIdentifier changeLogTypeIdentifier, 
391       String... labelNamesAndValues) {
392     
393     this.tempObject = tempObject1;
394 
395     ChangeLogType changeLogType = ChangeLogTypeFinder.find(changeLogTypeIdentifier.getChangeLogCategory(),
396         changeLogTypeIdentifier.getActionName(), true);
397     
398     this.changeLogTypeId = changeLogType.getId();
399     
400     int labelNamesAndValuesLength = GrouperUtil.length(labelNamesAndValues);
401     
402     if (labelNamesAndValuesLength % 2 != 0) {
403       throw new RuntimeException("labelNamesAndValuesLength must be divisible by 2: " 
404           + labelNamesAndValuesLength);
405     }
406     
407     for (int i=0;i<labelNamesAndValuesLength;i+=2) {
408       String label = labelNamesAndValues[i];
409       String value = labelNamesAndValues[i+1];
410 
411       assignStringValue(changeLogType, label, value);
412     }
413   }
414 
415   /**
416    * reutrn the value based on friendly label.  ChangeLogEntry keeps data in 
417    * string01, string02, etc.  But it is more useful when querying by group id.
418    * so pass in the fiendly label from the ChangeLogType, and it will look up which field,
419    * and return the value of that field
420    * @param changeLogLabel is probably from ChangeLogLabels constants
421    * @return the value
422    */
423   public String retrieveValueForLabel(ChangeLogLabel changeLogLabel) {
424     return retrieveValueForLabel(changeLogLabel.name());
425   }
426 
427   /**
428    * reutrn the value based on friendly label.  ChangeLogEntry keeps data in 
429    * string01, string02, etc.  But it is more useful when querying by group id.
430    * so pass in the fiendly label from the ChangeLogType, and it will look up which field,
431    * and return the value of that field
432    * @param label
433    * @return the value
434    */
435   public String retrieveValueForLabel(String label) {
436     ChangeLogType changeLogType = this.getChangeLogType();
437     String fieldName = changeLogType.retrieveChangeLogEntryFieldForLabel(label);
438     return (String)this.fieldValue(fieldName);
439   }
440   
441   /**
442    * @param changeLogType
443    * @param label
444    * @param value
445    */
446   public void assignStringValue(ChangeLogType changeLogType, String label, String value) {
447     if (StringUtils.equals(label, changeLogType.getLabelString01())) {
448       this.string01 = value;
449     } else if (StringUtils.equals(label, changeLogType.getLabelString02())) {
450       this.string02 = value;
451     } else if (StringUtils.equals(label, changeLogType.getLabelString03())) {
452       this.string03 = value;
453     } else if (StringUtils.equals(label, changeLogType.getLabelString04())) {
454       this.string04 = value;
455     } else if (StringUtils.equals(label, changeLogType.getLabelString05())) {
456       this.string05 = value;
457     } else if (StringUtils.equals(label, changeLogType.getLabelString06())) {
458       this.string06 = value;
459     } else if (StringUtils.equals(label, changeLogType.getLabelString07())) {
460       this.string07 = value;
461     } else if (StringUtils.equals(label, changeLogType.getLabelString08())) {
462       this.string08 = value;
463     } else if (StringUtils.equals(label, changeLogType.getLabelString09())) {
464       this.string09 = value;
465     } else if (StringUtils.equals(label, changeLogType.getLabelString10())) {
466       this.string10 = value;
467     } else if (StringUtils.equals(label, changeLogType.getLabelString11())) {
468       this.string11 = value;
469     } else if (StringUtils.equals(label, changeLogType.getLabelString12())) {
470       this.string12 = value;
471     } else {
472       throw new RuntimeException("Cant find string label: " + label 
473           + " in changeLog type: " + changeLogType.getChangeLogCategory() + " - " + changeLogType.getActionName());
474     }
475   }
476   
477   /** name of the grouper changeLog entry table in the db */
478   public static final String TABLE_GROUPER_CHANGE_LOG_ENTRY = "grouper_change_log_entry";
479 
480   /** name of the grouper changeLog entry temp table in the db, where records go first before being moved to the real table */
481   public static final String TABLE_GROUPER_CHANGE_LOG_ENTRY_TEMP = "grouper_change_log_entry_temp";
482 
483   /** foreign key to the type of changeLog entry this is */
484   private String changeLogTypeId;
485 
486   /**
487    * uuid for temp object
488    */
489   private String id;
490   
491   /**
492    * context id ties multiple db changes  
493    */
494   private String contextId;
495 
496   /**
497    * misc field 1
498    */
499   private String string01;
500   
501   /**
502    * misc field 2
503    */
504   private String string02;
505   
506   /**
507    * misc field 3
508    */
509   private String string03;
510   
511   /**
512    * misc field 4
513    */
514   private String string04;
515   
516   /**
517    * misc field 5
518    */
519   private String string05;
520   
521   /**
522    * misc field 6
523    */
524   private String string06;
525   
526   /**
527    * misc field 7
528    */
529   private String string07;
530   
531   /**
532    * misc field 8
533    */
534   private String string08;
535 
536   /**
537    * misc field 9
538    */
539   private String string09;
540 
541   /**
542    * misc field 10
543    */
544   private String string10;
545 
546   /**
547    * misc field 11
548    */
549   private String string11;
550 
551   /**
552    * misc field 12
553    */
554   private String string12;
555 
556   /**
557    * when this record was created 
558    */
559   private Long createdOnDb;
560 
561   /**
562    * optional sequence for ordering
563    */
564   private Long sequenceNumber;
565 
566   /**
567    * optional sequence for ordering
568    * @return sequence number
569    */
570   public Long getSequenceNumber() {
571     return this.sequenceNumber;
572   }
573 
574   /**
575    * optional sequence for ordering
576    * @param sequenceNumber1
577    */
578   public void setSequenceNumber(Long sequenceNumber1) {
579     this.sequenceNumber = sequenceNumber1;
580   }
581   
582   /**
583    * uuid for temp object
584    * @return uuid for temp object
585    */
586   public String getId() {
587     return this.id;
588   }
589   
590   /**
591    * set uuid for temp object
592    * @param id
593    */
594   public void setId(String id) {
595     this.id = id;
596   }
597 
598   /**
599    * foreign key to the type of changeLog entry this is
600    * @return the changeLog type id
601    */
602   public String getChangeLogTypeId() {
603     return this.changeLogTypeId;
604   }
605 
606   /**
607    * foreign key to the type of changeLog entry this is
608    * @param changeLogTypeId1
609    */
610   public void setChangeLogTypeId(String changeLogTypeId1) {
611     this.changeLogTypeId = changeLogTypeId1;
612   }
613 
614   /** if this object is bound for the temp table, or regular table */
615   private boolean tempObject = true;
616   
617   /**
618    * context id ties multiple db changes
619    * @return id
620    */
621   public String getContextId() {
622     
623     return tempConvert(this.contextId);
624   }
625 
626   /**
627    * context id ties multiple db changes
628    * @param contextId1
629    */
630   public void setContextId(String contextId1) {
631     this.contextId = contextId1;
632   }
633 
634   /**
635    * misc field 1
636    * @return field
637    */
638   public String getString01() {
639     return tempConvert(this.string01);
640   }
641 
642   /**
643    * @param theString
644    * @return the string without empty strings
645    */
646   private String tempConvert(String theString) {
647     //why would be have empty string?
648     if ("".equals(theString)) {
649       return null;
650     }
651     return theString;
652   }
653 
654   /**
655    * misc field 1
656    * @param string01a
657    */
658   public void setString01(String string01a) {
659     this.string01 = string01a;
660   }
661 
662   /**
663    * misc field 2
664    * @return field
665    */
666   public String getString02() {
667     return tempConvert(this.string02);
668   }
669 
670   /**
671    * misc field 2
672    * @param string02a
673    */
674   public void setString02(String string02a) {
675     this.string02 = string02a;
676   }
677 
678   /**
679    * misc field 3
680    * @return field
681    */
682   public String getString03() {
683     return tempConvert(this.string03);
684   }
685 
686   /**
687    * misc field 3
688    * @param string03a
689    */
690   public void setString03(String string03a) {
691     this.string03 = string03a;
692   }
693 
694   /**
695    * misc field 4
696    * @return field
697    */
698   public String getString04() {
699     return tempConvert(this.string04);
700   }
701 
702   /**
703    * misc field 4
704    * @param string04a
705    */
706   public void setString04(String string04a) {
707     this.string04 = string04a;
708   }
709 
710   /**
711    * misc field 5
712    * @return field
713    */
714   public String getString05() {
715     return tempConvert(this.string05);
716   }
717 
718   /**
719    * misc field 5
720    * @param string05a
721    */
722   public void setString05(String string05a) {
723     this.string05 = string05a;
724   }
725 
726   /**
727    * misc field 6
728    * @return field
729    */
730   public String getString06() {
731     return tempConvert(this.string06);
732   }
733 
734   /**
735    * misc field 6
736    * @param string06a
737    */
738   public void setString06(String string06a) {
739     this.string06 = string06a;
740   }
741 
742   /**
743    * misc field 7
744    * @return field
745    */
746   public String getString07() {
747     return tempConvert(this.string07);
748   }
749 
750   /**
751    * misc field 7
752    * @param string07a
753    */
754   public void setString07(String string07a) {
755     this.string07 = string07a;
756   }
757 
758   /**
759    * misc field 8
760    * @return field
761    */
762   public String getString08() {
763     return tempConvert(this.string08);
764   }
765 
766   /**
767    * misc field 8
768    * @param string08a
769    */
770   public void setString08(String string08a) {
771     this.string08 = string08a;
772   }
773 
774   /**
775    * when created
776    * @return timestamp
777    */
778   public Timestamp getCreatedOn() {
779     return this.createdOnDb == null ? null : new Timestamp(this.createdOnDb / 1000);
780   }
781 
782   /**
783    * when created, microseconds since 1970
784    * @return timestamp
785    */
786   public Long getCreatedOnDb() {
787     return this.createdOnDb;
788   }
789 
790   /**
791    * when created
792    * @param createdOn1
793    */
794   public void setCreatedOn(Timestamp createdOn1) {
795     this.createdOnDb = createdOn1 == null ? null : (createdOn1.getTime() * 1000);
796   }
797 
798   /**
799    * make sure this object will fit in the DB
800    */
801   public void truncate() {
802     this.changeLogTypeId = GrouperUtil.truncateAscii(this.changeLogTypeId, 128);
803     this.contextId = GrouperUtil.truncateAscii(this.contextId, 128);
804     this.string01 = GrouperUtil.truncateAscii(this.string01, 4000);
805     this.string02 = GrouperUtil.truncateAscii(this.string02, 4000);
806     this.string03 = GrouperUtil.truncateAscii(this.string03, 4000);
807     this.string04 = GrouperUtil.truncateAscii(this.string04, 4000);
808     this.string05 = GrouperUtil.truncateAscii(this.string05, 4000);
809     this.string06 = GrouperUtil.truncateAscii(this.string06, 4000);
810     this.string07 = GrouperUtil.truncateAscii(this.string07, 4000);
811     this.string08 = GrouperUtil.truncateAscii(this.string08, 4000);
812     this.string09 = GrouperUtil.truncateAscii(this.string09, 4000);
813     this.string10 = GrouperUtil.truncateAscii(this.string10, 4000);
814     this.string11 = GrouperUtil.truncateAscii(this.string11, 4000);
815     this.string12 = GrouperUtil.truncateAscii(this.string12, 4000);
816   }
817 
818   /**
819    * @see edu.internet2.middleware.grouper.GrouperAPI#clone()
820    */
821   @Override
822   public GrouperAPI clone() {
823     throw new RuntimeException("not implemented");
824   }
825 
826   /**
827    * 
828    * @see edu.internet2.middleware.grouper.GrouperAPI#onPreSave(edu.internet2.middleware.grouper.hibernate.HibernateSession)
829    */
830   @Override
831   public void onPreSave(HibernateSession hibernateSession) {
832     super.onPreSave(hibernateSession);
833     this.truncate();
834     if (this.tempObject) {
835       if (this.createdOnDb == null) {
836         this.createdOnDb = ChangeLogId.changeLogId();
837       }
838       if (StringUtils.isBlank(this.contextId)) {
839         this.contextId = GrouperContext.retrieveContextId(true);
840       }
841       
842       //assign id if not there
843       if (StringUtils.isBlank(this.getId())) {
844         this.setId(GrouperUuid.getUuid());
845       }
846     }
847     if (!this.tempObject) {
848       if (this.sequenceNumber == null) {
849         this.sequenceNumber = nextSequenceNumber();
850       }
851     }
852   }
853 
854   /**
855    * max sequence number of the entry table
856    */
857   private static Long nextSequenceNumber = null;
858   
859   /**
860    * find the max sequence number in the entry table
861    * @return the max sequence number (plus one)
862    */
863   private synchronized static long nextSequenceNumber() {
864     if (nextSequenceNumber == null) {
865       nextSequenceNumber = maxSequenceNumber(true);
866       if (nextSequenceNumber == null) {
867         nextSequenceNumber = 0l;
868       }
869     }
870     //we can cache this in memory since we are the only process that is inserting into the table
871     return ++nextSequenceNumber;
872   }
873   
874   /**
875    * clear the sequence number so the next call to nextSequenceNumber() will requery the max.
876    */
877   public static void clearNextSequenceNumberCache() {
878     nextSequenceNumber = null;
879   }
880 
881   /**
882    * max sequence number in DB
883    * @param considerConsumers if the consumers should be considered
884    * @return the max sequence number (or null if not there)
885    */
886   public static Long maxSequenceNumber(boolean considerConsumers) {
887     Long result = HibernateSession.byHqlStatic().createQuery(
888         "select max(sequenceNumber) from ChangeLogEntryEntity").uniqueResult(Long.class);
889     if (considerConsumers) {
890       Long resultConsumer = HibernateSession.byHqlStatic().createQuery(
891         "select max(lastSequenceProcessed) from ChangeLogConsumer").uniqueResult(Long.class);
892       
893       //if we have a consumer
894       if (resultConsumer != null) {
895   
896         //if results
897         if (result != null) {
898           if (result > resultConsumer) {
899             return result;
900           }
901         }
902         //return consumer if better than result
903         return resultConsumer;
904       }
905     }
906     return result;
907   }
908   
909   /**
910    * 
911    * @see edu.internet2.middleware.grouper.GrouperAPI#onPreUpdate(edu.internet2.middleware.grouper.hibernate.HibernateSession)
912    */
913   @Override
914   public void onPreUpdate(HibernateSession hibernateSession) {
915     super.onPreUpdate(hibernateSession);
916     this.truncate();
917   }
918 
919   /**
920    * when created, microseconds since 1970
921    * @param createdOn1
922    */
923   public void setCreatedOnDb(Long createdOn1) {
924     this.createdOnDb = createdOn1;
925   }
926 
927   /**
928    * the string repre
929    * @return string 
930    */
931   public String toStringDeep() {
932     return GrouperUtil.toStringFields(this, TO_STRING_DEEP_FIELDS);
933   }
934 
935   /**
936    * 
937    * @return the string 09
938    */
939   public String getString09() {
940     return tempConvert(this.string09);
941   }
942 
943   /**
944    * set the string 09
945    * @param theString09
946    */
947   public void setString09(String theString09) {
948     this.string09 = theString09;
949   }
950 
951   /**
952    * get string 10
953    * @return string 10
954    */
955   public String getString10() {
956     return tempConvert(this.string10);
957   }
958 
959   /**
960    * set string 10
961    * @param theString10
962    */
963   public void setString10(String theString10) {
964     this.string10 = theString10;
965   }
966 
967   /**
968    * 
969    * @return string 11
970    */
971   public String getString11() {
972     return tempConvert(this.string11);
973   }
974 
975   /**
976    * set string 11
977    * @param _string11
978    */
979   public void setString11(String _string11) {
980     this.string11 = _string11;
981   }
982 
983   /**
984    * get string 12
985    * @return string 12
986    */
987   public String getString12() {
988     return tempConvert(this.string12);
989   }
990 
991   /**
992    * set string 12
993    * @param _string12
994    */
995   public void setString12(String _string12) {
996     this.string12 = _string12;
997   }
998 
999   /**
1000    * if this is a temp object, destined for the temp table
1001    * @return temp object
1002    */
1003   public boolean isTempObject() {
1004     return this.tempObject;
1005   }
1006 
1007   /**
1008    * if this is a temp object headed for the temp table
1009    * @param tempObject1
1010    */
1011   public void setTempObject(boolean tempObject1) {
1012     this.tempObject = tempObject1;
1013   }
1014 
1015   /**
1016    * see if this identifier matches the change log type by category and action
1017    * @param changeLogTypeIdentifier
1018    * @return true if matches
1019    */
1020   public boolean equalsCategoryAndAction(ChangeLogTypeIdentifier changeLogTypeIdentifier) {
1021     return this.getChangeLogType() != null 
1022       && this.getChangeLogType().equalsCategoryAndAction(changeLogTypeIdentifier);
1023   }
1024 
1025   /**
1026    * convert json to a collection (generally of size one) of change log entries
1027    * @param json
1028    * @return the change log entry
1029    */
1030   public static Collection<ChangeLogEntry> fromJsonToCollection(String json) {
1031     if (StringUtils.isBlank(json)) {
1032       return null;
1033     }
1034     JSONObject jsonObject = (JSONObject) JSONSerializer.toJSON( json );
1035     JSONArray jsonArray = jsonObject.getJSONArray("event");
1036     if (jsonArray == null) {
1037       return null;
1038     }
1039     if (jsonArray.size() == 0) {
1040       return null;
1041     }
1042     Set<ChangeLogEntry> result = new LinkedHashSet<ChangeLogEntry>();
1043     for (int i=0;i<jsonArray.size();i++) {
1044       JSONObject currentEvent = jsonArray.getJSONObject(i);
1045       ChangeLogEntryLog/ChangeLogEntry.html#ChangeLogEntry">ChangeLogEntry currentEntry = new ChangeLogEntry();
1046       currentEntry.fromJsonHelper(currentEvent);
1047       result.add(currentEntry);
1048     }
1049     return result;
1050   }
1051 
1052   /**
1053    * convert to one json object
1054    * @param jsonObject
1055    */
1056   public void fromJsonHelper(JSONObject jsonObject) {
1057     
1058     this.changeLogTypeId = jsonObject.getString("changeLogTypeId");
1059     if (jsonObject.containsKey("contextId")) {
1060       this.contextId = jsonObject.getString("contextId");
1061     }
1062     if (jsonObject.containsKey("createdOnDb")) {
1063       this.createdOnDb = jsonObject.getLong("createdOnDb");
1064     }
1065     if (jsonObject.containsKey("changeLogEntryId")) {
1066       this.id = jsonObject.getString("changeLogEntryId");
1067     }
1068     if (jsonObject.containsKey("sequenceNumber")) {
1069       this.sequenceNumber = jsonObject.getLong("sequenceNumber");
1070     }
1071     if (jsonObject.containsKey("changeLogTypeCategory")) {
1072       if (!StringUtils.equals(this.getChangeLogType().getChangeLogCategory(), jsonObject.getString("changeLogTypeCategory"))) {
1073         throw new RuntimeException("Wrong category: expecting: " 
1074             + this.getChangeLogType().getChangeLogCategory() + ", but was: " + jsonObject.getString("changeLogTypeCategory"));
1075       }
1076     }
1077     if (jsonObject.containsKey("changeLogTypeAction")) {
1078       if (!StringUtils.equals(this.getChangeLogType().getActionName(), jsonObject.getString("changeLogTypeAction"))) {
1079         throw new RuntimeException("Wrong action: expecting: " 
1080             + this.getChangeLogType().getActionName() + ", but was: " + jsonObject.getString("changeLogTypeAction"));
1081       }
1082     }
1083     ChangeLogType changeLogType = this.getChangeLogType();
1084     
1085     for (String key : (Set<String>)(Object)jsonObject.keySet()) {
1086       if (key.startsWith("field_")) {
1087 
1088         //get string value
1089         String value = jsonObject.getString(key);
1090         String fieldName = key.substring("field_".length());
1091         this.assignStringValue(changeLogType, fieldName, value);
1092 
1093       }
1094     }
1095   }
1096   
1097   /**
1098    * @param includeContainer true will include a container and an array of events
1099    * @return json
1100    */
1101   public String toJson(boolean includeContainer) {
1102     JSONObject event = new JSONObject();
1103     event.element("changeLogTypeId", this.changeLogTypeId);
1104     event.element("contextId", this.contextId);
1105     event.element("createdOnDb", this.createdOnDb);
1106     event.element("changeLogEntryId", this.id);
1107     event.element("sequenceNumber", this.sequenceNumber);
1108     event.element("changeLogTypeCategory", this.getChangeLogType().getChangeLogCategory());
1109     event.element("changeLogTypeAction", this.getChangeLogType().getActionName());
1110     this.toJsonHelper(event, this.getChangeLogType().getLabelString01());
1111     this.toJsonHelper(event, this.getChangeLogType().getLabelString02());
1112     this.toJsonHelper(event, this.getChangeLogType().getLabelString03());
1113     this.toJsonHelper(event, this.getChangeLogType().getLabelString04());
1114     this.toJsonHelper(event, this.getChangeLogType().getLabelString05());
1115     this.toJsonHelper(event, this.getChangeLogType().getLabelString06());
1116     this.toJsonHelper(event, this.getChangeLogType().getLabelString07());
1117     this.toJsonHelper(event, this.getChangeLogType().getLabelString08());
1118     this.toJsonHelper(event, this.getChangeLogType().getLabelString09());
1119     this.toJsonHelper(event, this.getChangeLogType().getLabelString10());
1120     this.toJsonHelper(event, this.getChangeLogType().getLabelString11());
1121     this.toJsonHelper(event, this.getChangeLogType().getLabelString12());
1122     if (includeContainer) {
1123       JSONObject container = new JSONObject();
1124       container.element("event", GrouperUtil.toSet(event));
1125       return container.toString();
1126     }
1127     return event.toString();
1128   }
1129   
1130   /**
1131    * do some reflection to get the change log label into a field
1132    * @param event
1133    * @param labelString
1134    */
1135   private void toJsonHelper(JSONObject event, String labelString) {
1136     if (StringUtils.isBlank(labelString)) {
1137       return;
1138     }
1139     //even if null, put it in there?  i guess not
1140     Object fieldValue = this.retrieveValueForLabel(labelString);
1141     if (fieldValue != null) {
1142       event.element("field_" + labelString, fieldValue);
1143     }
1144   }
1145   
1146 }