blob: c4b57946b947e8ea9b6e2a2db9dda90f10a58219 [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 flex.messaging.MessageException;
import flex.messaging.io.amf.ASObject;
import flex.messaging.io.amf.ActionMessage;
import flex.messaging.io.amf.MessageBody;
import flex.messaging.io.amf.MessageHeader;
import flex.messaging.io.SerializationContext;
import java.util.Date;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import java.lang.reflect.Array;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.apache.xpath.CachedXPathAPI;
/**
* Verifies that a deserialized ActionMessage
* from an AMFX request matches the expected value.
*/
public abstract class DeserializationConfirmation
{
public static final int EXPECTED_VERSION = 3;
private IdentityHashMap knownObjects;
protected SerializationContext context;
protected DeserializationConfirmation()
{
}
private void init()
{
knownObjects = new IdentityHashMap(64);
}
public void setContext(SerializationContext context)
{
this.context = context;
}
public boolean isNegativeTest()
{
return false;
}
public boolean successMatches(ActionMessage m)
{
init();
try
{
boolean match = messagesMatch(m, getExpectedMessage());
return match;
}
catch (Exception ex)
{
return false;
}
}
public abstract ActionMessage getExpectedMessage();
public boolean negativeMatches(ActionMessage m)
{
init();
MessageException ex = null;
MessageBody body = m.getBody(0);
if ((body == null) || (body.getData() == null) || (!(body.getData() instanceof MessageException)))
return false;
else
ex = (MessageException)body.getData();
String message1 = ex.getMessage();
String message2 = getExpectedException().getMessage();
if (message1 == null && message2 == null)
return true;
if ((message1 == null && message2 != null) || (message1 != null && message2 == null))
return false;
return message1.equalsIgnoreCase(message2);
}
public abstract MessageException getExpectedException();
protected boolean headersMatch(MessageHeader header1, MessageHeader header2)
{
if ((header1 == null && header2 != null) || (header1 != null && header2 == null))
return false;
if (!header1.getName().equalsIgnoreCase(header2.getName()))
return false;
if (header1.getMustUnderstand() != header2.getMustUnderstand())
return false;
Object data1 = header1.getData();
Object data2 = header2.getData();
if (!headerValuesMatch(data1, data2))
return false;
return true;
}
protected boolean headerValuesMatch(Object o1, Object o2)
{
return valuesMatch(o1, o2);
}
protected boolean bodiesMatch(MessageBody body1, MessageBody body2)
{
if ((body1 == null && body2 != null) || (body1 != null && body2 == null))
return false;
if (!body1.getTargetURI().equalsIgnoreCase(body2.getTargetURI()))
return false;
if (!body1.getResponseURI().equalsIgnoreCase(body2.getResponseURI()))
return false;
Object data1 = body1.getData();
Object data2 = body2.getData();
if (!bodyValuesMatch(data1, data2))
return false;
return true;
}
protected boolean bodyValuesMatch(Object o1, Object o2)
{
return valuesMatch(o1, o2);
}
protected boolean messagesMatch(ActionMessage m1, ActionMessage m2)
{
if ((m1 == null && m2 != null) || (m2 == null && m1 != null))
return false;
if (m1.getVersion() != m2.getVersion())
return false;
int headerCount1 = m1.getHeaderCount();
int headerCount2 = m2.getHeaderCount();
if (headerCount1 != headerCount2)
return false;
for (int i = 0; i < headerCount1; i++)
{
MessageHeader header1 = m1.getHeader(i);
MessageHeader header2 = m2.getHeader(i);
if (!headersMatch(header1, header2))
return false;
}
int bodyCount1 = m1.getBodyCount();
int bodyCount2 = m2.getBodyCount();
if (bodyCount1 != bodyCount2)
return false;
for (int i = 0; i < bodyCount1; i++)
{
MessageBody body1 = m1.getBody(i);
MessageBody body2 = m2.getBody(i);
if (!bodiesMatch(body1, body2))
return false;
}
return true;
}
protected boolean valuesMatch(Object o1, Object o2)
{
if (o1 == null && o2 == null)
return true;
if (o1 instanceof ASObject)
{
return objectsMatch((ASObject)o1, (Map)o2);
}
else if (o1 instanceof Map)
{
return mapsMatch((Map)o1, (Map)o2);
}
else if (o1 instanceof List)
{
return listsMatch((List)o1, (List)o2);
}
else if (o1.getClass().isArray())
{
return arraysMatch(o1, o2);
}
else if (o1 instanceof Document)
{
return documentsMatch((Document)o1, (Document)o2);
}
else if (o1 != null)
{
if (hasComplexChildren(o1))
{
// We avoid checking a complex/custom types twice in
// case of circular dependencies
Object known = knownObjects.get(o1);
if (known != null)
{
return true;
}
else
{
knownObjects.put(o1, o2);
}
}
// Special case Double and Integer
if (o1 instanceof Double && o2 instanceof Integer)
return new Integer(((Double)o1).intValue()).equals(o2);
return o1.equals(o2);
}
return false;
}
protected boolean documentsMatch(Document doc1, Document doc2)
{
boolean match = false;
try
{
CachedXPathAPI xpath1 = new CachedXPathAPI();
CachedXPathAPI xpath2 = new CachedXPathAPI();
Node root1 = xpath1.selectSingleNode(doc1, "/");
Node root2 = xpath2.selectSingleNode(doc2, "/");
if (!nodesMatch(xpath1, root1, xpath2, root2))
{
return false;
}
match = true;
}
catch (Throwable t)
{
throw new MessageException("Error comparing XML Documents: " + t.getMessage(), t);
}
return match;
}
protected boolean nodesMatch(CachedXPathAPI xpath1, Node node1, CachedXPathAPI xpath2, Node node2)
{
boolean match = false;
try
{
NodeList list1 = xpath1.selectNodeList(node1, "*");
NodeList list2 = xpath2.selectNodeList(node2, "*");
if (list1.getLength() == list2.getLength())
{
for (int i = 0; i < list1.getLength(); i++)
{
Node n1 = list1.item(i);
Node n2 = list2.item(i);
NodeList attributes1 = xpath1.selectNodeList(n1, "@*");
NodeList attributes2 = xpath2.selectNodeList(n2, "@*");
if (!attributesMatch(attributes1, attributes2))
{
return false;
}
if (!nodesMatch(xpath1, n1, xpath2, n2))
{
return false;
}
}
match = true;
}
}
catch (Throwable t)
{
throw new MessageException("Error comparing XML nodes: " + t.getMessage(), t);
}
return match;
}
protected boolean attributesMatch(NodeList attributes1, NodeList attributes2)
{
if (attributes1 == null && attributes2 == null)
return true;
boolean match = false;
if (attributes1.getLength() == attributes2.getLength())
{
for (int i = 0; i < attributes1.getLength(); i++)
{
Node a1 = attributes1.item(i);
Node a2 = attributes2.item(i);
if (!stringValuesMatch(a1.getNodeName(), a2.getNodeName()))
return false;
if (!stringValuesMatch(a1.getNodeValue(), a2.getNodeValue()))
return false;
}
match = true;
}
return match;
}
protected boolean objectsMatch(ASObject aso1, Map aso2)
{
String type1 = aso1.getType();
// We may have an anonymous ASObject and an ECMA Array
// which get serialized in the same way, i.e. a Map,
// so we ignore this difference
if (type1 != null)
{
String type2 = ((ASObject)aso2).getType();
if (!stringValuesMatch(type1, type2))
return false;
}
return mapsMatch(aso1, aso2);
}
protected boolean stringValuesMatch(String str1, String str2)
{
if (str1 == null && str2 == null)
return true;
if (str1 != null && !str1.equals(str2))
return false;
return true;
}
protected boolean mapsMatch(Map map1, Map map2)
{
if (map1.size() != map2.size())
return false;
// We avoid checking a Map twice in case
// of circular dependencies
Object known = knownObjects.get(map1);
if (known != null)
{
return true;
}
else
{
knownObjects.put(map1, map2);
}
Iterator it = map1.keySet().iterator();
while (it.hasNext())
{
Object next = it.next();
if (next instanceof String)
{
String key = (String)next;
Object val1 = map1.get(key);
Object val2 = map2.get(key);
if (!valuesMatch(val1, val2))
return false;
}
else
{
return false;
}
}
return true;
}
protected boolean listsMatch(List list1, List list2)
{
if (list1.size() != list2.size())
return false;
// We avoid checking a List twice in case
// of circular dependencies
Object known = knownObjects.get(list1);
if (known != null)
{
return true;
}
else
{
knownObjects.put(list1, list2);
}
for (int i = 0; i < list1.size(); i++)
{
Object o1 = list1.get(i);
Object o2 = list2.get(i);
if (!valuesMatch(o1, o2))
return false;
}
return true;
}
protected boolean arraysMatch(Object array1, Object array2)
{
int len1 = Array.getLength(array1);
int len2 = Array.getLength(array2);
if (len1 != len2)
return false;
// We avoid checking an Array twice in case
// of circular dependencies
Object known = knownObjects.get(array1);
if (known != null)
{
return true;
}
else
{
knownObjects.put(array1, array2);
}
for (int i = 0; i < len1; i++)
{
Object o1 = Array.get(array1, i);
Object o2 = Array.get(array2, i);
if (!valuesMatch(o1, o2))
return false;
}
return true;
}
protected void addToList(Object list, int index, Object value)
{
if (list instanceof List)
((List)list).add(value);
else if (list.getClass().isArray())
Array.set(list, index, value);
}
protected Object getFromList(Object list, int index)
{
if (list instanceof List)
return ((List)list).get(index);
else
return Array.get(list, index);
}
protected Object createList(int length)
{
if (context.legacyCollection)
return new ArrayList(length);
else
return new Object[length];
}
private static boolean hasComplexChildren(Object o)
{
if (o instanceof String
|| o instanceof Boolean
|| o instanceof Number
|| o instanceof Character
|| o instanceof Date)
{
return false;
}
return true;
}
}