/* * 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.pages; import org.apache.log4j.Logger; import com.webobjects.appserver.WOComponent; import com.webobjects.appserver.WOContext; import com.webobjects.appserver.WORequest; import com.webobjects.directtoweb.D2W; import com.webobjects.directtoweb.EditPageInterface; import com.webobjects.directtoweb.InspectPageInterface; import com.webobjects.eoaccess.EOGeneralAdaptorException; 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.NSValidation; import er.directtoweb.ERD2WContainer; import er.directtoweb.ERD2WFactory; import er.directtoweb.interfaces.ERDEditPageInterface; import er.directtoweb.interfaces.ERDFollowPageInterface; import er.directtoweb.interfaces.ERDObjectSaverInterface; import er.extensions.appserver.ERXComponentActionRedirector; import er.extensions.components._private.ERXWOForm; import er.extensions.eof.ERXEOAccessUtilities; import er.extensions.eof.ERXEOControlUtilities; import er.extensions.foundation.ERXValueUtilities; import er.extensions.localization.ERXLocalizer; /** * Superclass for all inspecting/editing ERD2W templates. * * @d2wKey inspectConfirmConfigurationName * @d2wKey object * @d2wKey editConfigurationName * @d2wKey useNestedEditingContext * @d2wKey shouldRenderBorder * @d2wKey shouldShowActionButtons * @d2wKey shouldShowCancelButtons * @d2wKey shouldShowSubmitButton * @d2wKey hasForm * @d2wKey validationKeys * @d2wKey shouldRevertChanges * @d2wKey shouldSaveChanges * @d2wKey shoudlvalidateBeforeSave * @d2wKey shouldCollectValidationExceptions * @d2wKey shouldRecoverFromOptimisticLockingFailure * @d2wKey shouldRevertUponSaveFailure * @d2wKey firstResponder */ public class ERD2WInspectPage extends ERD2WPage implements InspectPageInterface, ERDEditPageInterface, ERDObjectSaverInterface, ERDFollowPageInterface, ERXComponentActionRedirector.Restorable { /** * 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 constructor * @param context current context */ public ERD2WInspectPage(WOContext context) { super(context); } /** logging support */ public static final Logger log = Logger.getLogger(ERD2WInspectPage.class); public static final Logger validationCat = Logger.getLogger(ERD2WInspectPage.class+".validation"); protected static final String firstResponderContainerName = "FirstResponderContainer"; @Override public String urlForCurrentState() { NSDictionary<String, Object> dict = null; String actionName = d2wContext().dynamicPage(); if(object() != null) { String primaryKeyString = ERXEOControlUtilities.primaryKeyStringForObject(object()); if(primaryKeyString != null) { dict = new NSDictionary<>(primaryKeyString, "__key"); } } return context().directActionURLForActionNamed(actionName, dict).replaceAll("&", "&"); } protected boolean _objectWasSaved; public boolean objectWasSaved() { return _objectWasSaved; } private WOComponent _previousPage; public WOComponent previousPage() { return _previousPage;} public void setPreviousPage(WOComponent existingPageName) { _previousPage = existingPageName; } @Override public WOComponent nextPage() { return nextPage(true); } public WOComponent nextPage(boolean doConfirm) { Object inspectConfirmConfigurationName = d2wContext().valueForKey("inspectConfirmConfigurationName"); if(doConfirm && inspectConfirmConfigurationName != null && ! "".equals(inspectConfirmConfigurationName)) { WOComponent ipi = D2W.factory().pageForConfigurationNamed((String)inspectConfirmConfigurationName, session()); if (ipi instanceof InspectPageInterface) { ((InspectPageInterface)ipi).setObject((EOEnterpriseObject)d2wContext().valueForKey("object")); ((InspectPageInterface)ipi).setNextPageDelegate(nextPageDelegate()); ((InspectPageInterface)ipi).setNextPage(super.nextPage()); } if (ipi instanceof ERDFollowPageInterface) ((ERDFollowPageInterface)ipi).setPreviousPage(context().page()); return ipi; } WOComponent result = nextPageFromDelegate(); if(result == null) { result = super.nextPage(); } return result; } public WOComponent editAction() { WOComponent returnPage = null; if (previousPage() == null) { String editConfigurationName = (String)d2wContext().valueForKey("editConfigurationName"); EditPageInterface editPage; if(editConfigurationName != null && editConfigurationName.length() > 0) { editPage = (EditPageInterface)D2W.factory().pageForConfigurationNamed(editConfigurationName,session()); } else { editPage = D2W.factory().editPageForEntityNamed(object().entityName(),session()); } Object value = d2wContext().valueForKey("useNestedEditingContext"); boolean createNestedContext = ERXValueUtilities.booleanValue(value); EOEnterpriseObject object = ERXEOControlUtilities.editableInstanceOfObject(object(), createNestedContext); editPage.setObject(object); editPage.setNextPage(nextPage()); if (currentTab() != null && editPage instanceof ERD2WPage) { // try to keep the current tab selection ERD2WPage tabPage = (ERD2WPage) editPage; for (ERD2WContainer aTab : tabPage.tabSectionsContents()) { if (aTab.equals(currentTab())) { tabPage.setCurrentTab(aTab); } } } returnPage = (WOComponent)editPage; } return returnPage != null ? returnPage : previousPage(); } public WOComponent deleteAction() throws Throwable { EOEnterpriseObject anEO = object(); if (anEO.editingContext()!=null) { anEO.editingContext().deleteObject(anEO); return tryToSaveChanges(false) ? nextPage() : null; } return nextPage(); } public WOComponent cancelAction() { if ((object() != null) && (object().editingContext()!=null) && shouldRevertChanges()) { object().editingContext().revert(); clearValidationFailed(); } return nextPage(false); } public boolean shouldRenderBorder() { return ERXValueUtilities.booleanValue(d2wContext().valueForKey("shouldRenderBorder")); } public boolean shouldShowActionButtons() { return ERXValueUtilities.booleanValue(d2wContext().valueForKey("shouldShowActionButtons")); } public boolean shouldShowCancelButton() { return ERXValueUtilities.booleanValue(d2wContext().valueForKey("shouldShowCancelButton")); } public boolean shouldShowSubmitButton() { return ERXValueUtilities.booleanValue(d2wContext().valueForKey("shouldShowSubmitButton")); } @Override public boolean showCancel() { return super.showCancel() && shouldShowCancelButton(); } public boolean doesNotHaveForm() { return !ERXValueUtilities.booleanValue(d2wContext().valueForKey("hasForm")); } @Override public void setObject(EOEnterpriseObject eoenterpriseobject) { super.setObject(eoenterpriseobject); } // Useful for validating after all the values have been poked in // Example: consider a wizard page or tab page with values X and Y, in the business logic X + Y > 10. // however the tab page or wizard step is not the last so validateForSave can't help. In this // case you would want the method validateXY to be called after X and Y are both successfully set // on the eo. In this case you sould write a the following rule: // pageConfiguration = 'Foo' && tabKey = 'Bar' => validationKeys = "(validateXY)" public void performAdditionalValidations() { NSArray<String> validationKeys = (NSArray<String>)d2wContext().valueForKey("validationKeys"); if (validationKeys != null && validationKeys.count() > 0) { if (log.isDebugEnabled()) log.debug("Validating Keys: " + validationKeys + " on eo: " + object()); for (String validationKey : validationKeys) { try { object().valueForKeyPath(validationKey); } catch (NSValidation.ValidationException ev) { validationFailedWithException(ev, object(), validationKey); } } } } @Override public void takeValuesFromRequest(WORequest request, WOContext context) { super.takeValuesFromRequest(request, context); if (isEditing() && errorMessages.count() == 0) { performAdditionalValidations(); } } public boolean hasPropertyName() { String displayNameForProperty=displayNameForProperty(); return displayNameForProperty!=null && displayNameForProperty.length()>0; } public boolean shouldRevertChanges() { return ERXValueUtilities.booleanValue(d2wContext().valueForKey("shouldRevertChanges")); } public boolean shouldSaveChanges() { return ERXValueUtilities.booleanValue(d2wContext().valueForKey("shouldSaveChanges")); } public boolean shouldValidateBeforeSave() { return ERXValueUtilities.booleanValue(d2wContext().valueForKey("shouldValidateBeforeSave")); } @Override public boolean shouldCollectValidationExceptions() { return ERXValueUtilities.booleanValue(d2wContext().valueForKey("shouldCollectValidationExceptions")); } public boolean shouldRecoverFromOptimisticLockingFailure() { return ERXValueUtilities.booleanValueWithDefault(d2wContext().valueForKey("shouldRecoverFromOptimisticLockingFailure"), false); } public boolean shouldRevertUponSaveFailure() { return ERXValueUtilities.booleanValueWithDefault(d2wContext().valueForKey("shouldRevertUponSaveFailure"), false); } public boolean tryToSaveChanges(boolean validateObject) { // throws Throwable { validationLog.debug("tryToSaveChanges calling validateForSave"); boolean saved = false; if(object()!=null) { EOEditingContext ec = object().editingContext(); boolean shouldRevert = false; try { if (object()!=null && validateObject && shouldValidateBeforeSave()) { if (ec.insertedObjects().containsObject(object())) object().validateForInsert(); else object().validateForUpdate(); } if (object()!=null && shouldSaveChanges() && ec.hasChanges()) { try { ec.saveChanges(); } catch (RuntimeException e) { if( shouldRevertUponSaveFailure() ) { shouldRevert = true; } throw e; } } saved = true; } catch (NSValidation.ValidationException ex) { setErrorMessage(ERXLocalizer.currentLocalizer().localizedTemplateStringForKeyWithObject("CouldNotSave", ex)); validationFailedWithException(ex, ex.object(), "saveChangesExceptionKey"); } catch(EOGeneralAdaptorException ex) { if(ERXEOAccessUtilities.isOptimisticLockingFailure(ex) && shouldRecoverFromOptimisticLockingFailure()) { EOEnterpriseObject eo = ERXEOAccessUtilities.refetchFailedObject(ec, ex); setErrorMessage(ERXLocalizer.currentLocalizer().localizedTemplateStringForKeyWithObject("CouldNotSavePleaseReapply", d2wContext())); validationFailedWithException(ex, eo, "CouldNotSavePleaseReapply"); } else if(ERXEOAccessUtilities.isUniqueFailure(ex)) { EOEnterpriseObject eo = ERXEOAccessUtilities.refetchFailedObject(ec, ex); setErrorMessage(ERXLocalizer.currentLocalizer().localizedTemplateStringForKeyWithObject("DatabaseUniqException", d2wContext())); validationFailedWithException(ex, eo, "DatabaseUniqException"); } else { throw ex; } } finally { if( shouldRevert ) { ec.lock(); try { ec.revert(); } finally { ec.unlock(); } } } } else { saved = true; } return saved; } public WOComponent submitAction() throws Throwable { WOComponent returnComponent = null; // catch the case where the user hits cancel and then the back button if (object()!=null && object().editingContext()==null) { setErrorMessage(ERXLocalizer.currentLocalizer().localizedTemplateStringForKeyWithObject("ERD2WInspect.alreadyAborted", d2wContext())); clearValidationFailed(); } else { if (errorMessages.count()==0) { try { _objectWasSaved=true; returnComponent = tryToSaveChanges(true) ? nextPage() : null; } finally { _objectWasSaved=false; } } else { // if we don't do this, we end up with the error message in two places // in errorMessages and errorMessage (super class) setErrorMessage(null); } } return returnComponent; } public String saveButtonFileName() { return object()!=null && object().editingContext()!=null ? object().editingContext().parentObjectStore() instanceof EOObjectStoreCoordinator ? "SaveMetalBtn.gif" : "OKMetalBtn.gif" : "SaveMetalBtn.gif"; } public WOComponent printerFriendlyVersion() { WOComponent result=ERD2WFactory.erFactory().printerFriendlyPageForD2WContext(d2wContext(),session()); ((EditPageInterface)result).setObject(object()); return result; } /** * Generates other strings to be included in the WOGenericContainer tag for the propertyKey component cell. This is * used in conjunction with the <code>firstResponderKey</code> to mark the cell where the propertyKey is that named * by the <code>firstResponderKey</code> so that the "focusing" JavaScript {@link #tabScriptString tabScriptString} * can identify it. * @return a String to be included in the <code>td</code> tag for the propertyKey component cell. */ public String otherTagStringsForPropertyKeyComponentCell() { String firstResponderKey = (String)d2wContext().valueForKey(Keys.firstResponderKey); if (firstResponderKey != null && firstResponderKey.equals(propertyKey())) { return " id=\"" + firstResponderContainerName + "\""; } return null; } /** * <p>Constructs a JavaScript string that will give a particular field focus when the page is loaded. If the key * <code>firstResponderKey</code> from the d2wContext resolves, the script will attempt to focus on the form field * belonging to the property key named by the <code>firstResponderKey</code>. Otherwise, the script will just focus * on the first field in the form.</p> * * <p>Note that the key <code>useFocus</code> must resolve to <code>true</code> in order for the script to be * generated.</p> * @return a JavaScript string. */ public String tabScriptString() { if (d2wContext().valueForKey(Keys.firstResponderKey) != null) { return scriptForFirstResponderActivation(); } else { String result=""; String formName = ERXWOForm.formName(context(), "EditForm"); if (formName!=null) { result="var elem = document."+formName+".elements[0];"+ "if (elem!=null && (elem.type == 'text' || elem.type == 'area')) elem.focus();"; } return result; } } /** * <p>Constructs a JavaScript string to include in the WOComponent that will give a particular field focus when the * page is loaded, if the key <code>firstResponderKey</code> from the d2wContext resolves. The script will attempt * to focus on the form field belonging to the property key named by the <code>firstResponderKey</code>. * @return a JavaScript string to bring focus to a specific form element. */ public String scriptForFirstResponderActivation() { /* This is a bit of a roundabout way of getting to the form element for the propertyKey designated by the * firstResponderKey. The problem is that, basically, we don't know what component will be rendered until * the rules are fired. We also can't find the component by name or by id because we don't get to attach * that info. to the components used by D2W, since they are all very generic. * * So, the approach here is to: * * 1) Get as close as possible to the right form field by demarcating the containing element (the table cell) * with the id="FirstResponderContainer" property. See the otherTagStringsForPropertyKeyComponentCell method. * Then the JavaScript can easily find the element with that id. * 2) Once we have that, the script goes spelunking through the element's children until it finds * one that is of a reasonable type to be used in a form. * 3) Finally, the script attempts to activate the focus on that form element. */ if (d2wContext().valueForKey(Keys.firstResponderKey) == null) { return null; } StringBuilder sb = new StringBuilder(); sb.append("function activateFirstResponder() {\n"); // Get the container element. sb.append("\tvar container = document.getElementById('").append(firstResponderContainerName).append("');\n"); sb.append("\tif (!container) { return; }\n"); // Go through all the child elements of the container to find // the first that is of a type that can be used as a form input. // Note that this excludes image and button/submit tags. sb.append("\tvar candidates = container.getElementsByTagName('*');\n"); sb.append("\tif (candidates && candidates.length > 0) {\n"); sb.append("\t\tfor (var i = 0; i < candidates.length; i++) {\n"); sb.append("\t\t\tvar el = candidates[i];\n"); sb.append("\t\t\tvar type = el.type;\n"); sb.append("\t\t\tif (type == 'text' || type == 'checkbox' || type == 'radio' || \n"); sb.append("\t\t\t\ttype == 'select-one' || type == 'select-multiple' || \n"); sb.append("\t\t\t\ttype == 'file') {\n"); // Found an element of an acceptable type. Try to set focus on it. sb.append("\t\t\t\ttry {\n"); sb.append("\t\t\t\t\tel.focus();\n"); sb.append("\t\t\t\t\treturn;\n"); sb.append("\t\t\t\t} catch (e) {}// Eat the exception.\n"); sb.append("\t\t\t}\n"); sb.append("\t\t}\n"); sb.append("\t}\n"); sb.append('}'); // Now call the function. sb.append("activateFirstResponder();"); return sb.toString(); } }