/* xmlfield.java This class is a data holding structure that is intended to hold object and field data for an XML object element for xmlclient. -- Created: 2 May 2000 Module By: Jonathan Abbey, jonabbey@arlut.utexas.edu ----------------------------------------------------------------------- Ganymede Directory Management System Copyright (C) 1996-2014 The University of Texas at Austin Ganymede is a registered trademark of The University of Texas at Austin Contact information Web site: http://www.arlut.utexas.edu/gash2 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.server; import java.rmi.RemoteException; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.Stack; import java.util.Vector; import org.xml.sax.SAXException; import arlut.csd.Util.TranslationService; import arlut.csd.Util.VectorUtils; import arlut.csd.Util.XMLCharData; import arlut.csd.Util.XMLElement; import arlut.csd.Util.XMLEndDocument; import arlut.csd.Util.XMLItem; import arlut.csd.Util.XMLUtils; import arlut.csd.ganymede.common.FieldTemplate; import arlut.csd.ganymede.common.FieldType; import arlut.csd.ganymede.common.Invid; import arlut.csd.ganymede.common.IPAddress; import arlut.csd.ganymede.common.NotLoggedInException; import arlut.csd.ganymede.common.PermEntry; import arlut.csd.ganymede.common.ReturnVal; import arlut.csd.ganymede.common.SyncPrefEnum; /*------------------------------------------------------------------------------ class xmlfield ------------------------------------------------------------------------------*/ /** * <p>This class is a data holding structure that is intended to hold * object and field data for an XML object element for * {@link arlut.csd.ganymede.client.xmlclient xmlclient}. This * class is also responsible for actually registering its data * on the server on demand.</p> * * @author Jonathan Abbey */ public final class xmlfield implements FieldType { final static boolean debug = false; /** * <p>TranslationService object for handling string localization in * the Ganymede server.</p> */ static final TranslationService ts = TranslationService.getTranslationService("arlut.csd.ganymede.server.xmlfield"); /** * <p>Array of formatters that we use for generating and parsing * date fields in Ganymede XML files</p> */ static private final DateFormat[] formatters = new DateFormat[6]; static { // 0 = mail-style date with timezone // 1 = mail-style date no timezone // 2 = UNIX date output, with timezone "Tue Jul 11 01:04:55 CDT 2000" // 3 = UNIX date output, without timezone // 4 = no-comma style 0 // 5 = no-comma style 1 formatters[0] = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z"); formatters[1] = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss"); formatters[2] = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy"); formatters[3] = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy"); formatters[4] = new SimpleDateFormat("EEE dd MMM yyyy HH:mm:ss z"); formatters[5] = new SimpleDateFormat("EEE dd MMM yyyy HH:mm:ss"); } /** * <p>constant string for the addIfNotPresent mode</p> */ static final String ADDIFNOTPRESENT = "addIfNotPresent"; /** * <p>constant string for the add mode</p> */ static final String ADD = "add"; /** * <p>constant string for the set mode</p> */ static final String SET = "set"; /** * <p>constant string for the delete mode</p> */ static final String DELETE = "delete"; /** * <p>Definition record for this field type</p> */ FieldTemplate fieldDef; /** * <p>The xmlobject that contains us.</p> */ xmlobject owner; // the following hold data values for this field Object value = null; // if scalar Vector setValues = null; // if vector Vector delValues = null; // if vector Vector addValues = null; // if vector Vector addIfNotPresentValues = null; // if vector /* -- */ public xmlfield(xmlobject owner, XMLElement openElement) throws SAXException { XMLItem nextItem; /* -- */ this.owner = owner; String elementName = openElement.getName(); fieldDef = owner.getFieldDef(elementName); // owner.xSession.tell("parsing " + openElement); if (fieldDef == null) { owner.xSession.tell("\nDid not recognize field " + elementName + " in object " + owner); if (openElement.isEmpty()) { throw new NullPointerException("void field def for field element " + elementName); } else { skipToEndField(elementName); throw new NullPointerException("void field def for field element " + elementName); } } if (fieldDef.getType() == FieldType.BOOLEAN) { if (openElement.isEmpty()) { value = null; return; } nextItem = owner.xSession.getNextItem(); if (nextItem.matchesClose(elementName)) { value = null; return; } else { if (nextItem.matches(ADD) || nextItem.matches(ADDIFNOTPRESENT) || nextItem.matches(DELETE)) { // "Error, can''t use vector operator {0} in scalar field: {1}." throw new RuntimeException(ts.l("constructor.scalar_error", nextItem.toString(), fieldDef.getName())); } value = parseBoolean(nextItem); // fall through to skipToEndField() } } else if (fieldDef.getType() == FieldType.NUMERIC) { if (openElement.isEmpty()) { value = null; return; } nextItem = owner.xSession.getNextItem(); if (nextItem.matchesClose(elementName)) { value = null; return; } else { if (nextItem.matches(ADD) || nextItem.matches(ADDIFNOTPRESENT) || nextItem.matches(DELETE)) { // "Error, can''t use vector operator {0} in scalar field: {1}." throw new RuntimeException(ts.l("constructor.scalar_error", nextItem.toString(), fieldDef.getName())); } Integer iValue = parseNumeric(nextItem); if (iValue != null) { value = iValue; } // fall through to skipToEndField() } } else if (fieldDef.getType() == FieldType.DATE) { if (openElement.isEmpty()) { value = null; return; } nextItem = owner.xSession.getNextItem(); if (nextItem.matchesClose(elementName)) { // <field></field> == clear the value value = null; return; } else { // owner.xSession.tell("Parsing date for item " + nextItem); if (nextItem.matches(ADD) || nextItem.matches(ADDIFNOTPRESENT) || nextItem.matches(DELETE)) { // "Error, can''t use vector operator {0} in scalar field: {1}." throw new RuntimeException(ts.l("constructor.scalar_error", nextItem.toString(), fieldDef.getName())); } Date dValue = parseDate(nextItem); // parseDate will always return a non-null value, or // a RuntimeException if the date element couldn't // be parsed if (dValue != null) { value = dValue; } // fall through to skipToEndField() } } else if (fieldDef.getType() == FieldType.STRING) { if (!fieldDef.isArray()) { if (openElement.isEmpty()) { value = null; return; } nextItem = owner.xSession.reader.peekNextItem(); if (nextItem.matchesClose(elementName)) { nextItem = owner.xSession.getNextItem(); // consume it value = null; return; } else if (nextItem.matches("string")) { // we've got a <string>-encoded scalar rather than // free-standing plain text nextItem = owner.xSession.getNextItem(); // consume it value = parseStringVecItem(nextItem); // fall through to skipToEndField() below } else if (nextItem.matches(ADD) || nextItem.matches(ADDIFNOTPRESENT) || nextItem.matches(DELETE)) { // "Error, can''t use vector operator {0} in scalar field: {1}." throw new RuntimeException(ts.l("constructor.scalar_error", nextItem.toString(), fieldDef.getName())); } else { value = owner.xSession.reader.getFollowingString(openElement, false); if (value != null) { value = ((String) value).intern(); } // getFollowingString automatically consumes the field // close element after the string text return; } } else { processVectorElements(openElement); // processVectorElements automatically consumes the field // close element after the vector elements return; } } else if (fieldDef.getType() == FieldType.INVID) { if (!fieldDef.isArray()) { if (openElement.isEmpty()) { value = null; return; } nextItem = owner.xSession.getNextItem(); if (nextItem.matchesClose(elementName)) { value = null; return; } else { // scalar invid fields are never embedded/edit in // place, so we know that any value we found should be // an <invid> if (nextItem.matches(ADD) || nextItem.matches(ADDIFNOTPRESENT) || nextItem.matches(DELETE)) { // "Error, can''t use vector operator {0} in scalar field: {1}." throw new RuntimeException(ts.l("constructor.scalar_error", nextItem.toString(), fieldDef.getName())); } value = new xInvid(nextItem); // fall through to skipToEndField() } } else { processVectorElements(openElement); // processVectorElements automatically consumes the field // close element after the vector elements return; } } else if (fieldDef.getType() == FieldType.PERMISSIONMATRIX) { nextItem = owner.xSession.getNextItem(); if (!nextItem.matches("permissions")) { owner.xSession.tell("\nUnrecognized tag while parsing data for a permissions field: " + nextItem); skipToEndField(elementName); throw new NullPointerException("void field def"); } else { setValues = new Vector(); nextItem = owner.xSession.getNextItem(); while (!nextItem.matchesClose("permissions") && !(nextItem instanceof XMLEndDocument)) { xPerm permElement = new xPerm(nextItem, true); if (permElement != null) { setValues.addElement(permElement); } nextItem = owner.xSession.getNextItem(); } if (nextItem instanceof XMLEndDocument) { throw new RuntimeException("Ran into end of XML file while processing permission field " + elementName); } // fall through to skipToEndField() } } else if (fieldDef.getType() == FieldType.PASSWORD) { if (openElement.isEmpty()) { value = null; return; } nextItem = owner.xSession.getNextItem(); if (nextItem.matchesClose(elementName)) { value = null; return; } else { if (nextItem.matches(ADD) || nextItem.matches(ADDIFNOTPRESENT) || nextItem.matches(DELETE)) { // "Error, can''t use vector operator {0} in scalar field: {1}." throw new RuntimeException(ts.l("constructor.scalar_error", nextItem.toString(), fieldDef.getName())); } try { value = new xPassword(nextItem); } catch (NullPointerException ex) { Ganymede.logError(ex); } // fall through to skipToEndField() } } else if (fieldDef.getType() == FieldType.IP) { if (!fieldDef.isArray()) { nextItem = owner.xSession.getNextItem(); if (nextItem.matchesClose(elementName)) { value = null; return; } else { if (nextItem.matches(ADD) || nextItem.matches(ADDIFNOTPRESENT) || nextItem.matches(DELETE)) { // "Error, can''t use vector operator {0} in scalar field: {1}." throw new RuntimeException(ts.l("constructor.scalar_error", nextItem.toString(), fieldDef.getName())); } value = parseIP(nextItem); // fall through to skipToEndField() } } else { processVectorElements(openElement); // processVectorElements automatically consumes the field // close element after the vector elements return; } } else if (fieldDef.getType() == FieldType.FLOAT) { nextItem = owner.xSession.getNextItem(); if (nextItem.matchesClose(elementName)) { value = null; return; } else { if (nextItem.matches(ADD) || nextItem.matches(ADDIFNOTPRESENT) || nextItem.matches(DELETE)) { // "Error, can''t use vector operator {0} in scalar field: {1}." throw new RuntimeException(ts.l("constructor.scalar_error", nextItem.toString(), fieldDef.getName())); } Double fValue = parseFloat(nextItem); if (fValue != null) { value = fValue; } // fall through to skipToEndField() } } else if (fieldDef.getType() == FieldType.FIELDOPTIONS) { nextItem = owner.xSession.getNextItem(); if (!nextItem.matches("options")) { owner.xSession.tell("\nUnrecognized tag while parsing data for a field options field: " + nextItem); skipToEndField(elementName); throw new NullPointerException("void field def"); } else { setValues = new Vector(); nextItem = owner.xSession.getNextItem(); while (!nextItem.matchesClose("options") && !(nextItem instanceof XMLEndDocument)) { xOption optionElement = new xOption(nextItem, true); if (optionElement != null) { setValues.addElement(optionElement); } nextItem = owner.xSession.getNextItem(); } if (nextItem instanceof XMLEndDocument) { throw new RuntimeException("Ran into end of XML file while processing options field " + elementName); } // fall through to skipToEndField() } } // if we get here, we haven't yet consumed the field close // element.. go ahead and eat everything up to and including the // field close element. skipToEndField(elementName); } /** * <p>This method is called to process a set of values * for the various kinds of vector fields. This method * consumes all XMLItems up to and including the field * termination item.</p> */ private void processVectorElements(XMLElement openElement) throws SAXException { XMLItem nextItem; boolean setMode = false; boolean canDoSetMode = true; /** modeStack is used to keep track of what vector mode we are currently in.. the list of reasonable values are "addIfNotPresent" -- Add those elements listed that are not already there "add" -- Add, although with invids we will ignore adds if they are already present "set" -- Set the list of elements to the provided list "delete" -- Delete the listed elements */ Stack modeStack = new Stack(); /* -- */ // by default, we add modeStack.push(ADDIFNOTPRESENT); nextItem = owner.xSession.getNextItem(); while (!nextItem.matchesClose(openElement.getName()) && !(nextItem instanceof XMLEndDocument)) { if (((nextItem.matches(ADDIFNOTPRESENT) || nextItem.matches(ADD) || nextItem.matches(DELETE)) && !nextItem.isEmpty())) { if (setMode) { owner.xSession.tell("\nxmlclient: error, can't enter " + nextItem.getName() + " mode with a previous <set> directive in field " + openElement); throw new RuntimeException("xmlclient: error, can't enter " + nextItem.getName() + " mode with a previous <set> directive in field " + openElement); } canDoSetMode = false; modeStack.push(nextItem.getName()); nextItem = owner.xSession.getNextItem(); } else if (nextItem.matches(SET)) { if (canDoSetMode) { setMode = true; setValues = new Vector(); if (!nextItem.isEmpty()) { modeStack.push(SET); } } else { owner.xSession.tell("\nxmlclient: error, can't enter set" + " mode with a previous mode directive in field " + openElement); throw new RuntimeException("xmlclient: error, can't enter set" + " mode with a previous mode directive in field " + openElement); } nextItem = owner.xSession.getNextItem(); } else if (nextItem.matchesClose(ADD) || nextItem.matchesClose(DELETE) || nextItem.matchesClose(ADDIFNOTPRESENT)) { if (modeStack.size() > 1 && modeStack.peek().equals(nextItem.getName())) { // we checked for modeStack.size() > 1 to cover the // initial modeStack.push(ADDIFNOTPRESENT) modeStack.pop(); } else { owner.xSession.tell("\nError, found a mismatched </" + nextItem.getName() + "> while parsing a vector field."); throw new RuntimeException("Error, found a mismatched </" + nextItem.getName() + "> while parsing a vector field."); } nextItem = owner.xSession.getNextItem(); } else if (nextItem.matchesClose(SET)) { // okay.. we're actually not going to do anything // here, because set mode is really exclusive within // a field definition if (modeStack.peek().equals(nextItem.getName())) { modeStack.pop(); } nextItem = owner.xSession.getNextItem(); } else { Object newValue = null; if (fieldDef.getType() == FieldType.STRING) { newValue = parseStringVecItem(nextItem); } else if (fieldDef.getType() == FieldType.INVID) { if (fieldDef.isEditInPlace() && nextItem.matches("object")) { // we've got an embedded object.. add it, we'll process it later newValue = new xmlobject((XMLElement) nextItem, owner.xSession, this); } else { newValue = new xInvid(nextItem); } } else if (fieldDef.getType() == FieldType.IP) { newValue = parseIP(nextItem); } if (newValue != null) { if (setMode) { // we made sure to create setValues when we // entered setMode, so that we can cope with // <field><set></set></field> to clear all // elements in a field. setValues.addElement(newValue); } else if (modeStack.peek().equals(ADD)) { if (addValues == null) { addValues = new Vector(); } canDoSetMode = false; addValues.addElement(newValue); } else if (modeStack.peek().equals(ADDIFNOTPRESENT)) { if (addIfNotPresentValues == null) { addIfNotPresentValues = new Vector(); } canDoSetMode = false; addIfNotPresentValues.addElement(newValue); } else if (modeStack.peek().equals(DELETE)) { if (delValues == null) { delValues = new Vector(); } canDoSetMode = false; delValues.addElement(newValue); } } else { owner.xSession.tell("xmlfield WARNING: couldn't get vector value for " + nextItem + "in xml field object " + openElement); } nextItem = owner.xSession.getNextItem(); } } if (nextItem instanceof XMLEndDocument) { throw new RuntimeException("Ran into end of XML file while processing vector field " + openElement); } } /** * <p>This private helper method works through the XML file * until the close element tag that terminates this field * definition is found.</p> */ private void skipToEndField(String elementName) throws SAXException { XMLItem nextItem = owner.xSession.getNextItem(); while (!(nextItem.matchesClose(elementName) || (nextItem instanceof XMLEndDocument))) { nextItem = owner.xSession.getNextItem(); } if (nextItem instanceof XMLEndDocument) { owner.xSession.tell("\nRan into end of XML file while processing field " + elementName); throw new RuntimeException("Ran into end of XML file while processing field " + elementName); } } public Boolean parseBoolean(XMLItem item) throws SAXException { if (item instanceof XMLCharData) { String val = item.getString(); if (val.equalsIgnoreCase("true") || val.equalsIgnoreCase("t")) { return Boolean.TRUE; } else if (val.equalsIgnoreCase("false") || val.equalsIgnoreCase("f")) { return Boolean.FALSE; } else { throw new RuntimeException("\nUnrecognized character string found when boolean expected: " + item); } } if (!item.matches("boolean")) { throw new RuntimeException("\nUnrecognized XML item found when boolean expected: " + item); } if (!item.isEmpty()) { owner.xSession.tell("\nError, found a non-empty boolean field value element: " + item); } return Boolean.valueOf(item.getAttrBoolean("val")); } public Integer parseNumeric(XMLItem item) throws SAXException { if (item instanceof XMLCharData) { String val = item.getString(); try { return Integer.valueOf(val); } catch (NumberFormatException ex) { owner.xSession.tell("\nUnrecognized character string found when integer numeric value expected: " + item); return null; } } if (!item.matches("int")) { owner.xSession.tell("\nUnrecognized XML item found when int expected: " + item); return null; } if (!item.isEmpty()) { owner.xSession.tell("\nError, found a non-empty int field value element: " + item); } return item.getAttrInt("val"); } public Date parseDate(XMLItem item) throws SAXException { String formattedDate; String timecodeStr; long timecode = -1; Date result1 = null; Date result2 = null; /* -- */ if (!item.matches("date")) { throw new RuntimeException("\nUnrecognized XML item found when date expected: " + item); } if (!item.isEmpty()) { owner.xSession.tell("\nError, found a non-empty date field value element: " + item); } formattedDate = item.getAttrStr("val"); if (formattedDate != null) { for (int i = 0; i < formatters.length && result1 == null; i++) { try { synchronized (formatters[i]) { result1 = formatters[i].parse(formattedDate); } } catch (ParseException ex) { } } if (result1 == null) { owner.xSession.tell("\nError, could not parse date entity val " + formattedDate + " in element " + item); } } timecodeStr = item.getAttrStr("timecode"); if (timecodeStr != null) { try { timecode = java.lang.Long.parseLong(timecodeStr); result2 = new Date(timecode); } catch (NumberFormatException ex) { owner.xSession.tell("\nError, could not parse date numeric timecode " + timecodeStr + " in element " + item); owner.xSession.tell(ex.getMessage()); } } if (result2 != null && result1 != null) { // test to see if the two time stamps are within a second or // so of each other.. the Ganymede client doesn't always // round times to the nearest second when setting times // in date fields, but the time string in 'val', if present, // will only have second resolution long timediff = result2.getTime() - result1.getTime(); if (timediff < -1000 || timediff > 1000) { owner.xSession.tell("\nWarning, date element " + item + " is not internally consistent."); owner.xSession.tell("Ignoring date string \"" + formattedDate + "\", which was parsed as "); synchronized (formatters[0]) { owner.xSession.tell(formatters[0].format(result1)); owner.xSession.tell("Using timecode data string \"" + formatters[0].format(result2) + "\"."); } } return result2; } if (result2 != null) { return result2; } if (result1 != null) { return result1; } throw new RuntimeException("Couldn't get valid date value from " + item.toString()); } public String parseStringVecItem(XMLItem item) throws SAXException { if (!item.matches("string")) { owner.xSession.tell("\nUnrecognized XML item found when vector string element expected: " + item); return null; } if (!item.isEmpty()) { owner.xSession.tell("\nError, found a non-empty vector string element: " + item); } String result = item.getAttrStr("val"); if (result != null) { result = result.intern(); } return result; } public IPAddress parseIP(XMLItem item) throws SAXException { if (!item.matches("ip")) { owner.xSession.tell("\nUnrecognized XML item found when ip expected: " + item); return null; } if (!item.isEmpty()) { owner.xSession.tell("\nError, found a non-empty ip field value element: " + item); } return new IPAddress(item.getAttrStr("val")); } public Double parseFloat(XMLItem item) throws SAXException { String valStr; Double result = null; /* -- */ if (!item.matches("float")) { owner.xSession.tell("\nUnrecognized XML item found when float expected: " + item); return null; } if (!item.isEmpty()) { owner.xSession.tell("\nError, found a non-empty float field value element: " + item); } valStr = item.getAttrStr("val"); if (valStr == null) { owner.xSession.tell("\nError, float element " + item + " has no val attribute."); return null; } try { result = java.lang.Double.valueOf(valStr); } catch (NumberFormatException ex) { owner.xSession.tell("\nError, float element " + item + " has a malformed val attribute."); return null; } return result; } /** * <p>This method is responsible for propagating this field's data * values to the server.</p> * * <p>Returns a {@link arlut.csd.ganymede.common.ReturnVal ReturnVal} * indicating the result of the server * operation. This result may be null on success, or it may * be an encoded success or failure message in the normal * arlut.csd.ganymede.common.ReturnVal way.</p> * * <p>This method will throw an exception if the xmlobject that * contains this xmlfield has not established a remote * {@link arlut.csd.ganymede.rmi.db_object db_object} reference to * the server through which the editing can be performed.</p> */ public ReturnVal registerOnServer() { ReturnVal result = null; /* -- */ if (debug) { owner.xSession.tell("Registering field " + this.toString()); } try { if (fieldDef.isBoolean() || fieldDef.isNumeric() || fieldDef.isDate() || fieldDef.isFloat() || (!fieldDef.isArray() && (fieldDef.isString() || fieldDef.isIP()))) { // typical scalar, nothing fancy return owner.objref.setFieldValue(fieldDef.getID(), value); } else if (fieldDef.isArray() && (fieldDef.isString() || fieldDef.isIP())) { DBField field = owner.objref.getField(fieldDef.getID()); if (setValues != null) { // delete any values that are currently in the field // but which are not in our setValues vector, then add // any that are missing Vector currentValues = field.getValuesLocal(); Vector removeValues = VectorUtils.minus(currentValues, setValues); Vector newValues = VectorUtils.minus(setValues, currentValues); if (removeValues.size() != 0) { result = field.deleteElements(removeValues); if (!ReturnVal.didSucceed(result)) { return result; } } if (newValues.size() > 0) { return ReturnVal.merge(result, field.addElements(newValues)); } else { // skip a pointless server call if we are doing a // <set></set> to clear the field, or if we have // already synchronized the field by deleting // elements return ReturnVal.merge(result, null); } } else { if (addValues != null) { result = field.addElements(addValues); if (!ReturnVal.didSucceed(result)) { return result; } } if (addIfNotPresentValues != null) { Vector newValues = VectorUtils.difference(addIfNotPresentValues, field.getValuesLocal()); if (newValues.size() != 0) { result = field.addElements(newValues); if (!ReturnVal.didSucceed(result)) { return result; } } } if (delValues != null) { result = field.deleteElements(delValues); if (!ReturnVal.didSucceed(result)) { return result; } } } } else if (fieldDef.isPassword()) { xPassword xp = (xPassword) value; PasswordDBField field = owner.objref.getPassField(fieldDef.getID()); if (xp == null) { return field.setUndefined(false); } // set anything we can.. note that if we transmit null for // any of the password hash options, it will null the // password out entirely, so we don't want to transmit a // null unless all password options are all null. if (xp.plaintext != null) { // setting plaintext will cause the server to generate // all other hashes, so we will just return here return field.setPlainTextPass(xp.plaintext); } // okay, set whatever hashes we were given.. note that if // we see something like <password/>, with no attributes // set, we'll wind up clearing the password field entirely return field.setAllHashes(xp.crypttext, xp.md5text, xp.apachemd5text, xp.lanman, xp.ntmd4, xp.sshatext, xp.shaunixcrypt, xp.bcrypt, false, false); } else if (fieldDef.isInvid()) { if (!fieldDef.isArray()) { // scalar invid fields are never embedded/editInPlace xInvid invidValue = (xInvid) value; if (invidValue == null) { return owner.objref.setFieldValue(fieldDef.getID(), null); } return owner.objref.setFieldValue(fieldDef.getID(), invidValue.getInvid()); } else if (!fieldDef.isEditInPlace()) { InvidDBField field = owner.objref.getInvidField(fieldDef.getID()); /* -- */ /* note that we use VectorUtils.difference() here rather than VectorUtils.minus(), as we don't allow duplicate invid's in an invid field. */ if (setValues != null) { Vector currentValues = field.getValuesLocal(); Vector invidValues = getExtantInvids(setValues); Vector removeValues = VectorUtils.difference(currentValues, invidValues); Vector newValues = VectorUtils.difference(invidValues, currentValues); if (removeValues.size() > 0) { result = field.deleteElements(removeValues); if (!ReturnVal.didSucceed(result)) { return result; } } if (newValues.size() > 0) { return ReturnVal.merge(result, field.addElements(newValues)); } else { // skip a pointless server call if we are doing a // <set></set> to clear the field, or if we have // already synchronized the field by deleting // elements return ReturnVal.merge(result, null); } } else { if (addIfNotPresentValues != null) { Vector invidValues = getExtantInvids(addIfNotPresentValues); Vector newValues = VectorUtils.difference(invidValues, field.getValuesLocal()); if (newValues.size() != 0) { result = ReturnVal.merge(result, field.addElements(newValues)); if (!ReturnVal.didSucceed(result)) { return result; } } } if (addValues != null) { result = ReturnVal.merge(result, field.addElements(getExtantInvids(addValues))); if (!ReturnVal.didSucceed(result)) { return result; } } if (delValues != null) { result = ReturnVal.merge(result, field.deleteElements(getExtantInvids(delValues))); if (!ReturnVal.didSucceed(result)) { return result; } } } } else // *** edit in place / embedded object case *** { InvidDBField field = owner.objref.getInvidField(fieldDef.getID()); /* -- */ Vector currentValues = field.getValuesLocal(); Vector needToBeEdited = null; Vector needToBeCreated = null; Vector needToBeRemoved = null; if (setValues != null) { needToBeEdited = getExtantObjects(setValues); needToBeCreated = getNonRegisteredObjects(setValues); needToBeRemoved = VectorUtils.difference(currentValues, getExtantInvids(setValues)); } else { if (addIfNotPresentValues != null || addValues != null) { needToBeCreated = VectorUtils.union(getNonRegisteredObjects(addIfNotPresentValues), getNonRegisteredObjects(addValues)); needToBeEdited = VectorUtils.union(getExtantObjects(addIfNotPresentValues), getExtantObjects(addValues)); } if (delValues != null) { needToBeRemoved = getExtantInvids(delValues); } } if (needToBeCreated != null) { if (debug) { owner.xSession.tell("Need to create " + needToBeCreated.size() + " embedded objects"); } for (int i = 0; i < needToBeCreated.size(); i++) { Object x = needToBeCreated.elementAt(i); if (x instanceof xInvid) { throw new RuntimeException("Error, could not process <invid> " + "element in embedded invid field: " + x.toString()); } xmlobject object = (xmlobject) x; if (debug) { owner.xSession.tell("Creating embedded object " + object); } result = ReturnVal.merge(result, field.createNewEmbedded()); if (!ReturnVal.didSucceed(result)) { String msg = result.getDialogText(); if (msg != null) { owner.xSession.tell("Error creating new embedded " + object + ", reason: " + msg); } else { owner.xSession.tell("Error creating " + object + ", no reason given."); } } else { object.setInvid(result.getInvid()); object.objref = (DBObject) result.getObject(); // now that we've copied the object // carrier info out, clear the return val // so that the ReturnVal.merge() will work // properly the next time through the // loop. result.setInvid(null); result.setObject(null); // store this embedded object so we can // resolve xinvid references to it if (!owner.xSession.storeObject(object)) { // "ERROR: Ran into a name conflict when attempting to record an embedded xml object: {0}" throw new RuntimeException(ts.l("registerOnServer.conflict_resolving", object.toString())); } } // remember that we created this embedded // object, so that we can refer to it // elsewhere by its id owner.xSession.rememberEmbeddedObject(object); // register any non-invids on this embedded // object.. this will trigger the creation of // any more embedded objects recursively if // need be result = ReturnVal.merge(result, object.registerFields(0)); if (!ReturnVal.didSucceed(result)) { return result; } } } if (needToBeEdited != null) { if (debug) { owner.xSession.tell("Need to edit " + needToBeEdited.size() + " embedded objects"); } for (int i = 0; i < needToBeEdited.size(); i++) { xmlobject object = (xmlobject) needToBeEdited.elementAt(i); if (debug) { owner.xSession.tell("Editing embedded object " + object); } result = ReturnVal.merge(result, object.editOnServer(owner.xSession.session)); if (!ReturnVal.didSucceed(result)) { String msg = result.getDialogText(); if (msg != null) { owner.xSession.tell("Error editing previous embedded " + object + ", reason: " + msg); } else { owner.xSession.tell("Error editing previous embedded " + object + ", no reason given."); } } // remember that we edited this embedded // object so that we can fixup any invids // after all is said and done owner.xSession.rememberEmbeddedObject(object); // register any non-invids on this embedded // object.. this will trigger the creation of // any more embedded objects recursively if // need be result = ReturnVal.merge(result, object.registerFields(0)); if (!ReturnVal.didSucceed(result)) { return result; } } } if (needToBeRemoved != null) { if (debug) { owner.xSession.tell("Need to remove " + needToBeRemoved.size() + " embedded objects"); } for (int i = 0; i < needToBeRemoved.size(); i++) { Invid invid = (Invid) needToBeRemoved.elementAt(i); result = ReturnVal.merge(result, field.deleteElement(invid)); if (!ReturnVal.didSucceed(result)) { String msg = result.getDialogText(); if (msg != null) { owner.xSession.tell("Error deleting embedded " + invid + ", reason: " + msg); } else { owner.xSession.tell("Error deleting previous embedded " + invid + ", no reason given."); } return result; } } return result; } } } else if (fieldDef.isFieldOptions()) { FieldOptionDBField field = owner.objref.getFieldOptionsField(fieldDef.getID()); if (setValues != null) { // first, clear out any options set field.resetOptions(); // now set the options for (int i = 0; i < setValues.size(); i++) { xOption option = (xOption) setValues.elementAt(i); short baseId = owner.xSession.getTypeNum(option.getName()); result = ReturnVal.merge(result, field.setOption(baseId, option.getOption())); if (!ReturnVal.didSucceed(result)) { return result; } if (option.fields != null) { for (xOption fieldOption: option.fields.values()) { Map<String, FieldTemplate> fieldHash = owner.xSession.getFieldHash(option.getName()); if (fieldHash == null) { owner.xSession.tell("Error, can't process field options for object base " + XMLUtils.XMLDecode(option.getName()) + ", base not found."); return new ReturnVal(false); } FieldTemplate optionFieldDef = owner.xSession.getObjectFieldType(fieldHash, fieldOption.getName()); if (optionFieldDef == null) { owner.xSession.tell("Error, can't process field options for field " + XMLUtils.XMLDecode(fieldOption.getName()) + " in object base " + XMLUtils.XMLDecode(option.getName()) + ", base not found."); return new ReturnVal(false); } result = ReturnVal.merge(result, field.setOption(baseId, optionFieldDef.getID(), fieldOption.getOption())); if (!ReturnVal.didSucceed(result)) { return result; } } } } } return null; // success! } else if (fieldDef.isPermMatrix()) { PermissionMatrixDBField field = owner.objref.getPermField(fieldDef.getID()); if (setValues != null) { // first, clear out any permissions set field.resetPerms(); // now set the permissions for (int i = 0; i < setValues.size(); i++) { xPerm perm = (xPerm) setValues.elementAt(i); short baseId = owner.xSession.getTypeNum(perm.getName()); result = field.setPerm(baseId, perm.getPermEntry()); if (!ReturnVal.didSucceed(result)) { return result; } if (perm.fields != null) { for (xPerm fieldPerm: perm.fields.values()) { Map<String, FieldTemplate> fieldHash = owner.xSession.getFieldHash(perm.getName()); if (fieldHash == null) { owner.xSession.tell("Error, can't process field permissions for object base " + XMLUtils.XMLDecode(perm.getName()) + ", base not found."); return new ReturnVal(false); } FieldTemplate permFieldDef = owner.xSession.getObjectFieldType(fieldHash, fieldPerm.getName()); if (permFieldDef == null) { owner.xSession.tell("Error, can't process field permissions for field " + XMLUtils.XMLDecode(fieldPerm.getName()) + " in object base " + XMLUtils.XMLDecode(perm.getName()) + ", base not found."); return new ReturnVal(false); } result = field.setPerm(baseId, permFieldDef.getID(), fieldPerm.getPermEntry()); if (!ReturnVal.didSucceed(result)) { return result; } } } } } return null; // success! } } catch (RemoteException ex) { Ganymede.logError(ex); throw new RuntimeException(ex.getMessage()); } return null; } /** * <p>This method is used by the {@link * arlut.csd.ganymede.server.GanymedeXMLSession} to cause this field * to attempt to do lookups on all labeled xInvids in this field, in * an attempt to get the Invids for them. If a lookup cannot be * resolved when this method is called, it will be left unresolved * for a later round, after we have created the objects we need to * create.</p> * * <p>Note in the code for this method that we don't care about the * actual invids returned, we're just wanting to make sure that we * try to look them up on the server at this point in time, before * we apply any object renaming in the processing of our containing * xml transaction.</p> */ public void dereferenceInvids() throws NotLoggedInException { if (getType() != FieldType.INVID) { // "dereferenceInvids() called on a non-Invid field." throw new RuntimeException(ts.l("dereferenceInvids.bad_type")); } if (fieldDef.isEditInPlace()) { throw new RuntimeException("ASSERT: dereferenceInvids() called on an embedded object field."); } try { if (!isArray()) { if (value != null) { ((xInvid) value).getInvid(false); // try to resolve } } else { if (setValues != null) { for (int i = 0; i < setValues.size(); i++) { xInvid xi = (xInvid) setValues.elementAt(i); xi.getInvid(false); // try to resolve } } if (delValues != null) { for (int i = 0; i < delValues.size(); i++) { xInvid xi = (xInvid) delValues.elementAt(i); xi.getInvid(false); // try to resolve } } if (addValues != null) { for (int i = 0; i < addValues.size(); i++) { xInvid xi = (xInvid) addValues.elementAt(i); xi.getInvid(false); // try to resolve } } if (addIfNotPresentValues != null) { for (int i = 0; i < addIfNotPresentValues.size(); i++) { xInvid xi = (xInvid) addIfNotPresentValues.elementAt(i); xi.getInvid(false); // try to resolve } } } } catch (ClassCastException ex) { Ganymede.logError(ex, "Error processing xmlfield dereferenceInvids()." + "xmlfield is " + this.toString()); } } /** * <p>This private helper method takes a Vector of xInvid and * xmlobject objects (in the embedded object case) and returns * a Vector of Invid objects. If any xmlobjects in the input * Vector did not map to pre-existing objects on the server, * then no invid will be returned for those elements, and as * a result, the returned vector may be smaller than the * input.</p> */ private Vector getExtantInvids(Vector values) throws NotLoggedInException { Invid invid; Vector invids = new Vector(); /* -- */ if (values == null) { return invids; } // if we're an embedded object field, we'll contain xmlobjects for (int i=0; i < values.size(); i++) { Object x = values.elementAt(i); if (x instanceof xInvid) { invid = ((xInvid) x).getInvid(); if (debug && invid == null) { owner.xSession.tell("Couldn't find an invid from an xInvid.getInvid() call on " + x); } } else if (x instanceof xmlobject) { invid = ((xmlobject) x).getInvid(); if (debug && invid == null) { owner.xSession.tell("Couldn't find an invid from an xmlobject.getInvid() call on " + x); } } else { owner.xSession.tell("Unrecognized XML element while processing Invid vector: " + x); continue; } if (invid != null) { invids.addElement(invid); } else { owner.xSession.tell("Couldn't find invid for " + x); } } return invids; } /** * <p>This private helper method takes a Vector of xInvid and * xmlobject objects (in the embedded object case) and returns * a Vector of xmlobjects that exist on the server. Any xInvid * objects in the input Vector, along with any xmlobject objects * which do not correspond to pre-existing objects on the server * will be omitted from the returned vector.</p> */ private Vector getExtantObjects(Vector values) throws NotLoggedInException { Vector objects = new Vector(); /* -- */ if (values == null) { return objects; } // if we're an embedded object field, we'll contain xmlobjects for (int i=0; i < values.size(); i++) { Object x = values.elementAt(i); if (x instanceof xInvid) { continue; } else if (x instanceof xmlobject) { if (((xmlobject) x).getInvid() != null) { objects.addElement(x); } else if (debug) { owner.xSession.tell("Couldn't find invid for " + x); } } else { owner.xSession.tell("Unrecognized XML element while processing Invid vector: " + x); continue; } } return objects; } /** * <p>This private helper method takes a Vector of xInvid and * xmlobject objects and returns a Vector of xInvids and xmlobjects * that could not be resolved on the server.</p> */ private Vector getNonRegisteredObjects(Vector values) throws NotLoggedInException { Vector objects = new Vector(); Invid invid; /* -- */ if (values == null) { return objects; } for (int i=0; i < values.size(); i++) { Object x = values.elementAt(i); if (x instanceof xInvid) { invid = ((xInvid) x).getInvid(); } else if (x instanceof xmlobject) { invid = ((xmlobject) x).getInvid(); } else { owner.xSession.tell("Unrecognized XML element while processing Invid vector: " + x); continue; } if (invid == null) { objects.addElement(x); } } return objects; } /** * <p>Returns the non-XML-encoded name of this field.</p> */ public String getName() { return fieldDef.getName(); } /** * <p>Returns the {@link arlut.csd.ganymede.common.FieldType} short for * the type of field represented by this xmlfield.</p> */ public short getType() { return fieldDef.getType(); } /** * <p>Returns true if this field is a vector field.</p> */ public boolean isArray() { return fieldDef.isArray(); } /** * <p>Debug diagnostics</p> */ public String toString() { StringBuilder result = new StringBuilder(); /* -- */ result.append(fieldDef.getName()); if (value != null) { result.append(", value = "); result.append(value.toString()); } else { if (setValues != null) { result.append(", setValues = \""); result.append(arlut.csd.Util.VectorUtils.vectorString(setValues)); result.append("\""); } else if (delValues != null) { result.append(", delValues = \""); result.append(arlut.csd.Util.VectorUtils.vectorString(delValues)); result.append("\""); } else if (addValues != null) { result.append(", addValues = \""); result.append(arlut.csd.Util.VectorUtils.vectorString(addValues)); result.append("\""); } } return result.toString(); } } /** * This class is used by the Ganymede XML client to represent an invid * object reference field value. This field value may or may not be * fully resolved to an actual invid on the server, depending on what * stage of processing the XML client is in. */ class xInvid { /** * TranslationService object for handling string localization in the * Ganymede system. */ static final TranslationService ts = TranslationService.getTranslationService("arlut.csd.ganymede.server.xInvid"); /** * <p>The numeric type id for the object type this xInvid is meant * to point to.</p> * * <p>In the XML file, this field is derived from the type * attribute, after doing an object type lookup in the server's data * structures.</p> */ short typeId; /** * <p>The id string for this xInvid from the XML file. Will be used * to resolve this xInvid to an actual {@link * arlut.csd.ganymede.common.Invid Invid} on the server, if set.</p> * * <p>In the XML file, this field is taken from the id * attribute.</p> */ String objectId; /** * <p>This polymorphic object field is intended to contain the * actual on-server Invid corresponding to this xInvid object.</p> * * <p>invidPtr will contain an actual {@link * arlut.csd.ganymede.common.Invid} object if this <invid> * element could be matched against a pre-existing Invid on the * Ganymede server.</p> * * <p>If no server-side Invid can be found, invidPtr may instead * point to the {@link arlut.csd.ganymede.server.xmlobject} object * which will eventually be given the Invid we're interested in when * it is integrated into the server's data store.</p> */ private Object invidPtr; /** * The numeric object id, if specified in the XML file for this * xInvid. */ int num = -1; /* -- */ public xInvid(XMLItem item) { if (!item.matches("invid")) { getXSession().tell("Unrecognized XML item found when invid element expected: " + item); throw new NullPointerException("Bad item!"); } if (!item.isEmpty()) { getXSession().tell("Error, found a non-empty invid element: " + item); throw new NullPointerException("Bad item!"); } String typeString = item.getAttrStr("type"); if (typeString == null) { getXSession().tell("Missing or malformed invid type in element: " + item); throw new NullPointerException("Bad item!"); } else { typeString = typeString.intern(); } objectId = item.getAttrStr("id"); if (objectId == null) { Integer iNum = item.getAttrInt("num"); if (iNum != null) { num = iNum.intValue(); } else { getXSession().tell("Unknown object target in invid field element: " + item); throw new NullPointerException("Bad item!"); } } else { // XXX note that this is very expensive in terms of the // Permanent Generation memory zone when using Sun's HotSpot // VM in 1.4 or 1.5. We already did this in the Ganymede // DBStore itself to reduce long term heap usage, so this // isn't anything special here, but if you run the Ganymede // server on a VM that can't handle large blocks of interned // Strings, you might want to turn this one off in particular, // along with the StringDBField interning. XXX objectId = objectId.intern(); } try { typeId = getXSession().getTypeNum(typeString); } catch (NullPointerException ex) { getXSession().tell("Unknown target type " + typeString + " in invid field element: " + item); throw new NullPointerException("Bad item!"); } } /** * This method resolves and returns the Invid for this xInvid place * holder, talking to the server if necessary to resolve an id * string. */ public Invid getInvid() throws NotLoggedInException { return getInvid(true); } /** * <p>This method resolves and returns the Invid for this xInvid * place holder, talking to the server if necessary to resolve an id * string.</p> * * @param noReally If false, we're being called by the xmlfield * dereferenceInvids method, and we don't need to consider it a * failure if we can't get either an Invid or xmlobject reference. * If true, we're being called at a time when we really do need to * have some kind of proper lookup, so we'll throw an exception * if we can't find something to match this xInvid. */ public Invid getInvid(boolean noReally) throws NotLoggedInException { if (invidPtr != null) { if (invidPtr instanceof Invid) { return (Invid) invidPtr; } else if (invidPtr instanceof xmlobject) { Invid deref = ((xmlobject) invidPtr).getInvid(); if (deref != null) { invidPtr = deref; return (Invid) invidPtr; } else { return null; } } } if (objectId != null) { invidPtr = getXSession().getInvid(typeId, objectId); // XXX modifies xSession's objectStore if (invidPtr == null) { // we couldn't get a direct Invid reference, so set this // invid reference to point to the xmlobject that matches // it invidPtr = getXSession().getXMLObjectTarget(typeId, objectId); // if noReally is true, we aren't merely doing a // speculative dereference.. complain and shout. if (invidPtr == null && noReally) { // "xInvid.getInvid(): Couldn''t find any {0} objects labeled {1}." throw new RuntimeException(ts.l("getInvid.bad_label", getXSession().getTypeName(typeId), objectId)); } // even if we found an xmlobject, we still don't know the // Invid, so we'll return null for now return null; } else { return (Invid) invidPtr; } } else if (num != -1) { invidPtr = Invid.createInvid(typeId, num); return (Invid) invidPtr; } return null; } public String toString() { StringBuilder result = new StringBuilder(); /* -- */ result.append("<invid type=\""); result.append(getXSession().getTypeName(typeId)); result.append("\" "); if (objectId != null) { result.append("id=\""); result.append(objectId); result.append("\"/>"); } else if (num != -1) { result.append("num=\""); result.append(num); result.append("\"/>"); } else { result.append("id=\"???\"/>"); } return result.toString(); } /** * This is a very hack-ish way of getting a GanymedeXMLSession * reference without having to store it redundantly in all of the * (possibly hundreds of thousands) xInvid objects stored in the * server during xml transaction processing. */ private GanymedeXMLSession getXSession() { if (java.lang.Thread.currentThread() instanceof GanymedeXMLSession) { return (GanymedeXMLSession) java.lang.Thread.currentThread(); } return null; } } /** * <p>This class is used by the Ganymede XML client to represent * a password field data value.</p> * * <p>This class has five separate value fields, for the * possible password formats supported by Ganymede, but in fact * only one of them at a time should be anything other than * null, as setting any of these attributes on a Ganymede * password field clears the others, with the exception of the * paired NT/Samba hash formats.</p> */ class xPassword { String plaintext; String crypttext; String md5text; String apachemd5text; String lanman; String ntmd4; String sshatext; String shaunixcrypt; String bcrypt; /* -- */ public xPassword(XMLItem item) throws SAXException { if (!item.matches("password")) { getXSession().tell("Unrecognized XML item found when password element expected: " + item); throw new NullPointerException("Bad item!"); } if (!item.isEmpty()) { getXSession().tell("Error, found a non-empty password element: " + item); throw new NullPointerException("Bad item!"); } plaintext = item.getAttrStr("plaintext"); crypttext = item.getAttrStr("crypt"); md5text = item.getAttrStr("md5crypt"); apachemd5text = item.getAttrStr("apachemd5crypt"); lanman = item.getAttrStr("lanman"); ntmd4 = item.getAttrStr("ntmd4"); sshatext = item.getAttrStr("ssha"); shaunixcrypt = item.getAttrStr("shaUnixCrypt"); bcrypt = item.getAttrStr("bCrypt"); } public String toString() { StringBuilder result = new StringBuilder(); /* -- */ result.append("<password"); if (plaintext != null) { result.append(" plaintext=\""); result.append(plaintext); result.append("\""); } if (crypttext != null) { result.append(" crypt=\""); result.append(crypttext); result.append("\""); } if (md5text != null) { result.append(" md5crypt=\""); result.append(md5text); result.append("\""); } if (apachemd5text != null) { result.append(" apachemd5crypt=\""); result.append(apachemd5text); result.append("\""); } if (lanman != null) { result.append(" lanman=\""); result.append(lanman); result.append("\""); } if (ntmd4 != null) { result.append(" ntmd4=\""); result.append(ntmd4); result.append("\""); } if (sshatext != null) { result.append(" ssha=\""); result.append(sshatext); result.append("\""); } if (shaunixcrypt != null) { result.append(" shaUnixCrypt=\""); result.append(shaunixcrypt); result.append("\""); } if (bcrypt != null) { result.append(" bCrypt=\""); result.append(bcrypt); result.append("\""); } result.append("/>"); return result.toString(); } private GanymedeXMLSession getXSession() { if (java.lang.Thread.currentThread() instanceof GanymedeXMLSession) { return (GanymedeXMLSession) java.lang.Thread.currentThread(); } return null; } } /** * <p>This class is used by the Ganymede XML client to represent * a permission field value.</p> * * <p>xPerm are slightly recursive, in that a top-level xPerm * is used to represent the permissions for a type of object * at the object level, and contain in turn xPerm objects used * for the individual fields defined within that object.</p> */ class xPerm { /** * <p>String describing this xPerm's contents. This String * is held in XMLEncoded form. That is, spaces in the Ganymede * object type and/or field name have been replaced with * underscores.</p> */ String label = null; /** * <p>If this xPerm is representing an object type as a whole, * fields will map field names to xPerm objects. If this * xPerm is representing permissions for a field, this variable * will be null.</p> */ Map<String, xPerm> fields = null; boolean view = false; boolean edit = false; boolean create = false; boolean delete = false; /* -- */ /** * <p>xPerm constructor. When the constructor is called, the * xPerm reads the next item from the xmlclient's * {@link arlut.csd.ganymede.client.xmlclient#getNextItem() getNextItem()} * method and uses it to initialize the xPerm.</p> * * <p>If objectType is true, the xPerm constructor assumes that * it is reading an entire object type's permission data. In this * case, it will load the permission data for the object, including * all of the field permissions data contained within the top-level * object.</p> */ public xPerm(XMLItem item, boolean objectType) throws SAXException { while (!(item instanceof XMLElement)) { getXSession().tell("Unrecognized element encountered in xPerm constructor, skipping: " + item); item = getXSession().getNextItem(); } label = ((XMLElement) item).getName(); if (label != null) { label = label.intern(); } String permbits = item.getAttrStr("perm"); if (permbits == null) { getXSession().tell("No perm attributes found for xPerm item " + item); } else { view = (permbits.indexOf('v') != -1) || (permbits.indexOf('V') != -1); edit = (permbits.indexOf('e') != -1) || (permbits.indexOf('E') != -1); create = (permbits.indexOf('c') != -1) || (permbits.indexOf('C') != -1); delete = (permbits.indexOf('d') != -1) || (permbits.indexOf('D') != -1); } if (objectType && !item.isEmpty()) { fields = new HashMap<String, xPerm>(); item = getXSession().getNextItem(); while (!item.matchesClose(label) && !(item instanceof XMLEndDocument)) { xPerm fieldperm = new xPerm(item, false); fields.put(fieldperm.getName(), fieldperm); item = getXSession().getNextItem(); } if (item instanceof XMLEndDocument) { throw new RuntimeException("Ran into end of XML file while parsing permission object " + label); } } } public String getName() { return label; } public PermEntry getPermEntry() { return PermEntry.getPermEntry(view, edit, create, delete); } private GanymedeXMLSession getXSession() { if (java.lang.Thread.currentThread() instanceof GanymedeXMLSession) { return (GanymedeXMLSession) java.lang.Thread.currentThread(); } return null; } } /** * <p>This class is used by the Ganymede XML client to represent * a permission field value.</p> * * <p>xOption are slightly recursive, in that a top-level xOption * is used to represent the permissions for a type of object * at the object level, and contain in turn xOption objects used * for the individual fields defined within that object.</p> */ class xOption { /** * <p>String describing this xOption's contents. This String * is held in XMLEncoded form. That is, spaces in the Ganymede * object type and/or field name have been replaced with * underscores.</p> */ String label = null; /** * <p>If this xOption is representing an object type as a whole, * fields will map field names to xOption objects. If this * xOption is representing permissions for a field, this variable * will be null.</p> */ Map<String, xOption> fields = null; /** * <p>The option for this xOption.</p> */ SyncPrefEnum option; /* -- */ /** * <p>xOption constructor. When the constructor is called, the * xOption reads the next item from the xmlclient's * {@link arlut.csd.ganymede.client.xmlclient#getNextItem() getNextItem()} * method and uses it to initialize the xOption.</p> * * <p>If objectType is true, the xOption constructor assumes that * it is reading an entire object type's permission data. In this * case, it will load the permission data for the object, including * all of the field permissions data contained within the top-level * object.</p> */ public xOption(XMLItem item, boolean objectType) throws SAXException { while (!(item instanceof XMLElement)) { getXSession().tell("Unrecognized element encountered in xOption constructor, skipping: " + item); item = getXSession().getNextItem(); } label = ((XMLElement) item).getName(); if (label != null) { label = label.intern(); } SyncPrefEnum myOption = SyncPrefEnum.find(item.getAttrStr("option")); if (myOption == null) { getXSession().tell("No perm attributes found for xOption item " + item); } else { this.option = myOption; } if (objectType && !item.isEmpty()) { fields = new HashMap<String, xOption>(); item = getXSession().getNextItem(); while (!item.matchesClose(label) && !(item instanceof XMLEndDocument)) { xOption fieldoption = new xOption(item, false); fields.put(fieldoption.getName(), fieldoption); item = getXSession().getNextItem(); } if (item instanceof XMLEndDocument) { throw new RuntimeException("Ran into end of XML file while parsing options object " + label); } } } public String getName() { return label; } public SyncPrefEnum getOption() { return this.option; } private GanymedeXMLSession getXSession() { if (java.lang.Thread.currentThread() instanceof GanymedeXMLSession) { return (GanymedeXMLSession) java.lang.Thread.currentThread(); } return null; } }