/* * Copyright (C) NetStruxr, Inc. All rights reserved. * * This software is published under the terms of the NetStruxr * Public Software License version 0.5, a copy of which has been * included with this distribution in the LICENSE.NPL file. */ package er.corebusinesslogic; import java.util.Collections; import java.util.Map; import java.util.WeakHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.webobjects.eoaccess.EOEntity; import com.webobjects.eocontrol.EOEditingContext; import com.webobjects.eocontrol.EOEnterpriseObject; import com.webobjects.foundation.NSNotification; import com.webobjects.foundation.NSNotificationCenter; import com.webobjects.foundation.NSSelector; import com.webobjects.foundation.NSTimestamp; import er.extensions.ERXExtensions; import er.extensions.eof.ERXGenericRecord; import er.extensions.foundation.ERXProperties; import er.extensions.foundation.ERXRetainer; import er.extensions.foundation.ERXSelectorUtilities; /** * EO subclass that has a timestamp with its creation date, the most recent modification, * and a log entry describing the change. * * @property er.corebusinesslogic.ERCStampedEnterpriseObject.touchReadOnlyEntities */ public abstract class ERCStampedEnterpriseObject extends ERXGenericRecord { /** * Do I need to update serialVersionUID? * See section 5.6 <cite>Type Changes Affecting Serialization</cite> on page 51 of the * <a href="http://java.sun.com/j2se/1.4/pdf/serial-spec.pdf">Java Object Serialization Spec</a> */ private static final long serialVersionUID = 1L; public interface Keys { public static final String CREATED = "created"; public static final String LAST_MODIFIED = "lastModified"; } public static abstract class ERCStampedEnterpriseObjectClazz extends ERXGenericRecord.ERXGenericRecordClazz { } private static final Logger log = LoggerFactory.getLogger(ERCStampedEnterpriseObject.class); public static String [] TimestampAttributeKeys = new String[] { Keys.CREATED, Keys.LAST_MODIFIED}; private static final Map<EOEditingContext, NSTimestamp> _datesPerEC = Collections.synchronizedMap(new WeakHashMap<EOEditingContext, NSTimestamp>()); public static class Observer { public void updateTimestampForEditingContext(NSNotification n) { NSTimestamp now=new NSTimestamp(); EOEditingContext editingContext = (EOEditingContext)n.object(); log.debug("Timestamp for {}: {}", editingContext, now); _datesPerEC.put(editingContext, now); } } protected static void initialize() { NSNotificationCenter center = NSNotificationCenter.defaultCenter(); NSSelector sel = ERXSelectorUtilities.notificationSelector("updateTimestampForEditingContext"); Observer observer = new Observer(); ERXRetainer.retain(observer); center.addObserver(observer, sel, ERXExtensions.objectsWillChangeInEditingContext, null); } public EOEnterpriseObject insertionLogEntry=null; @Override public void init(EOEditingContext ec) { super.init(ec); if (this instanceof ERCLogEntryInterface) { ERCLogEntryInterface lei = (ERCLogEntryInterface) this; String relationshipName =lei.relationshipNameForLogEntry(); EOEnterpriseObject logType = lei.logEntryType(); if (relationshipName != null && logType != null) { insertionLogEntry=ERCLogEntry.clazz.createLogEntryLinkedToEO(logType, null, this, relationshipName); } } // We now set the date created/last modified in willInsert/Update/Delete // A side effect of this technique is that for new EOs, created/lastModified is null until the EO actually gets saved // which means it'll fail the validation // two options: either we poke a value in those attributes here (even though it will be modified in willInsert, // or we make the property keys not mandatory // I am option for the former. NSTimestamp t=new NSTimestamp(); setCreated(t); setLastModified(t); } @Override public void willInsert() { super.willInsert(); touch(); setCreated(lastModified()); } @Override public void willUpdate() { super.willUpdate(); touch(); } @Override public void willDelete() { // this in theory should not have much effect // however EOF seems to have trouble with some cascade configuration // this will maybe help track them down super.willDelete(); touch(); } private static Boolean _touchReadOnlyEntities; /** * Returns whether or not read-only entities should be touched. This setting is only here in case there is a performance * issue introduced by looking up the entity() in touch(), so we can roll it back out. * * @return whether or not read-only entities should be touched (defaults to false) */ protected static boolean touchReadOnlyEntities() { if (_touchReadOnlyEntities == null) { // MS: don't worry about double-null check this-and-that .. it's a Boolean, so no constructor and worst case we double-check a property _touchReadOnlyEntities = ERXProperties.booleanForKeyWithDefault("er.corebusinesslogic.ERCStampedEnterpriseObject.touchReadOnlyEntities", false); } return _touchReadOnlyEntities; } private void touch() { // MS: Don't touch read-only entities -- that's just rude. if (!ERCStampedEnterpriseObject.touchReadOnlyEntities()) { EOEntity entity = entity(); if (entity != null && entity.isReadOnly()) { return; } } NSTimestamp date; EOEditingContext editingContext = editingContext(); if (editingContext != null) { date = _datesPerEC.get(editingContext); } else { log.error("Null editingContext in touch() for: {}", this); date = null; } if (date == null) { //either because there was no EC, or because there was no value in the Map log.error("Null modification date found in touch() call - EC delegate is probably missing"); date = new NSTimestamp(); } setLastModified(date); } public void addObjectToBothSidesOfLogEntryRelationshipWithKey(EOEnterpriseObject object, String key) { // if we said insertionLogEntry=null in validateForInsert, we run the risk of // the save failing and the user making a modif, which then would not be // propagated to the log entry. if (insertionLogEntry!=null && editingContext().insertedObjects().containsObject(this)) insertionLogEntry.addObjectToBothSidesOfRelationshipWithKey(object,key); } public NSTimestamp created() { return (NSTimestamp)storedValueForKey(Keys.CREATED); } public void setCreated(NSTimestamp value) { takeStoredValueForKey(value, Keys.CREATED); } public NSTimestamp lastModified() { return (NSTimestamp)storedValueForKey(Keys.LAST_MODIFIED); } public void setLastModified(NSTimestamp value) { takeStoredValueForKey(value, Keys.LAST_MODIFIED); } }