blob: 83911ff3b3ff1da4f0c76f77cb6f2d81ee547314 [file] [log] [blame]
/*
* 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 <code>SerialziationContext</code> object
*
* @param context the <code>SerialziationContext</code> 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 <code>AmfTrace</code> setting
*/
public void setDebugTrace(AmfTrace trace)
{
this.trace = trace;
isDebug = this.trace != null;
}
/**
* Set Action Message.
*
* @param msg current <code>ActionMessage</code>
*/
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();
}
// <bytearray>010F0A</bytearray>
/**
* 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()
{
}
// <object type="com.my.Class">
/**
* 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);
}
// </object>
/**
* 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),
context.allowXmlDoctypeDeclaration, context.allowXmlExternalEntityExpansion);
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("]]&gt;") != -1)
{
s = s.replaceAll("]]&gt;", "]]>");
}
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();
// <object type="..."> <traits externalizable="true">
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.");
}
}
// <object>
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);
}
// <array ecma="false"> 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);
}
// <array ecma="false"> 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>
{
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; // <array ecma="true">
// <item name="prop">
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);
}
}
// <object type="...">
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 <tt>ObjectPropertyValueTuple</tt>.
*
* @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;
}
}
}