All Downloads are FREE. Search and download functionalities are using the official Maven repository.

flex.messaging.io.amfx.AmfxInput Maven / Gradle / Ivy

There is a newer version: 4.8.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package flex.messaging.io.amfx;

import java.io.ByteArrayInputStream;
import java.io.Externalizable;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Date;
import java.util.Dictionary;
import java.util.EmptyStackException;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import org.xml.sax.Attributes;

import flex.messaging.MessageException;
import flex.messaging.io.AbstractProxy;
import flex.messaging.io.ArrayCollection;
import flex.messaging.io.BeanProxy;
import flex.messaging.io.ClassAliasRegistry;
import flex.messaging.io.PropertyProxy;
import flex.messaging.io.PropertyProxyRegistry;
import flex.messaging.io.SerializationContext;
import flex.messaging.io.SerializationException;
import flex.messaging.io.TypeMarshallingContext;
import flex.messaging.io.amf.ASObject;
import flex.messaging.io.amf.ActionMessage;
import flex.messaging.io.amf.Amf3Input;
import flex.messaging.io.amf.AmfTrace;
import flex.messaging.io.amf.MessageBody;
import flex.messaging.io.amf.MessageHeader;
import flex.messaging.util.ClassUtil;
import flex.messaging.util.Hex;
import flex.messaging.util.XMLUtil;

/**
 * Context for AMFX specific SAX handler.Contains start and end tag handlers for each of
 * the XML elements that occur in an AMFX request. The AmfxMessageDeserializer enforces
 * a naming convention for these handlers of xyz_start for the start handler and xyz_end
 * for the end handler of element xyz.
 *
 * Note that this context MUST be reset if reused between AMFX packet parsings.
 *
 * @see AmfxMessageDeserializer
 * @see AmfxOutput
 */
public class AmfxInput
{
    /**
     * This is the initial capacity that will be used for AMF arrays that have
     * length greater than 1024.
     */
    public static final int INITIAL_ARRAY_CAPACITY = 1024;

    private SerializationContext context;
    private BeanProxy beanproxy = new BeanProxy();

    private final ArrayList objectTable;
    private final ArrayList stringTable;
    private final ArrayList traitsTable;

    private StringBuffer text;

    private ActionMessage message;
    private MessageHeader currentHeader;
    private MessageBody currentBody;
    private Stack objectStack;
    private Stack proxyStack;
    private Stack arrayPropertyStack;
    private Stack ecmaArrayIndexStack;
    private Stack strictArrayIndexStack;
    private Stack dictionaryStack;
    private Stack traitsStack;
    private boolean isStringReference;
    private boolean isTraitProperty;

    /*
     *  DEBUG LOGGING
     */
    protected boolean isDebug;
    protected AmfTrace trace;

