/* ReturnVal.java This class is a serializable return code that is returned from most Ganymede server operations that need to pass back some sort of status information to the client. Created: 27 January 1998 Module By: Jonathan Abbey, jonabbey@arlut.utexas.edu ----------------------------------------------------------------------- Ganymede Directory Management System Copyright (C) 1996-2013 The University of Texas at Austin Ganymede is a registered trademark of The University of Texas at Austin Contact information Author Email: ganymede_author@arlut.utexas.edu Email mailing list: ganymede@arlut.utexas.edu US Mail: Computer Science Division Applied Research Laboratories The University of Texas at Austin PO Box 8029, Austin TX 78713-8029 Telephone: (512) 835-3200 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package arlut.csd.ganymede.common; import java.rmi.Remote; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.Vector; import arlut.csd.JDialog.JDialogBuff; import arlut.csd.ganymede.rmi.Ganymediator; import arlut.csd.ganymede.rmi.Session; import arlut.csd.ganymede.rmi.XMLSession; import arlut.csd.ganymede.rmi.FileTransmitter; import arlut.csd.ganymede.rmi.adminSession; import arlut.csd.ganymede.rmi.db_object; import arlut.csd.Util.TranslationService; /*------------------------------------------------------------------------------ class ReturnVal ------------------------------------------------------------------------------*/ /** * <p>This class provides a report on the status of the client's * requested operation. It is intended to be returned by a call on * the server to make a change to the database.</p> * * <p>Included in this object is a general success code, a list of * objects and fields that need to be rescanned, if applicable, a * dialog resource that can provide a description of a dialog box to * be presented to the user, and an optional callback that the client * can call with the results of the dialog box if necessary.</p> * * <p>When a ReturnVal is returned, the {@link * arlut.csd.ganymede.common.ReturnVal#didSucceed() didSucceed()} * determines whether the operation was considered to have been * successful. There may be a good bit of additional metadata passed * back with the successful result, including an informational dialog * returned and/or a list of objects and fields that need to be * updated in response to the successful update.</p> * * <p>Alternatively, {@link * arlut.csd.ganymede.common.ReturnVal#didSucceed() didSucceed()} may * return false, in which case the operation either could not succeed * or is incomplete. In this case, {@link * arlut.csd.ganymede.common.ReturnVal#doRescan() doRescan()} will * return false, and {@link * arlut.csd.ganymede.common.ReturnVal#getDialog() getDialog()} should * return a valid {@link arlut.csd.JDialog.JDialogBuff JDialogBuff}. * If the operation is simply incomplete pending more data from the * user, {@link arlut.csd.ganymede.common.ReturnVal#getCallback() * getCallback()} will return a non-null value. In this case, the * user should be presented the dialog box, and the results of that * dialog should be passed to the callback. The callback will in * return pass back another ReturnVal object. The server may walk the * user through an iterative set of dialogs to finally complete the * desired operation.</p> * * <p>ReturnVal is not thread safe, so don't use it in multiple * concurrent threads.</p> * * @see arlut.csd.JDialog.JDialogBuff * @see arlut.csd.JDialog.DialogRsrc * @see arlut.csd.JDialog.StringDialog * @see arlut.csd.ganymede.rmi.Ganymediator * */ public final class ReturnVal implements java.io.Serializable { static final boolean debug = false; static final long serialVersionUID = 6467433232278467398L; /** * Sentinel object representing an order to have the client refresh * all objects. */ private static final Vector<Short> all = new Vector<Short>(); /** * TranslationService object for handling string localization in * the Ganymede system. */ static final TranslationService ts = TranslationService.getTranslationService("arlut.csd.ganymede.common.ReturnVal"); /** * static factory method for returning a ReturnVal indicating * success. Because setter methods may be made on the ReturnVal * that we return, we'll create a new one each time. */ static final public ReturnVal success() { return new ReturnVal(true, true); } /** * static factory method for returning a ReturnVal indicating simple * failure. Because setter methods may be made on the ReturnVal * that we return, we'll create a new one each time. */ static final public ReturnVal failure() { return new ReturnVal(false); } /** * Simple static helper method that Ganymede code can use to verify * that a ReturnVal-returning operation did succeed. */ static public boolean didSucceed(ReturnVal retVal) { return retVal == null || retVal.didSucceed(); } /** * Simple static helper method that Ganymede code can use to verify * that a ReturnVal-returning method involved transforming a * supplied value. */ static public boolean hasTransformedValue(ReturnVal retVal) { return retVal != null && retVal.hasTransformedValue(); } /** * Simple static helper method that Ganymede code can use to verify * that a ReturnVal is either null (indicating unconditional * success) or doNormalProcessing set. */ static public boolean isDoNormalProcessing(ReturnVal retVal) { return retVal == null || retVal.doNormalProcessing; } /** * Simple static helper method that checks both for failure and for * wizard intercept. Useful for server-side code that checks for * wizard interaction. */ static public boolean wizardHandled(ReturnVal retVal) { return !ReturnVal.didSucceed(retVal) || !ReturnVal.isDoNormalProcessing(retVal); } /** * <p>This static method is responsible for intelligently merging a * pair of ReturnVal objects, ensuring that the appropriate * information from each is propagated forward.</p> * * <p>The logic in this method is critical for the proper chaining * of results in server-side code.</p> */ static final public ReturnVal merge(ReturnVal retVal, ReturnVal retVal2) { if (retVal2 == null) { return retVal; } if (retVal == null) { return retVal2; } if (retVal.isCompatible(retVal2)) { return retVal; } // if one of the ReturnVals indicated a failure, pass the failure // through, and we'll just forget the success result as immaterial if (retVal.didSucceed() != retVal2.didSucceed()) { if (!retVal.didSucceed()) { return retVal; } else { return retVal2; } } // okay, we've got compatible success results, let's create a // result we can work with going forward ReturnVal result = new ReturnVal(retVal.didSucceed()); if (retVal.didSucceed()) { // we know that both of the ReturnVals that we're merging were // successful, so we'll want to merge all rescan information from // both. result.unionRescan(retVal); result.unionRescan(retVal2); } // doNormalProcessing is meant to be a signal that the normal // course of action is not to be followed. We'll want to preserve // that signal going forward. // // Generally, we use doNormalProcessing for a few different // semantic interpretations.. one is for DBEditObject.wizardHook() // to signal to the field setting methods that an exception to the // default behavior is desired, and that the field logic should // let the wizard handle it. The second is to indicate whether an // attempted transaction commit should be considered as retryable // by the client. The third, most minor one, is when an object is // cloned but certain fields could not be successfully or // completely cloned during the process. // // A wizard can't really do anything useful if doNormalProcessing // is set to false unless the retVal has a Ganymediator returned // to be responsible for further processing, so we'll check for // that first. If we see a wizard active, we'll pass its dialog // and ganymediator information through to our result. if (retVal.callback != null) { result.callback = retVal.callback; result.dialog = retVal.dialog; result.doNormalProcessing = retVal.doNormalProcessing; } else if (retVal2.callback != null) { result.callback = retVal2.callback; result.dialog = retVal2.dialog; result.doNormalProcessing = retVal2.doNormalProcessing; } else { // if we're not involved in a wizard bit, we'll pass back a // true doNormalProcessing only if both of our inputs had // doNormalProcessing. result.doNormalProcessing = retVal.doNormalProcessing && retVal2.doNormalProcessing; // no wizard? look to see if either or both of the ReturnVals // that we are merging have any dialog information, and put // either or both of them together in the result. if (retVal.dialog != null && retVal2.dialog != null) { // ugh, we've got two dialogs that need to be merged, so we'll // have to do something about that. // // if either one was an error dialog, we'll prioritize that as // far as the title is concerned. Otherwise, the first // ReturnVal we're merging will have priority for the title, // image, and text. if ("error.gif".equals(retVal2.dialog.getImageName())) { result.dialog = retVal2.dialog; result.dialog.appendText(retVal.dialog.getText()); } else { result.dialog = retVal.dialog; result.dialog.appendText(retVal2.dialog.getText()); } } else if (retVal.dialog != null) { result.dialog = retVal.dialog; } else if (retVal2.dialog != null) { result.dialog = retVal2.dialog; } } // if either provide a newObjectInvid, or remoteObjectRef, we'll // want to include that. if both try to provide either of those, // we'll have to throw an exception and give up. if (retVal.newObjectInvid != null || retVal2.newObjectInvid != null) { if (retVal.newObjectInvid != null && retVal2.newObjectInvid == null) { result.newObjectInvid = retVal.newObjectInvid; result.newLabel = retVal.newLabel; } else if (retVal2.newObjectInvid != null && retVal.newObjectInvid == null) { result.newObjectInvid = retVal2.newObjectInvid; result.newLabel = retVal2.newLabel; } else if (retVal2.newObjectInvid == retVal.newObjectInvid) // remember we intern Invids { result.newObjectInvid = retVal.newObjectInvid; // they both agree on the invid, but what about the label? // // we'll give priority to the first ReturnVal if it was // not null.. otherwise we'll leave it null if (retVal.newLabel == null) { result.newLabel = retVal2.newLabel; } else { result.newLabel = retVal.newLabel; } } else { throw new RuntimeException("Couldn't merge ReturnVals with conflicting newObjectInvids."); } } // and the same basic logic for the remoteObjectRef. if (retVal.remoteObjectRef != null || retVal2.remoteObjectRef != null) { if (retVal.remoteObjectRef != null && retVal2.remoteObjectRef == null) { result.remoteObjectRef = retVal.remoteObjectRef; } else if (retVal2.remoteObjectRef != null && retVal.remoteObjectRef == null) { result.remoteObjectRef = retVal2.remoteObjectRef; } else if (retVal2.remoteObjectRef == retVal.remoteObjectRef) { // they both agree, who cares result.remoteObjectRef = retVal.remoteObjectRef; } else { throw new RuntimeException("Couldn't merge ReturnVals with conflicting remoteObjectRefs."); } } return result; } // --- /** * <p>If true, the operation that this ReturnVal is reporting on * succeeded.</p> */ boolean success; /** * <p>A Serializable Invid that can be returned in response to certain * operations on the server.</p> */ Invid newObjectInvid = null; /** * <p>An enum that is used to indicate if this ReturnVal represents * a specific type of error that the client will respond to in a * special way. */ private ErrorTypeEnum errorType = ErrorTypeEnum.UNSPECIFIED; /** * <p>A remote handle to an RMI reference of various kinds ({@link * arlut.csd.ganymede.rmi.db_object db_object}, {@link * arlut.csd.ganymede.rmi.Session Session}, {@link * arlut.csd.ganymede.rmi.XMLSession XMLSession}) on the server * returned for use by the client.</p> */ private Remote remoteObjectRef = null; /** * <p>A Serializable StringBuffer representation of objects and fields * that need to be rescanned.</p> */ private StringBuffer rescanList = null; /** * <p>A Serializable Dialog Definition</p> */ private JDialogBuff dialog = null; /** * <p>A Remote handle to a Wizard object on the server</p> */ private Ganymediator callback = null; /** * <p>This variable will be non-null if the operation being reported * on changed the object's label. The GUI client will look for this * variable in order to trigger a fix-up of all pointers to the * object that was modified by the action that resulted in this * ReturnVal.</p> * * <p>If newLabel is not null, newObjectInvid must be set to point * to the Invid which is being relabeled.</p> */ private String newLabel = null; /** * <p>This boolean variable is used to convey a context-specific * flag indicating whether the attempted operation requires * exceptional handling. Some examples of this include the * determination whether the field code that invoked wizardHook on a * DBEditObject subclass should continue with its normal process or * whether it should immediately return this ReturnVal to the * (client-side) caller. It is also used to decide whether a * failure to commit a transaction is retryable or not.</p> */ public boolean doNormalProcessing; /** * <p>Maps Invids to a Vector of Shorts representing fields in the * Invid objects that need to be refreshed by the client.</p> * * <p>Used on the client-side post-serialization.</p> */ private transient HashMap<Invid,Vector<Short>> rescanHash = null; /** * <p>This field is set if the verifyNewValue() method transforms a * value during the input.</p> * * <p>Server-side only.</p> */ private transient Object transformedValue = null; /** * This field is set if the verifyNewValue() method transforms a * value during the input. * * Server-side only. */ private transient boolean transformedSet = false; /* -- */ // client side access /** * <p>This method returns the general success code for the * preceding operation. If didSucceed() is true, doRescan() * should be checked.</p> */ public boolean didSucceed() { return success; } /** * <p>Returns the type of error condition represented by this * ReturnVal. Generally this will be ErrorTypeEnum.UNSPECIFIED, but * certain types of error conditions will be marked with some other * ErrorTypeEnum value so that the client will know to treat it * specially.</p> */ public ErrorTypeEnum getErrorType() { return errorType; } /** * <p>This method is used to get an Invid that the server * wants to return to the client. Used particularly for * {@link arlut.csd.ganymede.rmi.invid_field#createNewEmbedded() invid_field.createNewEmbedded()}. * Return null if no Invid was set.</p> * * @see arlut.csd.ganymede.rmi.invid_field * @see arlut.csd.ganymede.server.InvidDBField */ public Invid getInvid() { return newObjectInvid; } /** * <p>This method is used to get a remote {@link * arlut.csd.ganymede.rmi.db_object db_object} reference that the server * wants to return to the client. Used particularly for * Session.create_db_object() / Session.edit_db_object(), or null if * no db_object was returned.</p> * * @see arlut.csd.ganymede.rmi.Session */ public db_object getObject() { return (db_object) remoteObjectRef; } /** * <p>This method is used to get a remote {@link * arlut.csd.ganymede.rmi.Session Session} reference that the server * wants to return to the client. Used to return the results * of a remote login attempt. May be null if the login attempt * failed.</p> */ public Session getSession() { return (Session) remoteObjectRef; } /** * <p>This method is used to get a remote {@link * arlut.csd.ganymede.rmi.XMLSession XMLSession} reference that the server * wants to return to the client. Used to return the results * of a remote xml login attempt. May be null if the login attempt * failed.</p> */ public XMLSession getXMLSession() { return (XMLSession) remoteObjectRef; } /** * <p>This method is used to get a remote {@link * arlut.csd.ganymede.rmi.FileTransmitter FileTransmitter} reference that the server * wants to return to the client. Used to provide XML dump results * to a remote xmlclient. May be null if permissions refused the * dump attempt.</p> */ public FileTransmitter getFileTransmitter() { return (FileTransmitter) remoteObjectRef; } /** * <p>This method is used to get a remote {@link * arlut.csd.ganymede.rmi.adminSession adminSession} reference that the * server wants to return to the admin console. Used to return the * results of a remote admin console connect attempt. May be null * if the connect attempt failed.</p> */ public adminSession getAdminSession() { return (adminSession) remoteObjectRef; } /** * <p>If the operation was not successful, this method should return * a dialog box describing the problem and, potentially, asking for * more information to complete the operation.</p> * * <p>This method should be checked after all calls to the server * that return non-null ReturnVal objects.</p> */ public JDialogBuff getDialog() { return dialog; } /** * <p>If the operation was not successful, this method should return * a the text of any encoded dialog box describing the problem. * This method is intended for text-mode clients that do not support * the full callback/wizard features that the {@link * arlut.csd.JDialog.JDialogBuff JDialogBuff} class supports.</p> * * <p>This method (or getDialog() for GUI clients) should be checked * after all calls to the server that return non-null ReturnVal * objects.</p> */ public String getDialogText() { if (dialog == null) { return null; } else { return dialog.getText(); } } /** * <p>If the server is asking for more interaction from the user to * complete this operation, this method will return an RMI handle to * a callback on the server. The client should popup the dialog box * specified by getDialog() and pass the results to the callback * returned by this method.</p> * * <p>This method will return null if getDialog() returns null, and * need not be checked in that case.</p> */ public Ganymediator getCallback() { return callback; } /** * <p>This method returns true if this ReturnVal encodes rescan * information for one or more fields in on or more objects.</p> * * <p>This method will never return true if didSucceed() returns * false, and need not be checked in that case.</p> */ public boolean doRescan() { return !(rescanList == null); } /** * <p>This method returns a Vector of Invid objects, corresponding to * those objects which need to have some field rescan work done.</p> */ public Vector<Invid> getRescanObjectsList() { if (!doRescan()) { return new Vector<Invid>(); } breakOutRescanList(); return new Vector<Invid>(rescanHash.keySet()); // copy } /** * <p>If this method returns true, the object that was modified by * the operation resulting in this ReturnVal changed the object's * label field. The client will use this as a signal to refresh * displayed links to the object's Invid.</p> */ public boolean objectLabelChanged() { return (this.newLabel != null); } /** * <p>Returns a non-null String if the object pointed to by * getInvid() has had its label changed. The returned String is the * new label.</p> */ public String getNewLabel() { return this.newLabel; } /** * <p>This method returns true if the server is requesting that all * fields in the object referenced by the client's preceding call * to the server be reprocessed.</p> */ public boolean rescanAll(Invid objID) { if (!doRescan()) { return false; } breakOutRescanList(); if (rescanHash.containsKey(objID) && rescanHash.get(objID) == all) { return true; } return false; } /** * <p>This method returns a Vector of Short() objects if the server * provided an explicit list of fields that need to be reprocessed, * or null if all or no fields need to be processed.</p> */ public Vector<Short> getRescanList(Invid objID) { if (!doRescan()) { return null; } breakOutRescanList(); Vector<Short> result = rescanHash.get(objID); if (result == null || result == all) { return null; } return result; } /** * <p>This method returns an encoded string representing * the objects and fields to be rescanned by the * client in response to this ReturnVal.</p> * * <p>To be used for debugging.</p> */ public String dumpRescanInfo() { StringBuilder buffer = new StringBuilder(); /* -- */ if (rescanList != null) { buffer.append("dumpRescanInfo(): "); buffer.append(rescanList.toString()); } else { buffer.append("none in this object"); } return buffer.toString(); } /** * <p>This private method converts the rescanList StringBuffer to a * HashMap (rescanHash) that maps Invid's to either Vector of * Short's or "all".</p> */ private void breakOutRescanList() { if (rescanHash != null) { return; } rescanHash = new HashMap<Invid, Vector<Short>>(); decodeRescanList(rescanList, rescanHash); } /** * <p>This method takes a StringBuffer encoded as follows:</p> * * <pre>263:170|all|271:131|31|57|286:41|all|310:4|134|13|92|</pre> * * <p>and returns a HashMap mapping Invid's to the rescan * information for that Invid, where the rescan information will * either be the String "all", indicating that all fields need to be * rescanned, or a Vector of Short's specifying field id's to be * rescanned for that object.</p> * * @param buffer The StringBuffer to be decoded. * @param original The HashMap to put the results into.. this method * will put into original the Union of the field rescan information * specified in original and the rescan information held in buffer. * * @return A reference to original. */ private HashMap<Invid, Vector<Short>> decodeRescanList(StringBuffer buffer, HashMap<Invid, Vector<Short>> original) { if (buffer == null) { return null; } if (original == null) { throw new IllegalArgumentException("Can't have a null original hash."); } /* - */ int lastIndex = 0; int nextIndex; String tmpString = buffer.toString(); String atom; Invid invid = null; /* -- */ while (lastIndex < tmpString.length()) { nextIndex = tmpString.indexOf('|', lastIndex); atom = tmpString.substring(lastIndex, nextIndex); if (atom.indexOf(':') != -1) { invid = Invid.createInvid(atom); } else if (atom.equals("all")) { original.put(invid, all); } else { Vector<Short> vec; Short fieldID = Short.valueOf(atom); if (original.containsKey(invid) && original.get(invid) != all) { vec = (Vector<Short>) original.get(invid); if (!vec.contains(fieldID)) { vec.add(fieldID); } } else if (!original.containsKey(invid)) { vec = new Vector<Short>(); vec.add(fieldID); original.put(invid, vec); } // else we've already got 'all' specified for this invid, so we // don't need to do anything else. } lastIndex = nextIndex + 1; } return original; } public String toString() { StringBuilder result = new StringBuilder("ReturnVal ["); /* -- */ if (dialog != null) { result.append("\""); result.append(dialog.getText()); result.append("\""); } else { result.append("\"\""); } if (didSucceed()) { result.append(", success"); } else { result.append(", failure"); } if (doNormalProcessing) { result.append(", normal"); } else { result.append(", abnormal"); } if (newObjectInvid != null) { result.append(", invid set"); } else { result.append(", invid not set"); } if (remoteObjectRef != null) { result.append(", remote obj set"); } else { result.append(", remote obj not set"); } if (callback != null) { result.append(", callback set"); } else { result.append(", callback not set"); } if (rescanList != null) { result.append(", rescan set"); } else { result.append(", rescan not set"); } result.append("]"); return result.toString(); } // --------------------------------------------------------------------------- // server side operations // --------------------------------------------------------------------------- /** * Base constructor */ public ReturnVal(boolean success, boolean doNormalProcessing) { this.success = success; this.doNormalProcessing = doNormalProcessing; } /** * Short-cut constructor */ public ReturnVal(boolean success) { this(success, success); // we now have doNormalProcessing set to the same as success 28 Feb 2008 } public void clear() { rescanList = null; dialog = null; callback = null; newObjectInvid = null; remoteObjectRef = null; } /** * <p>unionRescan merges field and object rescan requests from the * supplied ReturnVal with and rescan requests we contain.</p> * * <p>It is used to allow multiple sources in InvidDBField to * contribute rescan requests.</p> * * <p>This method returns this so you can do a cascading return.</p> */ public synchronized ReturnVal unionRescan(ReturnVal retVal) { if ((retVal == null) || (retVal == this)) { return this; } // add any rescan fields requested by retVal if (retVal.rescanList != null) { // if our rescanList is null, take theirs. if (rescanList == null) { rescanList = new StringBuffer(); rescanList.append(retVal.rescanList.toString()); } else { HashMap<Invid,Vector<Short>> result = new HashMap<Invid,Vector<Short>>(); decodeRescanList(retVal.rescanList, result); decodeRescanList(rescanList, result); encodeRescanList(result); } } return this; } /** * <p>This method takes a HashMap mapping Invid's to Vectors of * Short field identifiers or the String "all" and generates the * StringBuffer to be serialized down to the client.</p> * * <p>For use on the server-side.</p> */ private void encodeRescanList(HashMap<Invid,Vector<Short>> rescanTable) { if (rescanList == null) { rescanList = new StringBuffer(); } else { rescanList.setLength(0); } for (Map.Entry<Invid, Vector<Short>> entry: rescanTable.entrySet()) { Invid invid = entry.getKey(); Vector<Short> fields = entry.getValue(); rescanList.append(invid.toString()); rescanList.append("|"); if (fields == all) { rescanList.append("all|"); } else { for (Short field: fields) { rescanList.append(field.toString()); rescanList.append("|"); } } } } /** * <p>This method can be handy if you know for a certain fact that * the ReturnVal you're calling merge on is not null. Remember that * any method returning a ReturnVal is likely to return null to * indicate unexceptional success, and that if you assume you have a * real ReturnVal without checking, you're going to lose.</p> * * <p>In most situations, you should be using * ReturnVal.merge(retVal1, retVal2), even though that makes for * noisier code.</p> */ public ReturnVal merge(ReturnVal otherReturnVal) { return ReturnVal.merge(this, otherReturnVal); } public ReturnVal setSuccess(boolean didSucceed) { this.success = didSucceed; return this; } /** * <p>Sets the type of error that this ReturnVal is representing to * the client.</p> */ public ReturnVal setErrorType(ErrorTypeEnum val) { this.errorType = val; return this; } /** * <p>This method controls whether or not this ReturnVal will return * a 'my label changed!' message to the client.</p> */ public ReturnVal setObjectLabelChanged(Invid objInvid, String newLabel) { if (newObjectInvid != null) { throw new RuntimeException(); } this.newObjectInvid = objInvid; this.newLabel = newLabel; return this; } /** * <p>This method makes a note in this ReturnVal to have the client * rescan all fields in object objID.</p> * * <p>For use on the server-side.</p> */ public synchronized ReturnVal setRescanAll(Invid objID) { if (debug) { System.err.println("ReturnVal.setRescanAll(" + objID + ")"); } if (rescanList == null) { rescanList = new StringBuffer(); } rescanList.append(objID.toString()); rescanList.append("|all|"); return this; } /** * <p>This method makes a note in this ReturnVal to have the client * rescan field fieldID in object objID.</p> * * <p>For use on the server-side.</p> */ public synchronized ReturnVal addRescanField(Invid objID, short fieldID) { if (debug) { System.err.println("ReturnVal.addRescanField(" + objID + ", " + fieldID + ")"); } if (rescanList == null) { rescanList = new StringBuffer(); } rescanList.append(objID.toString()); rescanList.append("|"); rescanList.append(fieldID); rescanList.append("|"); return this; } /** * <p>This method attaches a remote reference to a {@link * arlut.csd.ganymede.rmi.Ganymediator} wizard-handler to this * ReturnVal for extraction by the client.</p> * * <p>For use on the server-side.</p> */ public ReturnVal setCallback(Ganymediator callback) { this.callback = callback; return this; } /** * <p>This method sets up a basic error text dialog for this * ReturnVal.</p> * * <p>Unlike {@link * arlut.csd.ganymede.server.Ganymede#createErrorDialog(java.lang.String, * java.lang.String)}, this method does not write the error text to * stderr.</p> */ public ReturnVal setErrorText(String body) { this.setErrorText(ts.l("setErrorText.default_title"), body); return this; } /** * <p>This method sets up a basic error text dialog for this * ReturnVal.</p> * * <p>Unlike {@link * arlut.csd.ganymede.server.Ganymede#createErrorDialog(java.lang.String, * java.lang.String)}, this method does not write the error text to * stderr.</p> */ public ReturnVal setErrorText(String title, String body) { this.dialog = new JDialogBuff(title, body, ts.l("setErrorText.ok"), null, "error.gif"); return this; } /** * <p>This method sets up a basic info text dialog for this * ReturnVal.</p> * * <p>Unlike {@link * arlut.csd.ganymede.server.Ganymede#createErrorDialog(java.lang.String, * java.lang.String)}, this method does not write the error text to * stderr.</p> */ public ReturnVal setInfoText(String body) { this.setInfoText(ts.l("setInfoText.default_title"), body); return this; } /** * <p>This method sets up a basic info text dialog for this * ReturnVal.</p> * * <p>Unlike {@link * arlut.csd.ganymede.server.Ganymede#createErrorDialog(java.lang.String, * java.lang.String)}, this method does not write the error text to * stderr.</p> */ public ReturnVal setInfoText(String title, String body) { this.dialog = new JDialogBuff(title, body, ts.l("setInfoText.ok"), null, "ok.gif"); return this; } /** * <p>This method attaches a dialog definition to this ReturnVal for * extraction by the client.</p> * * <p>For use on the server-side.</p> */ public ReturnVal setDialog(JDialogBuff dialog) { this.dialog = dialog; return this; } /** * <p>This method is used to set an Invid that the client * can retrieve from us in those cases where a method * on the server really does need to return an Invid * _and_ a return val.</p> * * <p>For use on the server-side.</p> */ public ReturnVal setInvid(Invid invid) { this.newObjectInvid = invid; return this; } /** * <p>This method is used to set a {@link * arlut.csd.ganymede.rmi.db_object db_object} reference that the * client can retrieve from us in those cases where a method on the * server really does need to return a db_object _and_ a return * val.</p> * * <p>For use on the server-side.</p> */ public ReturnVal setObject(db_object object) { this.remoteObjectRef = object; return this; } /** * <p>This method is used to set a {@link * arlut.csd.ganymede.rmi.Session Session} reference that the client * can retrieve from us at login time.</p> * * <p>For use on the server-side.</p> */ public ReturnVal setSession(Session session) { this.remoteObjectRef = session; return this; } /** * <p>This method is used to set a {@link * arlut.csd.ganymede.rmi.XMLSession XMLSession} reference that the * client can retrieve from us at login time.</p> * * <p>For use on the server-side.</p> */ public ReturnVal setXMLSession(XMLSession session) { this.remoteObjectRef = session; return this; } /** * <p>This method is used to set a {@link * arlut.csd.ganymede.rmi.FileTransmitter FileTransmitter} reference * that the client can retrieve from us.</p> * * <p>For use on the server-side.</p> */ public ReturnVal setFileTransmitter(FileTransmitter transmitter) { this.remoteObjectRef = transmitter; return this; } /** * <p>This method is used to set a {@link * arlut.csd.ganymede.rmi.adminSession adminSession} reference that * the admin console can retrieve from us at console connect * time.</p> * * <p>For use on the server-side.</p> */ public ReturnVal setAdminSession(adminSession session) { this.remoteObjectRef = session; return this; } /** * <p>This method is intended to be used by {@link * arlut.csd.ganymede.server.DBEditObject#verifyNewValue(arlut.csd.ganymede.server.DBField, * java.lang.Object) DBEditObject.verifyNewValue()}, when the * verifyNewValue() method wants to take the submitted input and * canonicalize it.</p> * * <p>Code in the Ganymede server (mostly the base logic in DBField) * which calls the verifyNewValue() method should respond to a * transformed value by substituting the transformed value for the * originally submitted value.</p> * * <p>This version of setTransformedValueObject() only sets the * transformed value to be returned, but an additional call will * need to be made on this ReturnVal to set a refresh order for the * field which triggered this ReturnVal.</p> */ public ReturnVal setTransformedValueObject(Object obj) { this.transformedSet = true; this.transformedValue = obj; return this; } /** * <p>This method is intended to be used by {@link * arlut.csd.ganymede.server.DBEditObject#verifyNewValue(arlut.csd.ganymede.server.DBField, * java.lang.Object) DBEditObject.verifyNewValue()}, when the * verifyNewValue() method wants to take the submitted input and * canonicalize it.</p> * * <p>Code in the Ganymede server (mostly the base logic in DBField) * which calls the verifyNewValue() method should respond to a * transformed value by substituting the transformed value for the * originally submitted value.</p> * * <p>If a value is transformed, setTranformedValueObject() will * also set the ReturnVal so that it encodes a rescan of the field * in question so the client will refresh it.</p> */ public ReturnVal setTransformedValueObject(Object obj, Invid invid, short fieldId) { this.transformedSet = true; this.transformedValue = obj; requestRefresh(invid, fieldId); return this; } /** * <p>This method returns true if the code returning this ReturnVal * wants to substitute a canonicalized value for the value submitted * to verifyNewValue().</p> * * <p>Code in the Ganymede server (mostly the base logic in DBField) * which calls the verifyNewValue() method should respond if this * method returns true by substituting the transformed value for the * originally submitted value.</p> */ public boolean hasTransformedValue() { return this.transformedSet; } /** * <p>Code in the Ganymede server (mostly the base logic in DBField) * which calls the verifyNewValue() method should respond to a * transformed value by substituting the transformed value for the * originally submitted value.</p> */ public Object getTransformedValueObject() { return this.transformedValue; } /** * <p>This method causes this ReturnVal to request that the field * we're manipulating will be refreshed by the client.</p> */ public ReturnVal requestRefresh(Invid invid, short fieldId) { // create a temporary ReturnVal so that we can union it in ReturnVal tempRetVal = new ReturnVal(true); HashMap<Invid,Vector<Short>> rescanInfo = new HashMap<Invid,Vector<Short>>(1); Vector<Short> fields = new Vector<Short>(1); fields.add(Short.valueOf(fieldId)); rescanInfo.put(invid, fields); tempRetVal.encodeRescanList(rescanInfo); return this.unionRescan(tempRetVal); } /** * <p>This private helper for the static merge() method is used to * determine whether two ReturnVals are trivially identical, in * which case the merge() method will not need to create a new * result object.</p> * * <p>Useful especially in avoiding extra work when merging simple * ReturnVal.success() objects.</p> */ private boolean isCompatible(ReturnVal retVal) { if ((success != retVal.success) || (doNormalProcessing != retVal.doNormalProcessing) || (errorType != retVal.errorType) || (newObjectInvid != null) || (newLabel != null) || (retVal.newObjectInvid != null) || (retVal.newLabel != null) || (remoteObjectRef != null) || (retVal.remoteObjectRef != null) || (rescanList != null) || (retVal.rescanList != null) || (dialog != null) || (retVal.dialog != null) || (callback != null) || (retVal.callback != null)) { return false; } return true; } }