/* * 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.directtoweb.assignments.defaults; import com.webobjects.directtoweb.D2WContext; import com.webobjects.directtoweb.KeyValuePath; import com.webobjects.eoaccess.EOAttribute; import com.webobjects.eoaccess.EOEntity; import com.webobjects.eoaccess.EOModelGroup; import com.webobjects.eoaccess.EORelationship; import com.webobjects.eocontrol.EOEditingContext; import com.webobjects.eocontrol.EOEnterpriseObject; import com.webobjects.eocontrol.EOKeyValueUnarchiver; import com.webobjects.eocontrol.EOSortOrdering; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSDictionary; import er.directtoweb.assignments.ERDAssignment; import er.extensions.eof.ERXConstant; import er.extensions.eof.ERXEOAccessUtilities; import er.extensions.foundation.ERXDictionaryUtilities; /** * A bunch of methods used for pulling default values from EOModels.<br> * Provides defaults for the following keys: * <ul> * <li><code>smartAttribute</code></li> * <li><code>smartRelationship</code></li> * <li><code>smartDefaultRows</code></li> * <li><code>smartDefaultAttributeWidth</code></li> * <li><code>entity</code></li> * <li><code>dummyEntity</code></li> * <li><code>destinationEntity</code></li> * <li><code>entityForControllerName</code></li> * <li><code>entityForPageConfiguration</code></li> * <li><code>sortKeyForList</code></li> * </ul> */ public class ERDDefaultModelAssignment extends ERDAssignment { /** * 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; /** holds the array of keys this assignment depends upon */ protected static final NSDictionary keys = ERXDictionaryUtilities.dictionaryWithObjectsAndKeys( new Object [] { new NSArray(new Object[] {"propertyKey", "object.entityName", "entity.name"}), "smartAttribute", new NSArray(new Object[] {"propertyKey", "object.entityName", "entity.name"}), "smartRelationship", new NSArray(new Object[] {"smartAttribute"}), "attributeConstants", new NSArray(new Object[] {"smartAttribute"}), "smartDefaultRows", new NSArray(new Object[] {"smartAttribute"}), "smartDefaultAttributeWidth", new NSArray(new Object[] {"smartRelationship"}), "destinationEntity", new NSArray(new Object[] {"propertyKey", "keyWhenRelationship"}), "sortKeyForList", new NSArray(new Object[] {"controllerName"}), "entityForControllerName", new NSArray(new Object[] {"pageConfiguration"}), "entityForPageConfiguration", new NSArray(new Object[] {"pageConfiguration"}), "defaultSortOrdering", NSArray.EmptyArray, "entity", NSArray.EmptyArray, "dummyEntity" }); /** * Implementation of the {@link er.directtoweb.assignments.ERDComputingAssignmentInterface}. This array * of keys is used when constructing the * significant keys for the passed in keyPath. * @param keyPath to compute significant keys for. * @return array of context keys this assignment depends upon. */ public NSArray dependentKeys(String keyPath) { return (NSArray)keys.valueForKey(keyPath); } /** * Static constructor required by the EOKeyValueUnarchiver * interface. If this isn't implemented then the default * behavior is to construct the first super class that does * implement this method. Very lame. * @param eokeyvalueunarchiver to be unarchived * @return decoded assignment of this class */ // ENHANCEME: Only ever need one of these assignments. public static Object decodeWithKeyValueUnarchiver(EOKeyValueUnarchiver eokeyvalueunarchiver) { return new ERDDefaultModelAssignment(eokeyvalueunarchiver); } /** * Public constructor * @param u key-value unarchiver used when unarchiving * from rule files. */ public ERDDefaultModelAssignment (EOKeyValueUnarchiver u) { super(u); } /** * Public constructor * @param key context key * @param value of the assignment */ public ERDDefaultModelAssignment (String key, Object value) { super(key,value); } protected int attributeWidthAsInt(D2WContext c) { EOAttribute a = (EOAttribute)c.valueForKey("smartAttribute"); return a!=null ? a.width() : 0; } protected int smartDefaultAttributeWidthAsInt(D2WContext c) { int i=attributeWidthAsInt(c); return i<50 ? ( i==0 ? 20 : i ) : 50; } public Object smartDefaultAttributeWidth(D2WContext c) { return String.valueOf(smartDefaultAttributeWidthAsInt(c)); } public Object smartDefaultRows(D2WContext c) { int i = attributeWidthAsInt(c); int j = smartDefaultAttributeWidthAsInt(c); int k = j == 0 ? i : (int)(i / j + 0.5D); if(k > 8) k = 8; return String.valueOf(k); } /** * Resolves the {@link EOAttribute} in a smarter manner using * the current object from the context as well as the propertyKey * to determine the current attribute. Works even with inheritance. * Works around the following problem: * An entity A has a relationship b to an entity B, which has a * subentity B1. B1 has an attribute k, which B does not have. * If in an inspect page for entity A, you use b.k as a display * key, then the D2W rules which are based on d2wContext.attribute * will not fire properly. This is because attribute is null, instead * of containing <EOAttribute entity=B1 name=k>. The reason D2W does * not find it is that it uses the Model to find out the EOAttribute * and starts from A. Following the relationship b, gives a B, and * asking B for an attribute named k returns nil and you lose. * @param c current D2W context * @return attribute for the current propertyKey object combination. */ public Object smartAttribute(D2WContext c) { EOAttribute result=null; String propertyKey=c.propertyKey(); Object rawObject=c.valueForKey("object"); if (rawObject!=null && rawObject instanceof EOEnterpriseObject && propertyKey!=null) { EOEnterpriseObject object=(EOEnterpriseObject)rawObject; EOEnterpriseObject lastEO=object; if (lastEO!=null && propertyKey.indexOf(".")!=-1 && propertyKey.indexOf("@")==-1) { String partialKeyPath=KeyValuePath.keyPathWithoutLastProperty(propertyKey); Object rawLastEO=object.valueForKeyPath(partialKeyPath); lastEO=rawLastEO instanceof EOEnterpriseObject ? (EOEnterpriseObject)rawLastEO : null; } if (lastEO!=null) { EOEntity entity=EOModelGroup.defaultGroup().entityNamed(lastEO.entityName()); String lastKey=KeyValuePath.lastPropertyKeyInKeyPath(propertyKey); result=entity.attributeNamed(lastKey); } } if (result==null) { // default to the basic attribute if the above didn't work if (propertyKey!=null) result=c.attribute(); } return result; } /** * Resolves the {@link EORelationship} in a smarter manner using * the current object from the context as well as the propertyKey * to determine the current relationship. Works even with inheritance. * Works around the following problem:<br> * An entity A has a relationship b to an entity B, which * has a subentity B1. B1 has a relationship k, which B does * not have. If in an inspect page for entity A, you use b.k * as a display key, then the D2W rules which are based on * d2wContext.relationship will not fire properly. This is * because relationship is null, instead of containing * <EORelationship entity=B1 name=k>. The reason D2W does not * find it is that it uses the Model to find out the EORelationship * and starts from A. Following the relationship b, gives a B, and * asking B for a relationship named k returns null and you lose. * @param c current D2W context * @return relationship for the current propertyKey object combination. */ public Object smartRelationship(D2WContext c) { Object result = null; Object rawObject=c.valueForKey("object"); String propertyKey=c.propertyKey(); if(propertyKey != null) { if (rawObject!=null && rawObject instanceof EOEnterpriseObject) { EOEnterpriseObject object=(EOEnterpriseObject)rawObject; EOEnterpriseObject lastEO=object; if (propertyKey.indexOf(".")!=-1 && propertyKey.indexOf("@")==-1) { String partialKeyPath=KeyValuePath.keyPathWithoutLastProperty(propertyKey); Object rawLastEO=object.valueForKeyPath(partialKeyPath); lastEO=rawLastEO instanceof EOEnterpriseObject ? (EOEnterpriseObject)rawLastEO : null; } if (lastEO!=null) { EOEntity entity=EOModelGroup.defaultGroup().entityNamed(lastEO.entityName()); String lastKey=KeyValuePath.lastPropertyKeyInKeyPath(propertyKey); result=entity.relationshipNamed(lastKey); } } if (result==null) { // working around D2W bug result=c.relationship(); } } return result; } private transient EOEntity _dummyEntity; /** Utility to create a fake entity that can be used for tasks such as error/confirm. */ // CHECKME ak We may have to insert the entity into the default model group protected EOEntity dummyEntity() { if (_dummyEntity==null) { _dummyEntity=new EOEntity(); _dummyEntity.setName("*all*"); } return _dummyEntity; } /** * Returns a fake entity that can be used for tasks such as error/confirm. * @param c current D2W context * @return dummy entity. */ public Object dummyEntity(D2WContext c) { return dummyEntity(); } /** * Returns a fake entity that can be used for tasks such as error/confirm. * @param c current D2W context * @return dummy entity. */ public Object entity(D2WContext c) { return dummyEntity(); } protected boolean isTaskWithoutEntity(String task) { return ("queryAll".equals(task) || "confirm".equals(task) || "error".equals(task)); } protected Object entityForKey(D2WContext c, String key) { Object result = null; if(key != null) { result = ERXEOAccessUtilities.entityMatchingString((EOEditingContext)c.valueForKeyPath("session.defaultEditingContext"), (String)c.valueForKey(key)); } if(result == null && isTaskWithoutEntity(c.task())) { result = dummyEntity(); } return result; } /** * Returns the default value for the entity based on the controllerName. * @param c current D2W context * @return the entity. */ public Object entityForControllerName(D2WContext c) { return entityForKey(c, "controllerName"); } /** * Returns the default value for the entity based on the controllerName. * @param c current D2W context * @return the entity. */ public Object attributeConstants(D2WContext c) { EOAttribute attr = (EOAttribute)c.valueForKey("smartAttribute"); if(attr != null && attr.userInfo() != null) { String clazzName = (String)attr.userInfo().objectForKey("ERXConstantClassName"); if(clazzName != null) { return ERXConstant.constantsForClassName(clazzName); } } return null; } /** * Returns the default value for the entity based on the pageConfiguration. * @param c current D2W context * @return the entity. */ public Object entityForPageConfiguration(D2WContext c) { return entityForKey(c, "pageConfiguration"); } /** * Returns the default value for the destination entity. This is a value add because * we are now also able to set the destination entity in other rules. * @param c current D2W context * @return the destination entity. */ public Object destinationEntity(D2WContext c) { EOEntity destinationEntity = (EOEntity)c.valueForKeyPath("smartRelationship.destinationEntity"); return destinationEntity; } /** * Called when firing this assignment with the key-path: * <b>sortKeyForList</b>. * @return the current propertyKey + "." + the current value for * keyWhenRelationship. */ public Object sortKeyForList(D2WContext context) { return context.valueForKey("propertyKey")+"."+context.valueForKey("keyWhenRelationship"); } /** * Called when firing this assignment with the key-path: * <b>defaultSortOrdering</b>. * @return the first value of the display property keys, with ascending comparison. */ public Object defaultSortOrdering(D2WContext context) { NSArray<String> keys = (NSArray) context.valueForKey("displayPropertyKeys"); if(keys.count() == 0) return NSArray.EmptyArray; String first = keys.objectAtIndex(0); return new NSArray(new Object[]{first, EOSortOrdering.CompareAscending.name()}); } }