    /**
     * Constructor.
     * Construct an AmfxInput by passing in a SerialziationContext object
     * 

* @param context the SerialziationContext object */ public AmfxInput(SerializationContext context) { this.context = context; stringTable = new ArrayList(64); objectTable = new ArrayList(64); traitsTable = new ArrayList(10); objectStack = new Stack(); proxyStack = new Stack(); arrayPropertyStack = new Stack(); dictionaryStack = new Stack(); strictArrayIndexStack = new Stack(); ecmaArrayIndexStack = new Stack(); traitsStack = new Stack(); text = new StringBuffer(32); } /** * Reset the AmfxInput object. */ public void reset() { stringTable.clear(); objectTable.clear(); traitsTable.clear(); objectStack.clear(); proxyStack.clear(); arrayPropertyStack.clear(); dictionaryStack.clear(); traitsStack.clear(); currentBody = null; currentHeader = null; TypeMarshallingContext marshallingContext = TypeMarshallingContext.getTypeMarshallingContext(); marshallingContext.reset(); } /** * Set Debug trace. *

* @param trace current AmfTrace setting */ public void setDebugTrace(AmfTrace trace) { this.trace = trace; isDebug = this.trace != null; } /** * Set Action Message. *

* @param msg current ActionMessage */ public void setActionMessage(ActionMessage msg) { message = msg; } /** * Read object from the AmfxInput object. *

* @return currently return null, not supported * @throws IOException when reading the object has the IOException */ public Object readObject() throws IOException { return null; } /** * Append a string to text. * XML Considerations *

* @param s the String to append */ public void text(String s) { text.append(s); } // // AMFX Message Structure // /** * Start the amfx process by setting the ActionMessage version. *

* @param attributes current Attributes */ public void start_amfx(Attributes attributes) { String ver = attributes.getValue("ver"); int version = ActionMessage.CURRENT_VERSION; if (ver != null) { try { version = Integer.parseInt(ver); } catch (NumberFormatException ex) { throw new MessageException("Unknown version: " + ver); } } if (isDebug) trace.version(version); message.setVersion(version); } /** * End the Amfx process. *

*/ public void end_amfx() { } /** * Start the process of message headers. *

* @param attributes current Attributes */ public void start_header(Attributes attributes) { if (currentHeader != null || currentBody != null) throw new MessageException("Unexpected header tag."); currentHeader = new MessageHeader(); String name = attributes.getValue("name"); currentHeader.setName(name); String mu = attributes.getValue("mustUnderstand"); boolean mustUnderstand = false; if (mu != null) { mustUnderstand = Boolean.valueOf(mu).booleanValue(); currentHeader.setMustUnderstand(mustUnderstand); } if (isDebug) trace.startHeader(name, mustUnderstand, message.getHeaderCount()); } /** * End process of message headers. *

*/ public void end_header() { message.addHeader(currentHeader); currentHeader = null; if (isDebug) trace.endHeader(); } /** * Start process of the message body. *

* @param attributes current Attributes */ public void start_body(Attributes attributes) { if (currentBody != null || currentHeader != null) throw new MessageException("Unexpected body tag."); currentBody = new MessageBody(); if (isDebug) trace.startMessage("", "", message.getBodyCount()); } /** * End process of the message body. *

*/ public void end_body() { message.addBody(currentBody); currentBody = null; if (isDebug) trace.endMessage(); } // // ActionScript Types // /** * Start process of the Action Script type Array. *

* @param attributes current Attributes */ public void start_array(Attributes attributes) { int length = 10; String len = attributes.getValue("length"); if (len != null) { try { len = len.trim(); length = Integer.parseInt(len); if (length < 0) throw new NumberFormatException(); } catch (NumberFormatException ex) { throw new MessageException("Invalid array length: " + len); } } String ecma = attributes.getValue("ecma"); boolean isECMA = "true".equalsIgnoreCase(ecma); Object array; boolean useListTemporarily = false; if (isECMA) { array = ClassUtil.createDefaultInstance(HashMap.class, null, true /*validate*/); } else { // Don't instantiate List/Array right away with the supplied size if it is more than // INITIAL_ARRAY_CAPACITY in case the supplied size has been tampered. This at least // requires the user to pass in the actual objects for the List/Array to grow beyond. if (context.legacyCollection || length > INITIAL_ARRAY_CAPACITY) { useListTemporarily = !context.legacyCollection; ClassUtil.validateCreation(ArrayList.class); int initialCapacity = length < INITIAL_ARRAY_CAPACITY? length : INITIAL_ARRAY_CAPACITY; array = new ArrayList(initialCapacity); } else { ClassUtil.validateCreation(Object[].class); array = new Object[length]; } } array = setValue(array); ecmaArrayIndexStack.push(new int[]{0}); strictArrayIndexStack.push(new int[]{0}); objectTable.add(array); // Don't add the array to the object stack if the List is being used temporarily // for the length tampering detection. In that case, setValue method will add // an ObjectPropertyValueTuple to the object stack instead. if (!useListTemporarily) objectStack.push(array); proxyStack.push(null); if (isECMA) { if (isDebug) trace.startECMAArray(objectTable.size() - 1); } else { if (isDebug) trace.startAMFArray(objectTable.size() - 1); } } /** * End process of Action Script type Array. *

*/ public void end_array() { try { Object obj = objectStack.pop(); if (obj instanceof ObjectPropertyValueTuple) { // Means List was being used temporarily to guard against array length tampering. // Convert back to Object array and set it on the parent object using the proxy // and property saved in the tuple. ObjectPropertyValueTuple tuple = (ObjectPropertyValueTuple)obj; int objectId = objectTable.indexOf(tuple.value); Object newValue = ((ArrayList)tuple.value).toArray(); objectTable.set(objectId, newValue); tuple.proxy.setValue(tuple.obj, tuple.property, newValue); } proxyStack.pop(); ecmaArrayIndexStack.pop(); strictArrayIndexStack.pop(); } catch (EmptyStackException ex) { throw new MessageException("Unexpected end of array"); } if (isDebug) trace.endAMFArray(); } public void start_dictionary(Attributes attributes) { int length = 10; String len = attributes.getValue("length"); if (len != null) { try { len = len.trim(); length = Integer.parseInt(len); if (length < 0) throw new NumberFormatException(); } catch (NumberFormatException ex) { throw new MessageException("Invalid array length: " + len); } } Hashtable dictionary = (Hashtable)ClassUtil.createDefaultInstance(Hashtable.class, null, true /*validate*/); setValue(dictionary); objectTable.add(dictionary); objectStack.push(dictionary); proxyStack.push(null); if (isDebug) trace.startAMFDictionary(objectTable.size() - 1); } public void end_dictionary() { try { objectStack.pop(); proxyStack.pop(); } catch (EmptyStackException ex) { throw new MessageException("Unexpected end of dictionary"); } if (isDebug) trace.endAMFDictionary(); } // 010F0A /** * Start process of the Action Script type ByteArray. *

* @param attributes current Attributes */ public void start_bytearray(Attributes attributes) { text.delete(0, text.length()); } /** * End process of the Action Script type ByteArray. *

*/ public void end_bytearray() { ClassUtil.validateCreation(byte[].class); String bs = text.toString().trim(); Hex.Decoder decoder = new Hex.Decoder(); decoder.decode(bs); byte[] value = decoder.drain(); setValue(value); if (isDebug) trace.startByteArray(objectTable.size() - 1, bs.length()); } /** * Start process of the Action Script type Date. *

* @param attributes current Attributes */ public void start_date(Attributes attributes) { text.delete(0, text.length()); } /** * End process of the Action Script type Date. *

*/ public void end_date() { ClassUtil.validateCreation(Date.class); String d = text.toString().trim(); try { long l = Long.parseLong(d); Date date = new Date(l); setValue(date); objectTable.add(date); //Dates can be sent by reference if (isDebug) trace.write(date); } catch (NumberFormatException ex) { throw new MessageException("Invalid date: " + d); } } /** * Start process of the Action Script type Double. *

* @param attributes current Attributes */ public void start_double(Attributes attributes) { text.delete(0, text.length()); } /** * End process of the Action Script type Double. *

*/ public void end_double() { ClassUtil.validateCreation(Double.class); String ds = text.toString().trim(); try { Double d = Double.valueOf(ds); setValue(d); if (isDebug) trace.write(d.doubleValue()); } catch (NumberFormatException ex) { throw new MessageException("Invalid double: " + ds); } } /** * Start process of the Action Script type False. *

* @param attributes current Attributes */ public void start_false(Attributes attributes) { ClassUtil.validateCreation(Boolean.class); setValue(Boolean.FALSE); if (isDebug) trace.write(false); } /** * Start process of the Action Script type False. *

*/ public void end_false() { } /** * Start process of Item. *

* @param attributes current Attributes */ public void start_item(Attributes attributes) { String name = attributes.getValue("name"); if (name != null) { name = name.trim(); if (name.length() <= 0) throw new MessageException("Array item names cannot be the empty string."); char c = name.charAt(0); if (!(Character.isLetterOrDigit(c) || c == '_')) throw new MessageException("Invalid item name: " + name + ". Array item names must start with a letter, a digit or the underscore '_' character."); } else { throw new MessageException("Array item must have a name attribute."); } //Check that we're expecting an ECMA array Object o = objectStackPeek(); if (!(o instanceof Map)) { throw new MessageException("Unexpected array item name: " + name + ". Please set the ecma attribute to 'true'."); } arrayPropertyStack.push(name); } /** * End process of Item. *

*/ public void end_item() { arrayPropertyStack.pop(); } /** * Start process of the Action Script type Int. *

* @param attributes current Attributes */ public void start_int(Attributes attributes) { text.delete(0, text.length()); } /** * End process of the Action Script type Int. *

*/ public void end_int() { ClassUtil.validateCreation(Integer.class); String is = text.toString().trim(); try { Integer i = Integer.valueOf(is); setValue(i); if (isDebug) trace.write(i.intValue()); } catch (NumberFormatException ex) { throw new MessageException("Invalid int: " + is); } } /** * Start process of the Action Script type NULL. *

* @param attributes current Attributes */ public void start_null(Attributes attributes) { setValue(null); if (isDebug) trace.writeNull(); } /** * Start process of the Action Script type NULL. *

*/ public void end_null() { } // /** * Start process of type Object. *

* @param attributes current Attributes */ public void start_object(Attributes attributes) { PropertyProxy proxy = null; String type = attributes.getValue("type"); if (type != null) { type = type.trim(); } Object object; if (type != null && type.length() > 0) { // Check for any registered class aliases String aliasedClass = ClassAliasRegistry.getRegistry().getClassName(type); if (aliasedClass != null) type = aliasedClass; if (type == null || type.length() == 0) { object = ClassUtil.createDefaultInstance(ASObject.class, null, true /*validate*/); } else if (type.startsWith(">")) // Handle [RemoteClass] (no server alias) { object = ClassUtil.createDefaultInstance(ASObject.class, null, true /*validate*/); ((ASObject)object).setType(type); } else if (context.instantiateTypes || type.startsWith("flex.")) { object = getInstantiatedObject(type, proxy); } else { // Just return type info with an ASObject... object = ClassUtil.createDefaultInstance(ASObject.class, null, true /*validate*/); ((ASObject)object).setType(type); } } else { // TODO: QUESTION: Pete, Investigate why setValue for ASObject is delayed to endObject ClassUtil.validateCreation(ASObject.class); object = new ASObject(type); } if (proxy == null) proxy = PropertyProxyRegistry.getProxyAndRegister(object); objectStack.push(object); proxyStack.push(proxy); objectTable.add(object); if (isDebug) trace.startAMFObject(type, objectTable.size() - 1); } //

/** * End process of type Object. *

*/ public void end_object() { if (!traitsStack.empty()) traitsStack.pop(); if (!objectStack.empty()) { Object obj = objectStack.pop(); PropertyProxy proxy = (PropertyProxy) proxyStack.pop(); Object newObj = proxy == null ? obj : proxy.instanceComplete(obj); if (newObj != obj) { int i; // Find the index in the list of the old objct and replace it with // the new one. for (i = 0; i < objectTable.size(); i++) if (objectTable.get(i) == obj) break; if (i != objectTable.size()) objectTable.set(i, newObj); obj = newObj; } setValue(obj); } else { throw new MessageException("Unexpected end of object."); } if (isDebug) trace.endAMFObject(); } /** * Start process of reference. *

* @param attributes current Attributes */ public void start_ref(Attributes attributes) { String id = attributes.getValue("id"); if (id != null) { try { int i = Integer.parseInt(id); Object o = objectTable.get(i); setValue(o); if (isDebug) trace.writeRef(i); } catch (NumberFormatException ex) { throw new MessageException("Invalid object reference: " + id); } catch (IndexOutOfBoundsException ex) { throw new MessageException("Unknown object reference: " + id); } } else { throw new MessageException("Unknown object reference: " + id); } } /** * End process of reference. *

*/ public void end_ref() { } /** * Start process of the Action Script type String. *

* @param attributes current Attributes */ public void start_string(Attributes attributes) { String id = attributes.getValue("id"); if (id != null) { isStringReference = true; try { int i = Integer.parseInt(id); String s = (String)stringTable.get(i); if (isTraitProperty) { TraitsContext traitsContext = (TraitsContext)traitsStack.peek(); traitsContext.add(s); } else { ClassUtil.validateCreation(String.class); setValue(s); } } catch (NumberFormatException ex) { throw new MessageException("Invalid string reference: " + id); } catch (IndexOutOfBoundsException ex) { throw new MessageException("Unknown string reference: " + id); } } else { text.delete(0, text.length()); isStringReference = false; } } /** * End process of the Action Script type String. *

*/ public void end_string() { if (!isStringReference) { String s = text.toString(); // Special case the empty string as it isn't counted as in // the string reference table if (s.length() > 0) { // Traits won't contain CDATA if (!isTraitProperty) s = unescapeCloseCDATA(s); stringTable.add(s); } if (isTraitProperty) { TraitsContext traitsContext = (TraitsContext)traitsStack.peek(); traitsContext.add(s); } else { ClassUtil.validateCreation(String.class); setValue(s); if (isDebug) trace.writeString(s); } } } /** * Start process of Traits. *

* @param attributes current Attributes */ public void start_traits(Attributes attributes) { if (!objectStack.empty()) { List traitsList = new ArrayList(); TraitsContext traitsContext = new TraitsContext(traitsList); traitsStack.push(traitsContext); String id = attributes.getValue("id"); if (id != null) { try { int i = Integer.parseInt(id); List l = (List)traitsTable.get(i); Iterator it = l.iterator(); while (it.hasNext()) { String prop = (String)it.next(); traitsList.add(prop); } } catch (NumberFormatException ex) { throw new MessageException("Invalid traits reference: " + id); } catch (IndexOutOfBoundsException ex) { throw new MessageException("Unknown traits reference: " + id); } } else { boolean externalizable = false; String ext = attributes.getValue("externalizable"); if (ext != null) { externalizable = "true".equals(ext.trim()); } Object obj = objectStackPeek(); if (externalizable && !(obj instanceof Externalizable)) { //Class '{className}' must implement java.io.Externalizable to receive client IExternalizable instances. SerializationException ex = new SerializationException(); ex.setMessage(10305, new Object[] {obj.getClass().getName()}); throw ex; } traitsTable.add(traitsList); } isTraitProperty = true; } else { throw new MessageException("Unexpected traits"); } } /** * End process of Traits. *

*/ public void end_traits() { isTraitProperty = false; } /** * Start process of the Action Script type True. *

* @param attributes current Attributes */ public void start_true(Attributes attributes) { ClassUtil.validateCreation(Boolean.class); setValue(Boolean.TRUE); if (isDebug) trace.write(true); } /** * Start process of the Action Script type True. *

*/ public void end_true() { } /** * Start process of the Action Script type undefined. *

* @param attributes current Attributes */ public void start_undefined(Attributes attributes) { setValue(null); if (isDebug) trace.writeUndefined(); } /** * End process of the Action Script type undefined. *

*/ public void end_undefined() { } /** * Start process of XML. *

* @param attributes current Attributes */ public void start_xml(Attributes attributes) { text.delete(0, text.length()); } /** * End process of XML. *

*/ public void end_xml() { String xml = text.toString(); xml = unescapeCloseCDATA(xml); // Validation performed in XMLUtil#stringToDocument. Object value = XMLUtil.stringToDocument(xml, !(context.legacyXMLNamespaces)); setValue(value); } private String unescapeCloseCDATA(String s) { //Only check if string could possibly have an encoded closing for a CDATA "]]>" if (s.length() > 5 && s.indexOf("]]>") != -1) { s = s.replaceAll("]]>", "]]>"); } return s; } private Object setValue(Object value) { if (objectStack.empty()) { if (currentHeader != null) currentHeader.setData(value); else if (currentBody != null) currentBody.setData(value); else throw new MessageException("Unexpected value: " + value); return value; } // ActionScript Data Object obj = objectStackPeek(); // if (obj instanceof Externalizable) { if (value != null && value.getClass().isArray() && Byte.TYPE.equals(value.getClass().getComponentType())) { Externalizable extern = (Externalizable)obj; Amf3Input objIn = new Amf3Input(context); byte[] ba = (byte[])value; ByteArrayInputStream baIn = new ByteArrayInputStream(ba); try { //objIn.setDebugTrace(trace); objIn.setInputStream(baIn); extern.readExternal(objIn); } catch (ClassNotFoundException ex) { throw new MessageException("Error while reading Externalizable class " + extern.getClass().getName(), ex); } catch (IOException ex) { throw new MessageException("Error while reading Externalizable class " + extern.getClass().getName(), ex); } finally { try { objIn.close(); } catch (IOException ex) { } } } else { throw new MessageException("Error while reading Externalizable class. Value must be a byte array."); } } // else if (obj instanceof ASObject) { String prop; TraitsContext traitsContext = (TraitsContext)traitsStack.peek(); try { prop = traitsContext.next(); } catch (IndexOutOfBoundsException ex) { throw new MessageException("Object has no trait info for value: " + value); } ASObject aso = (ASObject)obj; ClassUtil.validateAssignment(aso, prop, value); aso.put(prop, value); if (isDebug) trace.namedElement(prop); } // in ArrayList form else if (obj instanceof ArrayList && !(obj instanceof ArrayCollection)) { ArrayList list = (ArrayList)obj; ClassUtil.validateAssignment(list, list.size(), value); list.add(value); if (isDebug) trace.arrayElement(list.size() - 1); } // in Object[] form else if (obj.getClass().isArray()) { if (!strictArrayIndexStack.empty()) { int[] indexObj = (int[])strictArrayIndexStack.peek(); int index = indexObj[0]; if (Array.getLength(obj) > index) { ClassUtil.validateAssignment(obj, index, value); Array.set(obj, index, value); } else { throw new MessageException("Index out of bounds at: " + index + " cannot set array value: " + value + ""); } indexObj[0]++; } } else if (obj instanceof Map) { if (obj instanceof Dictionary) // { Dictionary dict = (Dictionary)obj; if (!dictionaryStack.empty()) { Object key = dictionaryStack.pop(); if (isDebug) trace.addDictionaryEquals(); ClassUtil.validateAssignment(dict, key.toString(), value); dict.put(key, value); } else { if (isDebug) trace.startDictionaryElement(); dictionaryStack.push(value); } return value; } Map map = (Map)obj; // // if (!arrayPropertyStack.empty()) { String prop = (String)arrayPropertyStack.peek(); ClassUtil.validateAssignment(map, prop, value); map.put(prop, value); if (isDebug) trace.namedElement(prop); return value; } // Mixed content, auto-generate string for ECMA Array index if (!ecmaArrayIndexStack.empty()) { int[] index = (int[])ecmaArrayIndexStack.peek(); String prop = String.valueOf(index[0]); index[0]++; ClassUtil.validateAssignment(map, prop, value); map.put(prop, value); if (isDebug) trace.namedElement(prop); } } // else { value = setObjectValue(obj, value); } return value; } private Object setObjectValue(Object obj, Object value) { String prop; TraitsContext traitsContext = (TraitsContext)traitsStack.peek(); try { prop = traitsContext.next(); } catch (IndexOutOfBoundsException ex) { throw new MessageException("Object has no trait info for value: " + value, ex); } try { // Then check if there's a more suitable proxy now that we have an instance PropertyProxy proxy = (PropertyProxy) proxyStack.peek(); if (proxy == null) proxy = beanproxy; proxy.setValue(obj, prop, value); // Reset value in case it was changed by the proxy except empty lists. // Proxy converts empty lists to empty arrays in remoting messages. // Emply arrays are useless as containers and cause errors. if (!(value instanceof ArrayList && ((ArrayList)value).size() == 0)) { Object newValue = proxy.getValue(obj, prop); if (value != newValue) value = newValue; } if (value instanceof ArrayList && !(value instanceof ArrayCollection) && !context.legacyCollection) { // Means List is being used temporarily, see start_array method for explanation. objectStack.push(new ObjectPropertyValueTuple(proxy, obj, prop, value)); } } catch (Exception ex) { throw new MessageException("Failed to set property '" + prop + "' with value: " + value, ex); } if (isDebug) trace.namedElement(prop); return value; } /** * Utility method to peek the object in the object stack which can be an Object * or an ObjectPropertyValueTuple. * * @return The Object at the top of the object stack. */ private Object objectStackPeek() { Object obj = objectStack.peek(); return (obj instanceof ObjectPropertyValueTuple)? ((ObjectPropertyValueTuple)obj).value : obj; } private Object getInstantiatedObject(String className, PropertyProxy proxy) { Class desiredClass = null; try { desiredClass = AbstractProxy.getClassFromClassName(className); } catch (MessageException me) { // Type not found but don't mind using ASObject for the missing type. if (me.getCode().startsWith(MessageException.CODE_SERVER_RESOURCE_UNAVAILABLE) && context.createASObjectForMissingType) { ASObject object = (ASObject)ClassUtil.createDefaultInstance(ASObject.class, null, true /*validate*/); object.setType(className); return object; } throw me; // Rethrow. } // Type exists. proxy = PropertyProxyRegistry.getRegistry().getProxyAndRegister(desiredClass); return proxy == null? ClassUtil.createDefaultInstance(desiredClass, null, true /*validate*/) : proxy.createInstance(className); // Validation is performed in the proxy. } /** * Helper class used in the case where the supplied array length is more than the * INITIAL_ARRAY_CAPACITY. In that case, the List/Object[] on the server is not * initialized with that length in case the supplied length has been tampered. * Instead, a temporary List of length INITIAL_ARRAY_CAPACITY is constructed and List * grows as array members are supplied from the client. This way the user is required to * pass in the actual array members for the List to grow. This helper class is needed to * convert the temporary List into Object[] if needed. */ private static class ObjectPropertyValueTuple { private PropertyProxy proxy; private Object obj; private String property; private Object value; private ObjectPropertyValueTuple(PropertyProxy proxy, Object obj, String property, Object value) { this.proxy = proxy; this.obj = obj; this.property = property; this.value = value; } } private class TraitsContext { private List traits; private int counter; private TraitsContext(List traits) { this.traits = traits; } private void add(String trait) { trait = trait.trim(); if (trait.length() <= 0) throw new MessageException("Traits cannot be the empty string."); char c = trait.charAt(0); if (!(Character.isLetterOrDigit(c) || c == '_')) throw new MessageException("Invalid trait name: " + trait + ". Object property names must start with a letter, a digit or the underscore '_' character."); traits.add(trait); } private String next() { String trait = (String)traits.get(counter); counter++; return trait; } } }