/* * 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.components.relationships; import java.util.Enumeration; import org.apache.log4j.Logger; import com.webobjects.appserver.WOComponent; import com.webobjects.appserver.WOContext; import com.webobjects.directtoweb.D2W; import com.webobjects.directtoweb.EditPageInterface; import com.webobjects.directtoweb.NextPageDelegate; import com.webobjects.eoaccess.EOEntity; import com.webobjects.eoaccess.EOModelGroup; import com.webobjects.eoaccess.EORelationship; import com.webobjects.eoaccess.EOUtilities; import com.webobjects.eocontrol.EOArrayDataSource; import com.webobjects.eocontrol.EOClassDescription; import com.webobjects.eocontrol.EODataSource; import com.webobjects.eocontrol.EODetailDataSource; import com.webobjects.eocontrol.EOEditingContext; import com.webobjects.eocontrol.EOEnterpriseObject; import com.webobjects.eocontrol.EOObjectStoreCoordinator; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSDictionary; import com.webobjects.foundation.NSMutableArray; import com.webobjects.foundation.NSValidation; import er.directtoweb.ERDirectToWeb; import er.directtoweb.components.ERDCustomEditComponent; import er.directtoweb.interfaces.ERDPickPageInterface; import er.extensions.eof.ERXConstant; import er.extensions.eof.ERXEC; import er.extensions.eof.ERXEOControlUtilities; import er.extensions.eof.ERXEnterpriseObject; ///////////////////////////////////////////////////////////////////////////////// // Important D2W Keys: // showAddButton - boolean to tell component to show the add button // entityNamesForNewInstances - array of strings that detail which entities are to be created. // explanationComponentName - component that is used in the StringListPicker to explain what it is that they are picking. // additionalRelationshipKeys - key-value pairs of additional relationships that need to be set after the eo is created. // uiStyle - list style will use a D2WList, popup/radio/browser will use an ERToOneRelationship FIXME: add in ERToManyRelationship // listConfigurationName - optional key that is for the list configuration of the D2W component. // destinationDisplayKey - key used to denote what to display in the ERToOneRelationship // selectionListKey - key that denotes what to use as the dataSource for the ERToOneRelationship. // permissionToEdit - key that determines if the add and/or edit button are shown. // postCreateNextPageDelegateKey - key that allows you to get your own nextPageDelegate into the fray. ///////////////////////////////////////////////////////////////////////////////// /** * Crazy component. Useful for editing/creating objects in an owned toOne or toMany relationship. Even works with relationships to abstract entities. * * @binding showAddButton defaults=Boolean * @binding key * @binding object * @binding listConfigurationName * @binding entityNamesForNewInstances * @binding explanationComponentName * @binding uiStyle * @binding destinationSortKey * @binding destinationDisplayKey * @binding selectionListKey * @binding preRelationshipKeys * @binding permissionToEdit defaults=Boolean * @binding postRelationshipKeys * @binding useForms defaults=Boolean */ public class ERDEditOwnedRelationship extends ERDCustomEditComponent { /** * 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; /** logging support */ public final static Logger log = Logger.getLogger("er.directtoweb.components.ERDEditOwnedRelationship"); protected EOEditingContext localContext; @Override public boolean synchronizesVariablesWithBindings() { return false; } public ERDEditOwnedRelationship(WOContext c) { super(c); } @Override public void awake() { _selectionList = null; } public NSDictionary preRelationshipKeys() { return (NSDictionary)valueForBinding("preRelationshipKeys"); } public NSDictionary postRelationshipKeys() { return (NSDictionary)valueForBinding("postRelationshipKeys"); } public NSArray entityNamesForNewInstances() { return (NSArray)valueForBinding("entityNamesForNewInstances"); } public String explanationComponentName() { return (String)valueForBinding("explanationComponentName"); } public String listConfigurationName() { return (String)valueForBinding("listConfigurationName"); } public String selectionListKey() { return (String)valueForBinding("selectionListKey"); } public String postCreateNextPageDelegateKey() { return (String)valueForBinding("postCreateNextPageDelegateKey"); } public String errorMessage() { return hasBinding("noSelectionErrorMessage") ? (String)valueForBinding("noSelectionErrorMessage") : "";} public NSArray list() { // we need to put the list in a peer (for add/delete ops) or in a child if it's a new object NSArray initialArray = (NSArray) (selectionListKey()!=null ? object().valueForKeyPath(selectionListKey()) : objectKeyPathValue()); NSArray result = null; if (initialArray != null) { localContext=((ERXEnterpriseObject)object()).isNewObject() ? ERXEC.newEditingContext(object().editingContext(), false) : ERXEC.newEditingContext(object().editingContext().parentObjectStore()); result = EOUtilities.localInstancesOfObjects(localContext, initialArray); } return result != null ? result : ERXConstant.EmptyArray; } private EODetailDataSource _detailDataSource; public EODataSource detailDataSource() { if (_detailDataSource==null) { String relationshipKey=selectionListKey()!=null ? selectionListKey() : key(); _detailDataSource=new EODetailDataSource(object().classDescription(), relationshipKey); _detailDataSource.qualifyWithRelationshipKey(relationshipKey, object()); } return _detailDataSource; } public EORelationship entityRelationship() { EOEntity e = EOModelGroup.defaultGroup().entityNamed(object().entityName()); EORelationship result=e.relationshipNamed(key()); if (result==null) throw new RuntimeException("Could not find relationship for entity "+object().entityName()+" - "+key()); return result; } public String relationshipEntityName() { return entityRelationship().destinationEntity().name(); } public boolean relationshipIsManditory() {return hasBinding("isMandatory") ? booleanValueForBinding("isMandatory") : entityRelationship().isMandatory(); } public EOEnterpriseObject item; private NSArray _selectionList; public NSArray selectionList() { if (_selectionList == null) { // Test if we have a binding for selectionListKey if (selectionListKey() != null) _selectionList = (NSArray)object().valueForKeyPath(selectionListKey()); else _selectionList = ERXConstant.EmptyArray; } return _selectionList; } public EOArrayDataSource selectionDataSource() { EOArrayDataSource dataSource = new EOArrayDataSource(EOClassDescription.classDescriptionForEntityName(relationshipEntityName()), object().editingContext()); dataSource.setArray(selectionList()); return dataSource; } public CreateEOWithChoicesDelegate createEODelegate() { return new CreateEOWithChoicesDelegate(); } public WOComponent add() { WOComponent result = null; clearValidationFailed(); CreateEOWithChoicesDelegate createEOWithChoices = createEODelegate(); createEOWithChoices.object=object(); createEOWithChoices.key=key(); createEOWithChoices.followPage=context().page(); createEOWithChoices.preRelationshipKeys=preRelationshipKeys(); createEOWithChoices.postRelationshipKeys=postRelationshipKeys(); createEOWithChoices.postCreateNextPageDelegateKey = postCreateNextPageDelegateKey(); if (entityNamesForNewInstances() == null || entityNamesForNewInstances().count() == 0) { createEOWithChoices.entityNameForNewInstances=relationshipEntityName(); } else if (entityNamesForNewInstances().count() == 1) { createEOWithChoices.entityNameForNewInstances=(String)entityNamesForNewInstances().objectAtIndex(0); } else { String pickTypePageConfiguration = null; if (hasBinding("pickTypePageConfiguration")) pickTypePageConfiguration = (String)valueForBinding("pickTypePageConfiguration"); else pickTypePageConfiguration = "SelectPickType" + relationshipEntityName(); ERDPickPageInterface ppi = (ERDPickPageInterface)D2W.factory().pageForConfigurationNamed(pickTypePageConfiguration, session()); ppi.setNextPageDelegate(createEOWithChoices); ppi.setCancelPage(context().page()); NSMutableArray choices = new NSMutableArray(); for (Enumeration e = entityNamesForNewInstances().objectEnumerator(); e.hasMoreElements();) { String entityName = (String)e.nextElement(); String displayNameForEntity = (String)ERDirectToWeb.d2wContextValueForKey("displayNameForEntity", entityName, null); if (entityName != null && displayNameForEntity != null) { choices.addObject(new EOCreationMultipleChoice(displayNameForEntity, entityName)); } } ppi.setChoices(choices); result = (WOComponent)ppi; } return result != null ? result : createEOWithChoices.nextPage(); } public WOComponent edit() { EOEnterpriseObject eo = (EOEnterpriseObject)objectKeyPathValue(); EditPageInterface epi = null; if (eo == null) { parent().validationFailedWithException(new NSValidation.ValidationException(errorMessage()),objectPropertyValue(), key()); } else { String editConfigurationName = (String)ERDirectToWeb.d2wContextValueForKey("editConfigurationName", eo.entityName()); epi = (EditPageInterface)D2W.factory().pageForConfigurationNamed(editConfigurationName, session()); epi.setNextPage(context().page()); if (((ERXEnterpriseObject)eo).isNewObject()) localContext = ERXEC.newEditingContext(object().editingContext(), false); else localContext = ERXEC.newEditingContext(object().editingContext().parentObjectStore()); epi.setObject(EOUtilities.localInstanceOfObject(localContext, eo)); localContext.hasChanges(); } return (WOComponent)epi; } static class CreateEOWithChoicesDelegate implements NextPageDelegate { protected String entityNameForNewInstances; protected String currentPageConfiguration; protected String postCreateNextPageDelegateKey = null; protected String key; protected EOEnterpriseObject object; protected EOEditingContext localContext; protected NSDictionary preRelationshipKeys, postRelationshipKeys; protected WOComponent followPage; protected NextPageDelegate postCreateNextPageDelegate = null; public WOComponent nextPage(WOComponent sender) { //entityNameForNewInstances = (String)sender.valueForKey("entityNameForNewInstances"); entityNameForNewInstances = ((EOCreationMultipleChoice)((ERDPickPageInterface)sender).selectedObjects().objectAtIndex(0)).entityName; if (log.isDebugEnabled()) log.debug("Creating "+entityNameForNewInstances); if (postCreateNextPageDelegateKey != null) { postCreateNextPageDelegate = (NextPageDelegate)sender.valueForKeyPath(postCreateNextPageDelegateKey); } return nextPage(); } public WOComponent nextPage() { EOEnterpriseObject newEO = createEO(); NSDictionary extraValues=currentPageConfiguration!=null ? new NSDictionary(currentPageConfiguration, "pageConfiguration") : null; String createPageConfigurationName = (String)ERDirectToWeb.d2wContextValueForKey("createConfigurationName", entityNameForNewInstances, extraValues); if (log.isDebugEnabled()) log.debug("Create Page Config: " + createPageConfigurationName + " for entity: " + entityNameForNewInstances+" - "+extraValues); EditPageInterface epi = (EditPageInterface)D2W.factory().pageForConfigurationNamed(createPageConfigurationName,followPage.session()); epi.setObject(newEO); if (postCreateNextPageDelegate == null) { ERDEditOwnedRelationship.PostSaveDelegate postSaveDelegate = new PostSaveDelegate(); postSaveDelegate.postRelationshipKeys=postRelationshipKeys; postSaveDelegate.object=object; postSaveDelegate.key=key; postSaveDelegate.savedObject=newEO; postSaveDelegate.localContext=localContext; postSaveDelegate.followPage=followPage; epi.setNextPageDelegate(postSaveDelegate); } else { epi.setNextPageDelegate(postCreateNextPageDelegate); } return (WOComponent)epi; } public EOEnterpriseObject createEO() { // The relationship will be set in the PostSaveDelegate if object is a new eo, ie ok to use a peer context. if (object!=null && !(object.editingContext().parentObjectStore() instanceof EOObjectStoreCoordinator)) { log.warn("Newly created object is in an editing context that will not save to the database"); } localContext = ERXEC.newEditingContext(object.editingContext().parentObjectStore()); if (log.isDebugEnabled()) log.debug("Creating "+entityNameForNewInstances); EOEnterpriseObject newEO = ERXEOControlUtilities.createAndInsertObject(localContext, entityNameForNewInstances); // If the object already exists, then hookup the relationship, if not do it after the object is saved. if (!((ERXEnterpriseObject)object).isNewObject()) { EOEnterpriseObject localEO = EOUtilities.localInstanceOfObject(localContext, object); if (localEO != null) localEO.addObjectToBothSidesOfRelationshipWithKey(newEO, key); } if (preRelationshipKeys != null) { for(Enumeration e = preRelationshipKeys.allKeys().objectEnumerator(); e.hasMoreElements();) { String relationshipKey = (String)e.nextElement(); String objectKeyPath = (String)preRelationshipKeys.objectForKey(relationshipKey); EOEnterpriseObject localObject = EOUtilities.localInstanceOfObject(localContext, (EOEnterpriseObject)object.valueForKeyPath(objectKeyPath)); localObject.addObjectToBothSidesOfRelationshipWithKey(newEO, relationshipKey); } } localContext.hasChanges(); // Make sure the EC survives. return newEO; } } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Delegate Explanation: // This delegate is used to make sure that the object makes it to the database, ie if the user was in a childEC when they // hit the edit button or add button, then the change would only be propogated to the session's ec not to the db ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// static class PostSaveDelegate implements NextPageDelegate { NSDictionary postRelationshipKeys; EOEnterpriseObject object; EOEnterpriseObject savedObject; EOEditingContext localContext; WOComponent followPage; String key; public WOComponent nextPage(WOComponent sender) { // If cancel is clicked the ec will be reverted which will set the ec on the inserted object to null if (savedObject.editingContext() != null) postProcessing(); return followPage; } public void postProcessing() { if (object != null && savedObject != null && key != null) { EOEnterpriseObject localEO = EOUtilities.localInstanceOfObject(object.editingContext(), savedObject), localObject; // because this might be null, an array, or an EO if ((object.valueForKey(key) != null)&&(object.valueForKey(key) instanceof NSArray)&&(!((NSArray)object.valueForKey(key)).containsObject(localEO))) object.addObjectToBothSidesOfRelationshipWithKey(localEO, key); if (object.valueForKey(key) == null) object.addObjectToBothSidesOfRelationshipWithKey(localEO, key); if (postRelationshipKeys != null) { for(Enumeration e = postRelationshipKeys.allKeys().objectEnumerator(); e.hasMoreElements();) { String relationshipKey = (String)e.nextElement(); String objectKeyPath = (String)postRelationshipKeys.objectForKey(relationshipKey); if (objectKeyPath.equals("this")) { localObject = localEO; } else { localObject = (EOEnterpriseObject)localEO.valueForKeyPath(objectKeyPath); } localObject.addObjectToBothSidesOfRelationshipWithKey(savedObject, relationshipKey); } } } } } public String noSelectionString() { return hasBinding("noSelectionString") ? (String)valueForBinding("noSelectionString"):"- none -"; } public static class EOCreationMultipleChoice { public String displayName, entityName; public EOCreationMultipleChoice(String displayName, String entityName) { this.displayName = displayName; this.entityName = entityName; } @Override public String toString() { return displayName; } } public boolean useForms() { return booleanValueForBinding("useForms"); } public boolean doNotUseForm() { return !useForms(); } }