blob: d7ca89f9074e7a9c62799646217584a9c821dd9a [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 org.apache.axis2.rmi.databind;
import org.apache.axis2.rmi.Configurator;
import org.apache.axis2.rmi.exception.MetaDataPopulateException;
import org.apache.axis2.rmi.exception.SchemaGenerationException;
import org.apache.axis2.rmi.exception.XmlParsingException;
import org.apache.axis2.rmi.metadata.AttributeField;
import org.apache.axis2.rmi.metadata.ElementField;
import org.apache.axis2.rmi.metadata.Operation;
import org.apache.axis2.rmi.metadata.Parameter;
import org.apache.axis2.rmi.metadata.Type;
import org.apache.axis2.rmi.metadata.impl.TypeImpl;
import org.apache.axis2.rmi.types.MapType;
import org.apache.axis2.rmi.util.Constants;
import org.apache.axis2.rmi.util.JavaTypeToQNameMap;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class XmlStreamParser {
private Configurator configurator;
private Map qNameToTypeMap;
private SimpleTypeHandler simpleTypeHandler;
private Class simpleTypeHandlerClass;
private boolean readerPriorAccess;
private boolean priorAccessDetermined = false;
public XmlStreamParser(Map processedTypeMap,
Configurator configurator,
Map schemaMap) {
this.configurator = configurator;
this.simpleTypeHandler = this.configurator.getSimpleTypeHandler();
this.simpleTypeHandlerClass = this.simpleTypeHandler.getClass();
try {
populateQNameToTypeMap(processedTypeMap, schemaMap);
} catch (MetaDataPopulateException e) {
// TODO: what to do this exceptions is not going to happen
} catch (SchemaGenerationException e) {
// TODO: what to do this exceptions is not going to happen
}
}
private void populateQNameToTypeMap(Map processedTypeMap, Map schemaMap)
throws MetaDataPopulateException,
SchemaGenerationException {
// first add all the proceced type map values
Set defaultTypeMapKeys = JavaTypeToQNameMap.getKeys();
Class typeClass;
for (Iterator iter = defaultTypeMapKeys.iterator(); iter.hasNext();) {
typeClass = (Class) iter.next();
if (!processedTypeMap.containsKey(typeClass)) {
Type newType = new TypeImpl(typeClass);
processedTypeMap.put(typeClass, newType);
newType.populateMetaData(this.configurator, processedTypeMap);
newType.generateSchema(this.configurator, schemaMap);
}
}
this.qNameToTypeMap = new HashMap();
Type type = null;
for (Iterator iter = processedTypeMap.values().iterator(); iter.hasNext();) {
type = (Type) iter.next();
this.qNameToTypeMap.put(type.getXmlType().getQname(), type);
}
}
public Object getOutputObject(XMLStreamReader reader_in,
Operation operation)
throws XMLStreamException, XmlParsingException {
Object returnObject = null;
determinePriorAccess(reader_in);
StatefulXMLStreamReader reader = getStatefulReader(reader_in);
advanceToFirstElement(reader);
// first check whether we have got the correct input element or not
if (reader.getLocalName().equals(operation.getOutPutElement().getName()) &&
reader.getNamespaceURI().equals(operation.getOutPutElement().getNamespace())) {
reader.next();
if (operation.getOutputParameter() != null) {
// i.e this is not a void return type
returnObject = getObjectForParameter(reader, operation.getOutputParameter());
}
}
return returnObject;
}
public Object[] getInputParameters(XMLStreamReader reader_in,
Operation operation)
throws XMLStreamException,
XmlParsingException {
determinePriorAccess(reader_in);
StatefulXMLStreamReader reader = getStatefulReader(reader_in);
List returnObjects = new ArrayList();
advanceToFirstElement(reader);
// first check whether we have got the correct input element or not
if (reader.getLocalName().equals(operation.getInputElement().getName()) &&
reader.getNamespaceURI().equals(operation.getInputElement().getNamespace())) {
// point the reader to parameters
reader.next();
Parameter parameter = null;
List inputParameters = operation.getInputParameters();
QName parameterQName = null;
int startDepth = reader.getDepth();
for (Iterator iter = inputParameters.iterator(); iter.hasNext();) {
parameter = (Parameter) iter.next();
parameterQName = new QName(parameter.getNamespace(), parameter.getName());
returnObjects.add(getObjectForParameter(reader, parameter));
// if the reader is at the end of this parameter
// then we move it to next element.
if (reader.isEndElement() && reader.getName().equals(parameterQName)
&& (readerPriorAccess || reader.getDepth() == startDepth - 1)){
reader.next();
}
}
} else {
throw new XmlParsingException("Unexpected Subelement " + reader.getName() + " but " +
"expected " + operation.getInputElement().getName());
}
return returnObjects.toArray();
}
/**
* parameter has the same logic as the attribute. so reader pre and post conditions same.
*
* @param reader
* @param parameter
* @return
* @throws XMLStreamException
* @throws XmlParsingException
*/
public Object getObjectForParameter(XMLStreamReader reader_in,
Parameter parameter)
throws XMLStreamException,
XmlParsingException {
QName parameterQName = new QName(parameter.getNamespace(), parameter.getName());
determinePriorAccess(reader_in);
StatefulXMLStreamReader reader = getStatefulReader(reader_in);
return getObjectForElement(reader,
parameterQName,
parameter.getType(),
parameter.isArray(),
parameter.getElement().isNillable(),
parameter.getElement().isMinOccurs0(),
parameter.getClassType(),
parameter.getJavaClass());
}
/**
* when calls to this method reader must point to the start of the type.
* if it is a simple type it points the the text
* if it is a complex type it points to the start element of the first attribute. and we returning
* from the method it should point to the end element of the last parameter.
*
* @param reader
* @param type
* @return
* @throws XMLStreamException
* @throws XmlParsingException
*/
public Object getObjectForType(XMLStreamReader reader_in,
Type type)
throws XMLStreamException,
XmlParsingException {
try {
determinePriorAccess(reader_in);
StatefulXMLStreamReader reader = getStatefulReader(reader_in);
Object returnObject = null;
if (type.getXmlType().isSimpleType()) {
// this is a simple known type for us
// constructor should be able to invoke with the string.
if (type.getJavaClass().equals(Object.class)) {
returnObject = new Object();
} else {
// find the object for this string using converter util classes
returnObject = getSimpleTypeObject(type, reader, reader.getText());
}
} else {
// first we have to point to the reader to the beginning for the element
while (!reader.isStartElement() && !reader.isEndElement()) {
reader.next();
}
int startDepth = reader.getDepth();
// this is a complex type
returnObject = type.getJavaClass().newInstance();
// we have to get all the elementField and populate them
List elementFields = type.getAllElementFields();
ElementField elementField;
Object elementFieldObject;
QName elementFieldQName = null;
for (Iterator iter = elementFields.iterator(); iter.hasNext();) {
elementField = (ElementField) iter.next();
elementFieldObject = getObjectForElementField(reader, elementField);
elementFieldQName = new QName(elementField.getNamespace(), elementField.getName());
if (elementFieldObject != null) {
elementField.getSetterMethod().invoke(returnObject, new Object[]{elementFieldObject});
}
// if the reader is at the end of this elementField
// then we move it to next element.
if (reader.isEndElement() && reader.getName().equals(elementFieldQName)
&& (readerPriorAccess || reader.getDepth() == startDepth - 1)){
reader.next();
}
}
}
return returnObject;
} catch (InstantiationException e) {
throw new XmlParsingException("Constructor invoking exception for type " + type.getName());
} catch (IllegalAccessException e) {
throw new XmlParsingException("Constructor invoking exception for type " + type.getName());
} catch (InvocationTargetException e) {
e.printStackTrace();
throw new XmlParsingException("Constructor invoking exception for type " + type.getName());
}
}
private Object getSimpleTypeObject(Type type, StatefulXMLStreamReader reader, String value)
throws XmlParsingException {
Object returnObject;
String methodName = null;
try {
methodName = getMethodName(type.getJavaClass().getName());
Method methodToInvoke = this.simpleTypeHandlerClass.getMethod(methodName,new Class[]{String.class});
returnObject = methodToInvoke.invoke(this.simpleTypeHandler,new Object[]{value});
} catch (NoSuchMethodException e) {
throw new XmlParsingException("Can not invoke the converter util class method " + methodName, e);
} catch (IllegalAccessException e) {
throw new XmlParsingException("Can not invoke the converter util class method " + methodName, e);
} catch (InvocationTargetException e) {
throw new XmlParsingException("Can not invoke the converter util class method " + methodName, e);
}
return returnObject;
}
private String getMethodName(String className) {
// first handle some exceptional classes
if (className.equals(Integer.class.getName())) {
return "convertToInt";
} else if (className.equals(Calendar.class.getName())) {
return "convertToDateTime";
}
if (className.indexOf(".") > -1) {
className = className.substring(className.lastIndexOf(".") + 1);
} else {
// this is a primitive type class
// so capitalize the first letter
className = className.substring(0, 1).toUpperCase() + className.substring(1);
}
return "convertTo" + className;
}
/**
* give the relevant object for elementField.
*
* @param reader
* @param elementField
* @return
* @throws XMLStreamException
* @throws XmlParsingException
*/
private Object getObjectForElementField(StatefulXMLStreamReader reader,
ElementField elementField)
throws XMLStreamException,
XmlParsingException {
QName elementFieldQName = new QName(elementField.getNamespace(), elementField.getName());
return getObjectForElement(reader,
elementFieldQName,
elementField.getType(),
elementField.isArray(),
elementField.getElement().isNillable(),
elementField.getElement().isMinOccurs0(),
elementField.getClassType(),
elementField.getPropertyDescriptor().getPropertyType());
}
/**
* element parser corresponds to parse the element (for an attribute or parameter) correctly.
* when calling to this method reader must point to the start element of the element. and when returning
* it should point to the corresponding end element of the element.
* if the element is an array it may point to the start element of the next element.
*
* @param reader
* @param elementQName
* @param elementType
* @param isArray
* @param classType
* @param javaClass - if this is a list javaClass for the list
* @return
* @throws XMLStreamException
* @throws XmlParsingException
*/
private Object getObjectForElement(StatefulXMLStreamReader reader,
QName elementQName,
Type elementType,
boolean isArray,
boolean isNillable,
boolean isMinOccurs0,
int classType,
Class javaClass)
throws XMLStreamException,
XmlParsingException {
// first we have to point to the reader to the beginning for the element
while (!reader.isStartElement() && !reader.isEndElement()) {
reader.next();
}
// first validate the attribute
if (reader.getName().equals(elementQName)) {
String nillble = reader.getAttributeValue(Constants.URI_DEFAULT_SCHEMA_XSI, "nil");
// actual element type may be different from the element type given
// if extensions has used.
Type actualElementType = elementType;
QName typeQName = getTypeQName(reader);
if ((typeQName != null) && !elementType.getXmlType().getQname().equals(typeQName)) {
// i.e this is an extension type
if (this.qNameToTypeMap.containsKey(typeQName)) {
actualElementType = (Type) this.qNameToTypeMap.get(typeQName);
} else {
throw new XmlParsingException("Unknown type found ==> " + typeQName);
}
}
// point to the complex type elements
if (isArray) {
Collection objectsCollection = getCollectionObject(classType, javaClass);
// read the first element
if ("true".equals(nillble) || "1".equals(nillble)) {
// this is a nill attribute
while (!reader.isEndElement()) {
reader.next();
}
if (isNillable){
objectsCollection.add(null);
} else {
throw new XmlParsingException("Element " + elementQName + " can not be null");
}
} else {
Object returnObject = getElementObjectFromReader(actualElementType, reader);
objectsCollection.add(returnObject);
// we have to move the cursor until the end element of this attribute
while (!reader.isEndElement() || !reader.getName().equals(elementQName)) {
reader.next();
}
}
boolean loop = true;
while (loop) {
while (!reader.isEndElement()) {
reader.next();
}
reader.next();
// now we are at the end element of the first element
while (!reader.isStartElement() && !reader.isEndElement()) {
reader.next();
}
// in this step if it is an end element we have found an end element
// so have to exit from the loop
if (reader.isEndElement()) {
loop = false;
} else {
// now it should be in a start element
// check whether still we read the original element attributes. otherwise return
if (reader.getName().equals(elementQName)) {
nillble = reader.getAttributeValue(Constants.URI_DEFAULT_SCHEMA_XSI, "nil");
// since this is a new element we check for extensions
actualElementType = elementType;
typeQName = getTypeQName(reader);
if ((typeQName != null) && !elementType.getXmlType().getQname().equals(typeQName)) {
// i.e this is an extension type
if (this.qNameToTypeMap.containsKey(typeQName)) {
actualElementType = (Type) this.qNameToTypeMap.get(typeQName);
} else {
throw new XmlParsingException("Unknown type found ==> " + typeQName);
}
}
if ("true".equals(nillble) || "1".equals(nillble)) {
// this is a nill attribute
while (!reader.isEndElement()) {
reader.next();
}
if (isNillable) {
objectsCollection.add(null);
} else {
throw new XmlParsingException("Element " + elementQName + " can not be null");
}
} else {
Object returnObject = getElementObjectFromReader(actualElementType, reader);
objectsCollection.add(returnObject);
// we have to move the cursor until the end element of this attribute
while (!reader.isEndElement() || !reader.getName().equals(elementQName)) {
reader.next();
}
}
} else {
loop = false;
}
}
}
// this is very important in handling primitives
// they can not have null values so if array then we have to return the
// array object is null
// for other also it is convenient to assume like that.
if ((Constants.OTHER_TYPE & classType) == Constants.OTHER_TYPE){
// i.e this is not a collection type
List objectsList = (List) objectsCollection;
if ((objectsCollection.size() == 0) ||
((objectsCollection.size() == 1) && (objectsList.get(0) == null))) {
return null;
} else {
// create an array with the original element type
Object objectArray = Array.newInstance(elementType.getJavaClass(), objectsCollection.size());
for (int i = 0; i < objectsCollection.size(); i++) {
Array.set(objectArray, i, objectsList.get(i));
}
return objectArray;
}
} else if ((Constants.COLLECTION_TYPE & classType) == Constants.COLLECTION_TYPE){
if ((objectsCollection.size() == 0) ||
((objectsCollection.size() == 1) && (objectsCollection.iterator().next() == null))){
return null;
} else {
return objectsCollection;
}
} else if ((Constants.MAP_TYPE & classType) == Constants.MAP_TYPE){
if ((objectsCollection.size() == 0) ||
((objectsCollection.size() == 1) && (objectsCollection.iterator().next() == null))) {
return null;
} else {
List mapObjectsList = (List) objectsCollection;
MapType mapType = null;
Map mapObject = null;
if (javaClass.isInterface()) {
mapObject = new HashMap();
} else {
try {
mapObject = (Map) javaClass.newInstance();
} catch (InstantiationException e) {
throw new XmlParsingException("Can not instantiate the java class " + javaClass.getName(), e);
} catch (IllegalAccessException e) {
throw new XmlParsingException("Can not instantiate the java class " + javaClass.getName(), e);
}
}
for (Iterator iter = mapObjectsList.iterator(); iter.hasNext();) {
mapType = (MapType) iter.next();
mapObject.put(mapType.getKey(), mapType.getValue());
}
return mapObject;
}
} else {
throw new XmlParsingException("Unknow class type " + classType);
}
} else {
if ("true".equals(nillble) || "1".equals(nillble)) {
// this is a nill attribute
while (!reader.isEndElement()) {
reader.next();
}
reader.next();
if (isNillable) {
return null;
} else {
throw new XmlParsingException("Element " + elementQName + " can not be null");
}
} else {
Object returnObject = getElementObjectFromReader(actualElementType, reader);
// we have to move the cursor until the end element of this attribute
while (!reader.isEndElement() || !reader.getName().equals(elementQName)) {
reader.next();
}
return returnObject;
}
}
} else {
if (isMinOccurs0) {
return null;
} else {
throw new XmlParsingException("Unexpected Subelement " + reader.getName() + " but " +
"expected " + elementQName.getLocalPart());
}
}
}
private Object getElementObjectFromReader(Type elementType,
StatefulXMLStreamReader reader)
throws XmlParsingException, XMLStreamException {
Object returnObject = null;
if (RMIBean.class.isAssignableFrom(elementType.getJavaClass())) {
// this is an rmi bean
// so invoke the static parse method
try {
Method parseMethod = elementType.getJavaClass().getMethod("parse", new Class[]{XMLStreamReader.class, XmlStreamParser.class});
returnObject = parseMethod.invoke(null, new Object[]{reader, this});
} catch (NoSuchMethodException e) {
throw new XmlParsingException("parse method has not been implemented correctly for the rmi bean "
+ elementType.getJavaClass().getName(), e);
} catch (IllegalAccessException e) {
throw new XmlParsingException("can not access parse method of the rmi bean "
+ elementType.getJavaClass().getName(), e);
} catch (InvocationTargetException e) {
throw new XmlParsingException("can not invoke parse method of the rmi bean "
+ elementType.getJavaClass().getName(), e);
}
} else {
// read the attributes.
Map javaMethodToValueMap = getJavaMethodValueHashMap(elementType, reader);
reader.next();
returnObject = getObjectForType(reader, elementType);
populateObjectAttributes(javaMethodToValueMap, returnObject);
}
return returnObject;
}
private void populateObjectAttributes(Map javaMethodToValueMap, Object returnObject) throws XmlParsingException {
Method javaMehtod;
try {
for (Iterator iter = javaMethodToValueMap.keySet().iterator();iter.hasNext();){
javaMehtod = (Method) iter.next();
javaMehtod.invoke(returnObject,new Object[]{javaMethodToValueMap.get(javaMehtod)});
}
} catch (IllegalAccessException e) {
throw new XmlParsingException("Can not set the attribute value");
} catch (InvocationTargetException e) {
throw new XmlParsingException("Can not set the attribute value");
}
}
private Map getJavaMethodValueHashMap(Type actualElementType, StatefulXMLStreamReader reader)
throws XmlParsingException {
AttributeField attributeField;
String attributeVlaue;
Object attributeObject;
Map javaMethodToValueMap = new HashMap();
for (Iterator iter = actualElementType.getAllAttributeFields().iterator();iter.hasNext();){
attributeField = (AttributeField) iter.next();
attributeVlaue = reader.getAttributeValue(attributeField.getNamespace(),
attributeField.getName());
if (attributeVlaue != null) {
attributeObject = getSimpleTypeObject(attributeField.getType(), reader, attributeVlaue);
javaMethodToValueMap.put(attributeField.getSetterMethod(), attributeObject);
} else if (attributeField.isRequried()){
throw new XmlParsingException("Required attribute " + attributeField.getName() + " is missing");
}
}
return javaMethodToValueMap;
}
/**
* returns the collection object according to the type.
* @param classType
* @param javaClass
* @return
* @throws XmlParsingException
*/
private Collection getCollectionObject(int classType,
Class javaClass)
throws XmlParsingException {
Collection objectsCollection = null;
try {
if ((Constants.OTHER_TYPE & classType) == Constants.OTHER_TYPE) {
// i.e this is not a list or a map
objectsCollection = new ArrayList();
} else {
if (javaClass.isInterface()) {
if ((Constants.LIST_TYPE & classType) == Constants.LIST_TYPE) {
objectsCollection = new ArrayList();
} else if ((Constants.SET_TYPE & classType) == Constants.SET_TYPE) {
objectsCollection = new HashSet();
} else if ((Constants.COLLECTION_TYPE & classType) == Constants.COLLECTION_TYPE) {
objectsCollection = new ArrayList();
} else if ((Constants.MAP_TYPE & classType) == Constants.MAP_TYPE) {
objectsCollection = new ArrayList();
}
} else {
if ((Constants.COLLECTION_TYPE & classType) == Constants.COLLECTION_TYPE) {
objectsCollection = (Collection) javaClass.newInstance();
} else if ((Constants.MAP_TYPE & classType) == Constants.MAP_TYPE) {
objectsCollection = new ArrayList();
}
}
}
} catch (InstantiationException e) {
throw new XmlParsingException("Problem with instanciating the element class " +
javaClass.getName() , e);
} catch (IllegalAccessException e) {
throw new XmlParsingException("Problem with instanciating the element class " +
javaClass.getName() , e);
}
return objectsCollection;
}
/**
* reader must be at the start of the element
*
* @param reader
* @return qName for the type attribute
*/
private QName getTypeQName(StatefulXMLStreamReader reader) {
QName typeQName = null;
String typeValue = reader.getAttributeValue(Constants.URI_2001_SCHEMA_XSI, "type");
if ((typeValue != null) && !typeValue.equals("")) {
int index = typeValue.indexOf(":");
String nsPrefix = "";
if (index > -1) {
nsPrefix = typeValue.substring(0, index);
}
String localPart = typeValue.substring(index + 1);
typeQName = new QName(reader.getNamespaceURI(nsPrefix), localPart);
}
return typeQName;
}
/**
* Determine if the reader has been advanced beyond its initial location, prior
* to the methods within XmlStreamParser being called.
*
* @param reader The XMLStreamReader on which the XmlStreamParser is acting.
* @return true if the reader has been advanced beyond its initial location, false otherwise
*/
private boolean determinePriorAccess(XMLStreamReader reader) {
if (!priorAccessDetermined) {
if (reader.getLocation().getCharacterOffset() == 0)
readerPriorAccess = false;
else
readerPriorAccess = true;
priorAccessDetermined = true;
}
return readerPriorAccess;
}
/**
* Provide a mechanism (a stack) to track the current state of the XMLStream
*
* @param reader_in the XMLStreamReader that the XmlStreamParser is acting on
* @return a StatefulXMLStreamReader to monitor the state of the XMLStreamReader
*/
private StatefulXMLStreamReader getStatefulReader(XMLStreamReader reader_in) {
if (reader_in instanceof StatefulXMLStreamReader)
return (StatefulXMLStreamReader) reader_in;
else
return new StatefulXMLStreamReader(reader_in, readerPriorAccess);
}
/**
* Advance the reader to point to the beginning of the element
* @param reader the StatefulXMLStreamReader we are using to parse the XMLStream
*/
private void advanceToFirstElement(StatefulXMLStreamReader reader) throws XMLStreamException {
if (readerPriorAccess) {
while (!reader.isStartElement() && !reader.isEndElement() && reader.hasNext()) {
reader.next();
}
} else {
while (reader.getDepth() == 0 && reader.hasNext()) {
reader.next();
}
}
}
}