package er.modern.look.pages; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import org.apache.commons.lang3.ObjectUtils; import com.webobjects.appserver.WOComponent; import com.webobjects.appserver.WOContext; import com.webobjects.appserver.WODisplayGroup; import com.webobjects.directtoweb.D2W; import com.webobjects.directtoweb.ERD2WUtilities; import com.webobjects.directtoweb.EditPageInterface; import com.webobjects.directtoweb.NextPageDelegate; import com.webobjects.directtoweb.SelectPageInterface; import com.webobjects.eoaccess.EOEntity; import com.webobjects.eoaccess.EORelationship; import com.webobjects.eoaccess.EOUtilities; 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.EOQualifier; import com.webobjects.eocontrol.EOSortOrdering; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSDictionary; import com.webobjects.foundation.NSMutableArray; import com.webobjects.foundation.NSNotification; import com.webobjects.foundation.NSNotificationCenter; import com.webobjects.foundation.NSSelector; import er.directtoweb.pages.ERD2WEditRelationshipPage; import er.directtoweb.pages.ERD2WPage; import er.extensions.appserver.ERXDisplayGroup; import er.extensions.eof.ERXConstant; import er.extensions.eof.ERXEC; import er.extensions.eof.ERXEOAccessUtilities; import er.extensions.eof.ERXEOControlUtilities; import er.extensions.eof.ERXGenericRecord; import er.extensions.foundation.ERXArrayUtilities; import er.extensions.foundation.ERXStringUtilities; import er.extensions.foundation.ERXValueUtilities; import er.modern.directtoweb.components.ERMDAjaxNotificationCenter; import er.modern.directtoweb.components.buttons.ERMDActionButton; import er.modern.directtoweb.components.repetitions.ERMDInspectPageRepetition; import er.modern.directtoweb.interfaces.ERMEditRelationshipPageInterface; /** * An improved EditRelationshipPage that supports embedding and inline editing tasks. * * @d2wKey editConfigurationName * @d2wKey isEntityEditable * @d2wKey checkSortOrderingKeys * @d2wKey defaultSortOrdering * @d2wKey readOnly * @d2wKey relationshipRestrictingQualifier - An additional qualifier that can be used to restrict the objects * shown in the relationship (see: ERDDelayedExtraQualifierAssignment). * Useful if you have a value like: isDeleted that you wish to respect. * * @author davidleber */ public class ERMODEditRelationshipPage extends ERD2WPage implements ERMEditRelationshipPageInterface, SelectPageInterface { /** * 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 extends ERD2WEditRelationshipPage.Keys { public static String parentPageConfiguration = "parentPageConfiguration"; public static String inlineTask = "inlineTask"; public static String inspectEmbeddedConfigurationName = "inspectEmbeddedConfigurationName"; public static String editEmbeddedConfigurationName = "editEmbeddedConfigurationName"; public static String createEmbeddedConfigurationName = "createEmbeddedConfigurationName"; public static String queryEmbeddedConfigurationName = "queryEmbeddedConfigurationName"; public static String localContext = "localContext"; public static String relationshipRestrictingQualifier ="relationshipRestrictingQualifier"; public static String checkSortOrderingKeys = "checkSortOrderingKeys"; public static String defaultSortOrdering = "defaultSortOrdering"; public static String userPreferencesSortOrdering = "sortOrdering"; public static String displayPropertyKeys = "displayPropertyKeys"; public static String subTask = "subTask"; public static String isEntityCreatable = "isEntityCreatable"; public static String shouldShowQueryRelatedButton = "shouldShowQueryRelatedButton"; } private EOEnterpriseObject _masterObject; private EOEnterpriseObject _selectedObject; private EOEnterpriseObject _objectToAddToRelationship; private String _relationshipKey; private EODataSource _dataSource; private EODataSource _selectDataSource; private WODisplayGroup _relationshipDisplayGroup; private Integer _batchSize = null; public boolean isRelationshipToMany; public WOComponent nextPage; public NextPageDelegate nextPageDelegate; public ERMODEditRelationshipPage(WOContext context) { super(context); } @Override public void awake() { _dataSource = null; NSNotificationCenter.defaultCenter().addObserver(this, new NSSelector<Void>("relatedObjectDidChange", ERXConstant.NotificationClassArray), ERMDActionButton.BUTTON_PERFORMED_DELETE_ACTION, null); super.awake(); } @Override public void sleep() { NSNotificationCenter.defaultCenter().removeObserver(this, ERMDActionButton.BUTTON_PERFORMED_DELETE_ACTION, null); super.sleep(); } // ACTIONS /** * Perform the displayQueryAction. Sets the inline task to 'query'. */ public WOComponent displayQueryAction() { setInlineTaskSafely("query"); return null; } /** * Performs the newObjectAction. Creates a new object and sets the inline task * to 'create' */ public WOComponent newObjectAction() { EOEditingContext newEc = ERXEC.newEditingContext(masterObject().editingContext()); EOClassDescription relatedObjectClassDescription = masterObject().classDescriptionForDestinationKey(relationshipKey()); EOEnterpriseObject relatedObject = EOUtilities.createAndInsertInstance(newEc, relatedObjectClassDescription.entityName()); EOEnterpriseObject localObj = EOUtilities.localInstanceOfObject(relatedObject.editingContext(), masterObject()); if (localObj instanceof ERXGenericRecord) { ((ERXGenericRecord)localObj).setValidatedWhenNested(false); } localObj.addObjectToBothSidesOfRelationshipWithKey(relatedObject, relationshipKey()); setSelectedObject(relatedObject); setInlineTaskSafely("create"); return null; } /** * Performs the queryAction. Sets the inline task to 'list' */ public WOComponent queryAction() { if (inlineTask() != null) { setInlineTaskSafely("list"); } return null; } /** * Performs the saveAction. Called by inline edit or create pages. */ public WOComponent saveAction() { if ("create".equals(inlineTask())) { relationshipDisplayGroup().fetch(); int count = relationshipDisplayGroup().allObjects().count(); if (count > 0) { Object object = relationshipDisplayGroup().allObjects().objectAtIndex(relationshipDisplayGroup().allObjects().count() - 1); relationshipDisplayGroup().selectObject(object); relationshipDisplayGroup().displayBatchContainingSelectedObject(); } } setInlineTaskSafely(null); // support for ERMDAjaxNotificationCenter postChangeNotification(); return null; } /** * Perform the selectAction. Called by inline select page. */ public WOComponent selectAction() { EOEnterpriseObject selected = (objectToAddToRelationship() != null) ? EOUtilities.localInstanceOfObject(masterObject().editingContext(), objectToAddToRelationship()) : null; if (selected != null) { masterObject().addObjectToBothSidesOfRelationshipWithKey(selected, relationshipKey()); relationshipDisplayGroup().fetch(); relationshipDisplayGroup().selectObject(selected); relationshipDisplayGroup().displayBatchContainingSelectedObject(); } // support for ERMDAjaxNotificationCenter postChangeNotification(); return null; } /** * Perform the returnAction. Called when the page is a non embedded page is returning to the originating * edit page. */ public WOComponent returnAction() { masterObject().editingContext().saveChanges(); WOComponent result = (nextPageDelegate() != null) ? nextPageDelegate().nextPage(this) : super.nextPage(); if (result != null) { return result; } result = (WOComponent)D2W.factory().editPageForEntityNamed(masterObject().entityName(), session()); ((EditPageInterface)result).setObject(masterObject()); // support for ERMDAjaxNotificationCenter postChangeNotification(); return result; } /** * Called when an {@link ERMDActionButton} changes the related object. * Forces the displayGroup to fetch. */ @SuppressWarnings("unchecked") public void relatedObjectDidChange(NSNotification notif) { NSDictionary<String, Object>userInfo = notif.userInfo(); if (userInfo != null) { Object key = userInfo.valueForKey("propertyKey"); EOEnterpriseObject obj = (EOEnterpriseObject)userInfo.valueForKey("object"); if (relationshipKey() != null && relationshipKey().equals(key) && ERXEOControlUtilities.eoEquals(masterObject(), obj)) { relationshipDisplayGroup().fetch(); // when the last object of the last batch gets removed, select the new last batch if (relationshipDisplayGroup().currentBatchIndex() > relationshipDisplayGroup().batchCount()) { relationshipDisplayGroup().setCurrentBatchIndex(relationshipDisplayGroup().batchCount()); } } } if (notif.userInfo().valueForKey("ajaxNotificationCenterId") == null) { // the change notification was not sent from ERMDAjaxNotificationCenter postChangeNotification(); } } private void postChangeNotification() { ERMDInspectPageRepetition parent = ERD2WUtilities.enclosingComponentOfClass(this, ERMDInspectPageRepetition.class); if (ERXValueUtilities.booleanValueWithDefault( parent.valueForKeyPath("d2wContext.shouldObserve"), false)) { NSNotificationCenter.defaultCenter().postNotification( ERMDAjaxNotificationCenter.PropertyChangedNotification, parent.valueForKeyPath("d2wContext")); } } // COMPONENT DISPLAY CONTROLS /** * Controls whether the inline query page is displayed. */ public boolean displayQuery() { return "query".equals(inlineTask()); } /** * Controls whether the inline eidt/create page is displayed. */ public boolean displayNew() { return "edit".equals(inlineTask()) || "inspect".equals(inlineTask()) || "create".equals(inlineTask()); } /** * Controls whether the inline list page is displayed. */ public boolean displayList() { return "list".equals(inlineTask()); } /** * Returns the name of the current inline page configuration */ public String inspectConfiguration() { String result = null; if ("create".equals(inlineTask())) { result = (String)d2wContext().valueForKey(Keys.createEmbeddedConfigurationName); } else if ("edit".equals(inlineTask())) { result = (String)d2wContext().valueForKey(Keys.editEmbeddedConfigurationName); } else { result = (String)d2wContext().valueForKey(Keys.inspectEmbeddedConfigurationName); } return result; } // SELECT PAGE INTERFACE /** * Returns the current selected Object. Required by the SelectPageInterface */ public EOEnterpriseObject selectedObject() { return _selectedObject; } /** * Sets the current selected Object. Required by the SelectPageInterface */ public void setSelectedObject(EOEnterpriseObject eo) { _selectedObject = eo; } // ERMEditRelationshipPageInterface /** * Returns an array containing the master object (index 0) and relationship key (index 1). * Required by the {@link ERMEditRelationshipPageInterface} * * @return NSArray containing the master object (index 0) and relationship key (index 1). */ public NSArray<?> masterObjectAndRelationshipKey() { return new NSArray<Object>(new Object[] { masterObject(), relationshipKey() }); } /** * Sets the master object and relationship key. * Takes an NSArray containing the master object (index 0) and relationship key (index 1). * Required by the {@link ERMEditRelationshipPageInterface} * * @param a an NSArray containing the master object (index 0) and relationship key (index 1). */ public void setMasterObjectAndRelationshipKey(NSArray<?> a) { EOEnterpriseObject masterObject = (EOEnterpriseObject) a.objectAtIndex(0); String relationshipKey = (String) a.objectAtIndex(1); if (masterObject != null && !ERXStringUtilities.stringIsNullOrEmpty(relationshipKey)) { EOEntity masterEntity = EOUtilities.entityForObject( masterObject.editingContext(), masterObject); EORelationship rel = masterEntity.relationshipNamed(relationshipKey); // set currentRelationship key to allow unique ID creation // (wonder-140) d2wContext().takeValueForKey(rel, "currentRelationship"); } setMasterObjectAndRelationshipKey(masterObject, relationshipKey); } /** * Set the master object and relationship key. * * @param eo the master object, an EOEnterpriseObject * @param relationshipKey */ public void setMasterObjectAndRelationshipKey(EOEnterpriseObject eo, String relationshipKey) { // only do this if the eo and relationshipKey have changed; if (relationshipKey != null && eo != null) { if (ObjectUtils.notEqual(relationshipKey(), relationshipKey) || (masterObject() != null && !ERXEOControlUtilities.eoEquals(masterObject(), eo))) { // NSLog.out.appendln("***ERMODEditRelationshipPage.setMasterObjectAndRelationshipKey: " // + "HAS CHANGES; " + eo + " - " + masterObject() + " " + relationshipKey + " - " + relationshipKey() +"***"); _dataSource = null; setMasterObject(eo); setEditingContext(eo.editingContext()); setRelationshipKey(relationshipKey); if (masterObject().isToManyKey(relationshipKey)) isRelationshipToMany = true; else relationshipDisplayGroup().setSelectsFirstObjectAfterFetch(true); relationshipDisplayGroup().setDataSource(dataSource()); relationshipDisplayGroup().setSortOrderings(sortOrderings()); relationshipDisplayGroup().fetch(); EOQualifier extraQualifier = (EOQualifier)d2wContext().valueForKey(Keys.relationshipRestrictingQualifier); if (extraQualifier != null) { relationshipDisplayGroup().setQualifier(extraQualifier); relationshipDisplayGroup().qualifyDataSource(); } setPropertyKey(keyWhenRelationship()); } } } /* * Overridden to set the parentRelationship key. * * @see er.directtoweb.pages.ERD2WPage#settings() */ @Override public NSDictionary<String,Object> settings() { String pc = d2wContext().dynamicPage(); if (pc != null) { if (d2wContext().valueForKey("currentRelationship") != null) { // set parentRelationship key to allow subcomponents to // reference the correct ID (wonder-140) return new NSDictionary<String,Object>(new Object[] { pc, d2wContext().valueForKey("currentRelationship") }, new String[] { "parentPageConfiguration", "parentRelationship" }); } else { return new NSDictionary<String,Object>(pc, "parentPageConfiguration"); } } return null; } // SORT ORDERING @SuppressWarnings("unchecked") public NSArray<EOSortOrdering> sortOrderings() { NSArray<EOSortOrdering> sortOrderings = null; if (userPreferencesCanSpecifySorting()) { sortOrderings = (NSArray<EOSortOrdering>) userPreferencesValueForPageConfigurationKey(Keys.userPreferencesSortOrdering); if (log.isDebugEnabled()) { log.debug("Found sort Orderings in user prefs " + sortOrderings); } } if (sortOrderings == null) { NSArray<String> sortOrderingDefinition = (NSArray<String>) d2wContext().valueForKey(Keys.defaultSortOrdering); if (sortOrderingDefinition != null) { NSMutableArray<EOSortOrdering> validatedSortOrderings = new NSMutableArray<EOSortOrdering>(); NSArray<String> displayPropertyKeys = (NSArray<String>) d2wContext().valueForKey(Keys.displayPropertyKeys); for (int i = 0; i < sortOrderingDefinition.count();) { String sortKey = sortOrderingDefinition.objectAtIndex(i++); String sortSelectorKey = sortOrderingDefinition.objectAtIndex(i++); if (!checkSortOrderingKeys() || isValidSortKey(displayPropertyKeys, sortKey)) { EOSortOrdering sortOrdering = new EOSortOrdering(sortKey, ERXArrayUtilities.sortSelectorWithKey(sortSelectorKey)); validatedSortOrderings.addObject(sortOrdering); } } sortOrderings = validatedSortOrderings; if (log.isDebugEnabled()) { log.debug("Found sort Orderings in rules " + sortOrderings); } } } return sortOrderings; } /** * Returns whether or not sort orderings should be validated (based on the checkSortOrderingKeys rule). * @return whether or not sort orderings should be validated */ public boolean checkSortOrderingKeys() { return ERXValueUtilities.booleanValueWithDefault(d2wContext().valueForKey(Keys.checkSortOrderingKeys), false); } /** * Validates the given sort key (is it a display key, an attribute, or a valid attribute path). * * @param displayPropertyKeys the current display properties * @param sortKey the sort key to validate * @return true if the sort key is valid, false if not */ protected boolean isValidSortKey(NSArray<String> displayPropertyKeys, String sortKey) { boolean validSortOrdering = false; try { if (displayPropertyKeys.containsObject(sortKey) || entity().anyAttributeNamed(sortKey) != null || ERXEOAccessUtilities.attributePathForKeyPath(entity(), sortKey).count() > 0) { validSortOrdering = true; } } catch (IllegalArgumentException e) { // MS: ERXEOAccessUtilities.attributePathForKeyPath throws IllegalArgumentException for a bogus key path validSortOrdering = false; } if (!validSortOrdering) { log.warn("Sort key '" + sortKey + "' is not in display keys, attributes or non-flattened key paths for the entity '" + entity().name() + "'."); validSortOrdering = false; } return validSortOrdering; } // this can be overridden by subclasses for which sorting has to be fixed // (i.e. Grouping Lists) public boolean userPreferencesCanSpecifySorting() { return !"printerFriendly".equals(d2wContext().valueForKey(Keys.subTask)); } // BATCH SIZE /** * @return the batch size as set via ERCPreference or the rules */ public int numberOfObjectsPerBatch() { if (_batchSize == null) { Integer batchSize = ERXValueUtilities.IntegerValueWithDefault(d2wContext() .valueForKey("defaultBatchSize"), 5); Object batchSizePref = userPreferencesValueForPageConfigurationKey("batchSize"); if (batchSizePref != null) { if (log.isDebugEnabled()) { log.debug("Found batch size in user prefs " + batchSizePref); } batchSize = ERXValueUtilities.IntegerValueWithDefault(batchSizePref, batchSize); } _batchSize = batchSize; } return _batchSize.intValue(); } // ACCESSORS public String inlineTask() { return (String)d2wContext().valueForKey(Keys.inlineTask); } public void setInlineTask(String task) { // noop } public void setInlineTaskSafely(String task) { d2wContext().takeValueForKey(task, Keys.inlineTask); } public String relationshipKey() { return _relationshipKey; } public void setRelationshipKey(String key) { _relationshipKey = key; } /** * DataSource for the relationship. * * @return EODataSource an EODetailDataSource created from the masterObject and relationshipKey. */ @Override public EODataSource dataSource() { if (_dataSource == null) { _dataSource = ERXEOControlUtilities.dataSourceForObjectAndKey(masterObject(), relationshipKey()); } return _dataSource; } @Override public void setDataSource(EODataSource ds) { _dataSource = ds; } public EOEnterpriseObject objectToAddToRelationship() { return _objectToAddToRelationship; } public void setObjectToAddToRelationship( EOEnterpriseObject objectToAddTorRelationship) { _objectToAddToRelationship = objectToAddTorRelationship; } /** * Display group for the related objects * * @return WODisplayGroup lazily instantiated display group. */ public WODisplayGroup relationshipDisplayGroup() { if (_relationshipDisplayGroup == null) { _relationshipDisplayGroup = new ERXDisplayGroup(); _relationshipDisplayGroup.setNumberOfObjectsPerBatch(numberOfObjectsPerBatch()); } return _relationshipDisplayGroup; } public void setRelationshipDisplayGroup(WODisplayGroup relationshipDisplayGroup) { _relationshipDisplayGroup = relationshipDisplayGroup; } public EODataSource selectDataSource() { return _selectDataSource; } public void setSelectDataSource(EODataSource selectDataSource) { _selectDataSource = selectDataSource; } public EOEnterpriseObject masterObject() { return _masterObject; } public void setMasterObject(EOEnterpriseObject masterObject) { _masterObject = masterObject; } /** Checks if the current list is empty. */ public boolean isListEmpty() { return listSize() == 0; } /** The number of objects in the list. */ public int listSize() { return relationshipDisplayGroup().displayedObjects().count(); } /** Should the 'new' button be displayed? */ public boolean isEntityCreatable() { return ERXValueUtilities.booleanValue(d2wContext().valueForKey(Keys.isEntityCreatable)) && !isEntityReadOnly(); } public boolean shouldShowQueryRelatedButton() { boolean shouldShowQueryRelatedButton = ERXValueUtilities .booleanValue(d2wContext().valueForKey(Keys.shouldShowQueryRelatedButton)); if (isRelationshipOwned()) { // if the relationship is owned, search makes no sense shouldShowQueryRelatedButton = false; } return shouldShowQueryRelatedButton; } public boolean isRelationshipOwned() { boolean isRelationshipOwned = false; if (masterObject().allPropertyKeys().contains(relationshipKey())) { isRelationshipOwned = masterObject().classDescription().ownsDestinationObjectsForRelationshipKey(relationshipKey()); } return isRelationshipOwned; } private void writeObject(ObjectOutputStream out) throws IOException { out.writeObject(_masterObject); out.writeObject(_objectToAddToRelationship); out.writeObject(_selectedObject); out.writeObject(_relationshipKey); out.writeBoolean(isRelationshipToMany); out.writeObject(_dataSource); out.writeObject(_relationshipDisplayGroup.dataSource()); out.writeObject(_relationshipDisplayGroup); out.writeObject(_selectDataSource); out.writeObject(d2wContext().valueForKey("inlineTask")); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { _masterObject = (EOEnterpriseObject) in.readObject(); _objectToAddToRelationship = (EOEnterpriseObject) in.readObject(); _selectedObject = (EOEnterpriseObject) in.readObject(); _relationshipKey = (String) in.readObject(); isRelationshipToMany = in.readBoolean(); _dataSource = (EODataSource) in.readObject(); EODetailDataSource ds = (EODetailDataSource) in.readObject(); ds.qualifyWithRelationshipKey(_relationshipKey, _masterObject); _relationshipDisplayGroup = (WODisplayGroup) in.readObject(); _selectDataSource = (EODataSource) in.readObject(); String inlineTask = (String) in.readObject(); if(inlineTask != null) { d2wContext().takeValueForKey(inlineTask, "inlineTask"); } } /** * @return a unique ID for the repetition container */ public String idForRepetitionContainer() { String repetitionContainerID = (String) d2wContext().valueForKey( "idForRepetitionContainer"); // use master object to generate globally unique ID // - allows for nesting of relationship components repetitionContainerID = repetitionContainerID.concat("_" + masterObject().hashCode()); return repetitionContainerID; } }