/* * Copyright 2010 Werner Guttmann * * Licensed 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.exolab.castor.xml; import java.text.MessageFormat; import java.util.Locale; import java.util.ResourceBundle; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.castor.core.util.StringUtil; import org.castor.xml.XMLProperties; import org.exolab.castor.mapping.ExtendedFieldHandler; import org.exolab.castor.mapping.FieldHandler; import org.exolab.castor.mapping.loader.FieldHandlerImpl; import org.exolab.castor.xml.MarshalFramework.InheritanceMatch; import org.exolab.castor.xml.MarshalFramework.InternalXMLClassDescriptor; import org.exolab.castor.xml.UnmarshalHandler.Arguments; import org.exolab.castor.xml.UnmarshalHandler.ArrayHandler; import org.exolab.castor.xml.util.ContainerElement; import org.exolab.castor.xml.util.XMLClassDescriptorImpl; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; /** * A processor that assists {@link UnmarshalHandler} in dealing with the SAX 2 * {@link ContentHandler#startElement(String, String, String, org.xml.sax.Attributes)} * callback method. * * @author <a href=" mailto:wguttmn AT codehaus DOT org">Werner Guttmann</a> * @since 1.3.2 */ public class StartElementProcessor { /** * Standard logger to use. */ private static final Log LOG = LogFactory .getLog(StartElementProcessor.class); /** * resource bundle */ protected static ResourceBundle resourceBundle; /** * Callback {@link UnmarshalHandler} reference to set the actual state on * this instance. */ private final UnmarshalHandler _unmarshalHandler; static { resourceBundle = ResourceBundle.getBundle("UnmarshalHandlerMessages", Locale.getDefault()); } /** * Creates an instance of this class, with a reference to the actual * {@link UnmarshalHandler} for which this processor deals with the SAX 2 * startElement() callback method. * * @param unmarshalHandler * The {@link UnmarshalHandler} instance on which the results of * processing the startElement method will be 'persisted'/set. */ public StartElementProcessor(final UnmarshalHandler unmarshalHandler) { super(); _unmarshalHandler = unmarshalHandler; } public void compute(String name, String namespace, AttributeSet atts) throws SAXException { UnmarshalState state = null; String xmlSpace = null; // -- handle special atts if (atts != null) { // -- xml:space xmlSpace = atts.getValue(UnmarshalHandler.XML_SPACE, Namespaces.XML_NAMESPACE); if (xmlSpace == null) { xmlSpace = atts.getValue( UnmarshalHandler.XML_SPACE_WITH_PREFIX, ""); } } if (_unmarshalHandler.getStateStack().isEmpty()) { // -- Initialize since this is the first element _unmarshalHandler.processFirstElement(name, namespace, atts, xmlSpace); return; } // --rootElement // -- get MarshalDescriptor for the given element UnmarshalState parentState = _unmarshalHandler.getStateStack() .getLastState(); // Test if we can accept the field in the parentState // in case the parentState fieldDesc is a container // -- This following logic tests to see if we are in a // -- container and we need to close out the container // -- before proceeding: boolean canAccept = false; while ((parentState.getFieldDescriptor() != null) && (parentState.getFieldDescriptor().isContainer() && !canAccept)) { XMLClassDescriptor tempClassDesc = parentState.getClassDescriptor(); // -- Find ClassDescriptor for Parent if (tempClassDesc == null) { tempClassDesc = (XMLClassDescriptor) parentState.getFieldDescriptor() .getClassDescriptor(); if (tempClassDesc == null) tempClassDesc = _unmarshalHandler .getClassDescriptor(parentState.getObject().getClass()); } canAccept = tempClassDesc.canAccept(name, namespace, parentState.getObject()); if (!canAccept) { // -- Does container class even handle this field? if (tempClassDesc.getFieldDescriptor(name, namespace, NodeType.Element) != null) { if (!parentState.getFieldDescriptor().isMultivalued()) { String error = MessageFormat .format(resourceBundle .getString("unmarshalHandler.error.container.full"), new Object[] { tempClassDesc.getJavaClass() .getName(), name }); ValidationException vx = new ValidationException(error); throw new SAXException(vx); } } _unmarshalHandler.endElement(parentState.getElementName()); parentState = _unmarshalHandler.getStateStack().getLastState(); } tempClassDesc = null; } // -- create new state object state = new UnmarshalState(); state.setElementName(name); state.setParent(parentState); if (xmlSpace != null) { state.setWhitespacePreserving(UnmarshalHandler.PRESERVE.equals(xmlSpace)); } else { state.setWhitespacePreserving(parentState.isWhitespacePreserving()); } _unmarshalHandler.getStateStack().pushState(state); // -- make sure we should proceed if (parentState.getObject() == null) { if (!parentState.isWrapper()) { return; } } Class cls = null; // -- Find ClassDescriptor for Parent XMLClassDescriptor classDesc = parentState.getClassDescriptor(); if (classDesc == null) { classDesc = (XMLClassDescriptor) parentState.getFieldDescriptor() .getClassDescriptor(); if (classDesc == null) classDesc = _unmarshalHandler .getClassDescriptor(parentState.getObject().getClass()); } else { // classDesc.resetElementCount(); } // ----------------------------------------------------/ // - Find FieldDescriptor associated with the element -/ // ----------------------------------------------------/ // -- A reference to the FieldDescriptor associated // -- the the "current" element XMLFieldDescriptor descriptor = null; // -- inherited class descriptor // -- (only needed if descriptor cannot be found directly) XMLClassDescriptor cdInherited = null; // -- loop through stack and find correct descriptor // int pIdx = _stateInfo.size() - 2; //-- index of parentState UnmarshalState targetState = parentState; String path = ""; StringBuffer pathBuf = null; int count = 0; boolean isWrapper = false; XMLClassDescriptor oldClassDesc = classDesc; while (descriptor == null) { // -- NOTE (kv 20050228): // -- we need to clean this code up, I made this // -- fix to make sure the correct descriptor which // -- matches the location path is used if (path.length() > 0) { String tmpName = path + "/" + name; descriptor = classDesc.getFieldDescriptor(tmpName, namespace, NodeType.Element); } // -- End Patch if (descriptor == null) { descriptor = classDesc.getFieldDescriptor(name, namespace, NodeType.Element); } // -- Namespace patch, should be moved to XMLClassDescriptor, but // -- this is the least intrusive patch at the moment. kv - 20030423 if ((descriptor != null) && (!descriptor.isContainer())) { if (StringUtil.isNotEmpty(namespace)) { if (!MarshalFramework.namespaceEquals(namespace, descriptor.getNameSpaceURI())) { // -- if descriptor namespace is not null, then we must // -- have a namespace match, so set descriptor to null, // -- or if descriptor is not a wildcard we can also // -- set to null. if ((descriptor.getNameSpaceURI() != null) || (!descriptor.matches("*"))) { descriptor = null; } } } } // -- end namespace patch /* * If descriptor is null, we need to handle possible inheritence, * which might not be described in the current ClassDescriptor. This * can be a slow process...for speed use the match attribute of the * xml element in the mapping file. This logic might not be * completely necessary, and perhaps we should remove it. */ // handle multiple level locations (where count > 0) (CASTOR-1039) // if ((descriptor == null) && (count == 0) && // (!targetState.wrapper)) { if ((descriptor == null) && (!targetState.isWrapper())) { MarshalFramework.InheritanceMatch[] matches = null; try { matches = _unmarshalHandler.searchInheritance(name, namespace, classDesc); // TODO: Joachim, // _cdResolver); } catch (MarshalException rx) { // -- TODO: } if (matches.length != 0) { InheritanceMatch match = null; // It may be the case that this class descriptor can // appear under multiple parent field descriptors. Look // for the first match whose parent file descriptor XML // name matches the name of the element we are under for (int i = 0; i < matches.length; i++) { if (parentState.getElementName() .equals(matches[i].parentFieldDesc .getLocationPath())) { match = matches[i]; break; } } if (match == null) match = matches[0]; descriptor = match.parentFieldDesc; cdInherited = match.inheritedClassDesc; break; // -- found } /* */ // handle multiple level locations (where count > 0) // (CASTOR-1039) // isWrapper = (isWrapper || hasFieldsAtLocation(name, // classDesc)); StringBuffer tmpLocation = new StringBuffer(); tmpLocation.append(name); if (count > 0) { tmpLocation.append(path + "/" + name); } isWrapper = (isWrapper || MarshalFramework.hasFieldsAtLocation( tmpLocation.toString(), classDesc)); } else if (descriptor != null) { String tmpPath = descriptor.getLocationPath(); if (path.equals(StringUtil.defaultString(tmpPath))) break; // -- found descriptor = null; // -- not found, try again } else { if (pathBuf == null) pathBuf = new StringBuffer(); else pathBuf.setLength(0); pathBuf.append(path); pathBuf.append('/'); pathBuf.append(name); isWrapper = (isWrapper || MarshalFramework.hasFieldsAtLocation( pathBuf.toString(), classDesc)); } // -- Make sure there are more parent classes on stack // -- otherwise break, since there is nothing to do // if (pIdx == 0) break; if (targetState == _unmarshalHandler.getTopState()) break; // -- adjust name and try parent if (count == 0) path = targetState.getElementName(); else { if (pathBuf == null) pathBuf = new StringBuffer(); else pathBuf.setLength(0); pathBuf.append(targetState.getElementName()); pathBuf.append('/'); pathBuf.append(path); path = pathBuf.toString(); } // -- get // --pIdx; // targetState = (UnmarshalState)_stateInfo.elementAt(pIdx); targetState = targetState.getParent(); classDesc = targetState.getClassDescriptor(); count++; } if (descriptor != null && _unmarshalHandler.isValidating() && !_unmarshalHandler.getInternalContext() .getLenientSequenceOrder()) { try { classDesc.checkDescriptorForCorrectOrderWithinSequence( descriptor, parentState, name); } catch (ValidationException e) { throw new SAXException(e); } } // -- The field descriptor is still null, we face a problem if (descriptor == null) { // -- reset classDesc classDesc = oldClassDesc; // -- isWrapper? if (isWrapper) { state.setClassDescriptor(new XMLClassDescriptorImpl( ContainerElement.class, name)); state.setWrapper(true); if (LOG.isDebugEnabled()) { LOG.debug("wrapper-element: " + name); } // -- process attributes _unmarshalHandler.processWrapperAttributes(atts); return; } String error = MessageFormat.format(resourceBundle .getString("unmarshalHandler.error.find.field.descriptor"), new Object[] { name, classDesc.getXMLName() }); // -- unwrap classDesc, if necessary, for the check // -- Introspector.introspected done below if (classDesc instanceof InternalXMLClassDescriptor) { classDesc = ((InternalXMLClassDescriptor) classDesc) .getClassDescriptor(); } // -- If we are skipping elements that have appeared in the XML but // for // -- which we have no mapping, increase the ignore depth counter // and return boolean lenientElementStrictnessForIntrospection = _unmarshalHandler .getInternalContext() .getBooleanProperty( XMLProperties.LENIENT_INTROSPECTED_ELEMENT_STRICTNESS) .booleanValue(); if (_unmarshalHandler.getStrictElementHandler().skipStartElement()) { // -- remove the StateInfo we just added _unmarshalHandler.getStateStack().removeLastState(); // drop Namespace instance as well _unmarshalHandler.getNamespaceHandling() .removeCurrentNamespaceInstance(); if (LOG.isDebugEnabled()) { String debug = MessageFormat.format(resourceBundle .getString("LOG.debug.ignore.extra.element"), new Object[] { error }); LOG.debug(debug); } return; } // if we have no field descriptor and // the class descriptor was introspected // just log it else if (lenientElementStrictnessForIntrospection && Introspector.introspected(classDesc)) { LOG.warn(error); return; } // -- otherwise report error since we cannot find a suitable // -- descriptor else { throw new SAXException(error); } } // -- end null descriptor // / DEBUG: System.out.println("path: " + path); // -- Save targetState (used in endElement) if (targetState != parentState) { state.setTargetState(targetState); parentState = targetState; // -- reassign } Object object = parentState.getObject(); // --container support if (descriptor.isContainer()) { // create a new state to set the container as the object // don't save the current state, it will be recreated later if (LOG.isDebugEnabled()) { LOG.debug("#container: " + descriptor.getFieldName()); } // -- clear current state and re-use for the container state.clear(); // -- inherit whitespace preserving from the parentState state.setWhitespacePreserving(parentState.isWhitespacePreserving()); state.setParent(parentState); // here we can hard-code a name or take the field name state.setElementName(descriptor.getFieldName()); state.setFieldDescriptor(descriptor); state.setClassDescriptor((XMLClassDescriptor) descriptor .getClassDescriptor()); Object containerObject = null; // 1-- the container is not multivalued (not a collection) if (!descriptor.isMultivalued()) { // Check if the container object has already been instantiated FieldHandler handler = descriptor.getHandler(); containerObject = handler.getValue(object); if (containerObject != null) { if (state.getClassDescriptor() != null) { if (state.getClassDescriptor().canAccept(name, namespace, containerObject)) { // remove the descriptor from the used list parentState.markAsNotUsed(descriptor); } } else { // remove the descriptor from the used list parentState.markAsNotUsed(descriptor); } } else { containerObject = handler.newInstance(object); } } // 2-- the container is multivalued else { Class containerClass = descriptor.getFieldType(); try { containerObject = containerClass.newInstance(); } catch (Exception ex) { throw new SAXException(ex); } } state.setObject(containerObject); state.setType(containerObject.getClass()); // we need to recall startElement() // so that we can find a more appropriate descriptor in for the // given name _unmarshalHandler.getNamespaceHandling().createNamespace(); _unmarshalHandler.startElementProcessing(name, namespace, atts); return; } // --End of the container support // -- Find object type and create new Object of that type state.setFieldDescriptor(descriptor); /* * <update> we need to add this code back in, to make sure we have * proper access rights. * * if (!descriptor.getAccessRights().isWritable()) { if (debug) { * buf.setLength(0); buf.append("The field for element '"); * buf.append(name); buf.append("' is read-only."); * message(buf.toString()); } return; } */ // -- Find class to instantiate // -- check xml names to see if we should look for a more specific // -- ClassDescriptor, otherwise just use the one found in the // -- descriptor classDesc = null; if (cdInherited != null) classDesc = cdInherited; else if (!name.equals(descriptor.getXMLName())) classDesc = _unmarshalHandler.resolveByXMLName(name, namespace, null); if (classDesc == null) classDesc = (XMLClassDescriptor) descriptor.getClassDescriptor(); FieldHandler handler = descriptor.getHandler(); boolean useHandler = true; try { // -- Get Class type...first use ClassDescriptor, // -- since it could be more specific than // -- the FieldDescriptor if (classDesc != null) { cls = classDesc.getJavaClass(); // -- XXXX This is a hack I know...but we // -- XXXX can't use the handler if the field // -- XXXX types are different if (descriptor.getFieldType() != cls) { state.setDerived(true); } } else { cls = descriptor.getFieldType(); } // -- This *shouldn't* happen, but a custom implementation // -- could return null in the XMLClassDesctiptor#getJavaClass // -- or XMLFieldDescriptor#getFieldType. If so, just replace // -- with java.lang.Object.class (basically "anyType"). if (cls == null) { cls = java.lang.Object.class; } // Retrieving the xsi:type attribute, if present String currentPackage = _unmarshalHandler .getJavaPackage(parentState.getType()); String instanceType = _unmarshalHandler.getInstanceType(atts, currentPackage); if (instanceType != null) { Class instanceClass = null; try { XMLClassDescriptor instanceDesc = _unmarshalHandler .getClassDescriptor(instanceType, _unmarshalHandler.getClassLoader()); boolean loadClass = true; if (instanceDesc != null) { instanceClass = instanceDesc.getJavaClass(); classDesc = instanceDesc; if (instanceClass != null) { loadClass = (!instanceClass.getName().equals( instanceType)); } } if (loadClass) { instanceClass = _unmarshalHandler.loadClass( instanceType, null); // the FieldHandler can be either an XMLFieldHandler // or a FieldHandlerImpl FieldHandler tempHandler = descriptor.getHandler(); boolean collection = false; if (tempHandler instanceof FieldHandlerImpl) { collection = ((FieldHandlerImpl) tempHandler) .isCollection(); } else { collection = Introspector .isCollection(instanceClass); } if ((!collection) && !cls.isAssignableFrom(instanceClass)) { if (!MarshalFramework.isPrimitive(cls)) { String err = MessageFormat .format(resourceBundle .getString("unmarshalHandler.error.not.subclass"), new Object[] { instanceClass.getName(), cls.getName() }); throw new SAXException(err); } } } cls = instanceClass; useHandler = false; } catch (Exception ex) { String err = MessageFormat .format(resourceBundle .getString("unmarshalHandler.error.unable.instantiate.exception"), new Object[] { instanceType, ex.getMessage() }); throw new SAXException(err, ex); } } // -- Handle ArrayHandler if (cls == Object.class) { if (parentState.getObject() instanceof ArrayHandler) cls = ((ArrayHandler) parentState.getObject()).componentType(); } // -- Handle support for "Any" type if (cls == Object.class) { Class pClass = parentState.getType(); ClassLoader loader = pClass.getClassLoader(); // -- first look for a descriptor based // -- on the XML name classDesc = _unmarshalHandler.resolveByXMLName(name, namespace, loader); // -- if null, create classname, and try resolving String cname = null; if (classDesc == null) { // -- create class name cname = _unmarshalHandler.getJavaNaming().toJavaClassName( name); classDesc = _unmarshalHandler.getClassDescriptor(cname, loader); } // -- if still null, try using parents package if (classDesc == null) { // -- use parent to get package information String pkg = pClass.getName(); int idx = pkg.lastIndexOf('.'); if (idx > 0) { pkg = pkg.substring(0, idx + 1); cname = pkg + cname; classDesc = _unmarshalHandler.getClassDescriptor(cname, loader); } } if (classDesc != null) { cls = classDesc.getJavaClass(); useHandler = false; } else { // we are dealing with an AnyNode state.setObject(_unmarshalHandler.getAnyNodeHandler() .commonStartElement(name, namespace, state.isWhitespacePreserving())); state.setType(cls); return; } } boolean byteArray = false; if (cls.isArray()) byteArray = (cls.getComponentType() == Byte.TYPE); // -- check for immutable if (MarshalFramework.isPrimitive(cls) || descriptor.isImmutable() || byteArray) { state.setObject(null); state.setPrimitiveOrImmutable(true); // -- handle immutable types, such as java.util.Locale if (descriptor.isImmutable()) { if (classDesc == null) classDesc = _unmarshalHandler.getClassDescriptor(cls); state.setClassDescriptor(classDesc); Arguments args = _unmarshalHandler.processConstructorArgs( atts, classDesc); if ((args != null) && (args.size() > 0)) { state.setConstructorArguments(args); } } } else { if (classDesc == null) classDesc = _unmarshalHandler.getClassDescriptor(cls); // -- XXXX should remove this test once we can // -- XXXX come up with a better solution if ((!state.isDerived()) && useHandler) { boolean create = true; if (_unmarshalHandler.isReuseObjects()) { state.setObject(handler.getValue(parentState.getObject())); create = (state.getObject() == null); } if (create) { Arguments args = _unmarshalHandler .processConstructorArgs(atts, classDesc); if ((args.getValues() != null) && (args.getValues().length > 0)) { if (handler instanceof ExtendedFieldHandler) { ExtendedFieldHandler efh = (ExtendedFieldHandler) handler; state.setObject(efh.newInstance( parentState.getObject(), args.getValues())); } else { String err = resourceBundle .getString("unmarshalHandler.error.constructor.arguments"); throw new SAXException(err); } } else { state.setObject(handler .newInstance(parentState.getObject())); } } } // -- reassign class in case there is a conflict // -- between descriptor#getFieldType and // -- handler#newInstance...I should hope not, but // -- who knows if (state.getObject() != null) { cls = state.getObject().getClass(); if (classDesc != null) { if (classDesc.getJavaClass() != cls) { classDesc = null; } } } else { try { if (cls.isArray()) { state.setObject(new ArrayHandler( cls.getComponentType())); cls = ArrayHandler.class; } else { Arguments args = _unmarshalHandler .processConstructorArgs(atts, classDesc); state.setObject(_unmarshalHandler.createInstance( cls, args)); // state.object = _class.newInstance(); } } catch (java.lang.Exception ex) { String err = MessageFormat .format(resourceBundle .getString("unmarshalHandler.error.unable.instantiate.exception"), new Object[] { _unmarshalHandler .className(cls), ex.getMessage() }); throw new SAXException(err, ex); } } } state.setType(cls); } catch (java.lang.IllegalStateException ise) { LOG.error(ise.toString()); throw new SAXException(ise); } // -- At this point we should have a new object, unless // -- we are dealing with a primitive type, or a special // -- case such as byte[] if (classDesc == null) { classDesc = _unmarshalHandler.getClassDescriptor(cls); } state.setClassDescriptor(classDesc); if ((state.getObject() == null) && (!state.isPrimitiveOrImmutable())) { String err = MessageFormat.format(resourceBundle .getString("unmarshalHandler.error.unable.unmarshal"), new Object[] { name, _unmarshalHandler.className(cls) }); throw new SAXException(err); } // -- assign object, if incremental if (descriptor.isIncremental()) { if (LOG.isDebugEnabled()) { String debug = MessageFormat.format(resourceBundle .getString("LOG.debug.process.incrementally"), new Object[] { name }); LOG.debug(debug); } try { handler.setValue(parentState.getObject(), state.getObject()); } catch (java.lang.IllegalStateException ise) { String err = MessageFormat .format(resourceBundle .getString("unmarshalHandler.error.unable.add.element"), new Object[] { name, parentState.getFieldDescriptor().getXMLName(), ise.getMessage() }); throw new SAXException(err, ise); } } if (state.getObject() != null) { // --The object has just been initialized // --notify the listener Object stateObject = state.getObject(); Object parentObject = (state.getParent() == null) ? null : state.getParent().getObject(); _unmarshalHandler.getDelegateUnmarshalListener().initialized( stateObject, parentObject); _unmarshalHandler.processAttributes(atts, classDesc); _unmarshalHandler.getDelegateUnmarshalListener().attributesProcessed( stateObject, parentObject); _unmarshalHandler.getNamespaceHandling().processNamespaces(classDesc, _unmarshalHandler.getStateStack().getLastState().getObject()); } else if ((state.getType() != null) && (!state.isPrimitiveOrImmutable())) { if (atts != null) { _unmarshalHandler.processWrapperAttributes(atts); String warn = MessageFormat.format(resourceBundle .getString("LOG.warn.process.attribute.as.location"), new Object[] { name }); LOG.warn(warn); } } else { // -- check for special attributes, such as xsi:nil if (atts != null) { String nil = atts.getValue(MarshalFramework.NIL_ATTR, MarshalFramework.XSI_NAMESPACE); state.setNil("true".equals(nil)); _unmarshalHandler.processWrapperAttributes(atts); } } } }