/** * 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.cxf.aegis.type.basic; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Set; import java.util.logging.Logger; import javax.xml.namespace.QName; import javax.xml.stream.XMLStreamReader; import org.apache.cxf.aegis.Context; import org.apache.cxf.aegis.DatabindingException; import org.apache.cxf.aegis.type.AegisType; import org.apache.cxf.aegis.type.TypeUtil; import org.apache.cxf.aegis.xml.MessageReader; import org.apache.cxf.aegis.xml.MessageWriter; import org.apache.cxf.aegis.xml.stax.ElementReader; import org.apache.cxf.common.logging.LogUtils; import org.apache.ws.commons.schema.XmlSchema; import org.apache.ws.commons.schema.XmlSchemaComplexType; import org.apache.ws.commons.schema.XmlSchemaElement; import org.apache.ws.commons.schema.XmlSchemaSequence; import org.apache.ws.commons.schema.constants.Constants; /** * An ArrayType. */ public class ArrayType extends AegisType { private static final Logger LOG = LogUtils.getL7dLogger(ArrayType.class); private QName componentName; private long minOccurs; private long maxOccurs = Long.MAX_VALUE; public ArrayType() { } public Object readObject(MessageReader reader, QName flatElementName, Context context, boolean asArray) throws DatabindingException { try { Collection<Object> values = readCollection(reader, flatElementName, context); if (asArray) { return makeArray(getComponentType().getTypeClass(), values); } return values; } catch (IllegalArgumentException e) { throw new DatabindingException("Illegal argument.", e); } } /* * This version is not called for the flat case. */ @Override public Object readObject(MessageReader reader, Context context) throws DatabindingException { return readObject(reader, null, context, true); } protected Collection<Object> createCollection() { return new ArrayList<>(); } /** * Read the elements of an array or array-like item. * @param reader reader to read from. * @param flatElementName if flat, the elements we are looking for. When we see * something else. we stop. * @param context context. * @return a collection of the objects. * @throws DatabindingException */ protected Collection<Object> readCollection(MessageReader reader, QName flatElementName, Context context) throws DatabindingException { Collection<Object> values = createCollection(); /** * If we are 'flat' (writeOuter is false), then we aren't reading children. We're reading starting * from where we are. */ if (isFlat()) { // the reader does some really confusing things. XMLStreamReader xmlReader = reader.getXMLStreamReader(); while (xmlReader.getName().equals(flatElementName)) { AegisType compType = TypeUtil.getReadType(reader.getXMLStreamReader(), context.getGlobalContext(), getComponentType()); // gosh, what about message readers of some other type? ElementReader thisItemReader = new ElementReader(xmlReader); collectOneItem(context, values, thisItemReader, compType); } } else { while (reader.hasMoreElementReaders()) { MessageReader creader = reader.getNextElementReader(); AegisType compType = TypeUtil.getReadType(creader.getXMLStreamReader(), context.getGlobalContext(), getComponentType()); collectOneItem(context, values, creader, compType); } } // check min occurs if (values.size() < minOccurs) { throw new DatabindingException("The number of elements in " + getSchemaType() + " does not meet the minimum of " + minOccurs); } return values; } private void collectOneItem(Context context, Collection<Object> values, MessageReader creader, AegisType compType) { if (creader.isXsiNil()) { values.add(null); creader.readToEnd(); } else { values.add(compType.readObject(creader, context)); } // check max occurs int size = values.size(); if (size > maxOccurs) { throw new DatabindingException("The number of elements in " + getSchemaType() + " exceeds the maximum of " + maxOccurs); } } protected Object makeArray(Class<?> arrayType, Collection<Object> values) { int i; int n; Object array = null; if (Integer.TYPE.equals(arrayType)) { Object[] objects = values.toArray(); array = Array.newInstance(Integer.TYPE, objects.length); for (i = 0, n = objects.length; i < n; i++) { Array.set(array, i, objects[i]); } } else if (Long.TYPE.equals(arrayType)) { Object[] objects = values.toArray(); array = Array.newInstance(Long.TYPE, objects.length); for (i = 0, n = objects.length; i < n; i++) { Array.set(array, i, objects[i]); } } else if (Short.TYPE.equals(arrayType)) { Object[] objects = values.toArray(); array = Array.newInstance(Short.TYPE, objects.length); for (i = 0, n = objects.length; i < n; i++) { Array.set(array, i, objects[i]); } } else if (Double.TYPE.equals(arrayType)) { Object[] objects = values.toArray(); array = Array.newInstance(Double.TYPE, objects.length); for (i = 0, n = objects.length; i < n; i++) { Array.set(array, i, objects[i]); } } else if (Float.TYPE.equals(arrayType)) { Object[] objects = values.toArray(); array = Array.newInstance(Float.TYPE, objects.length); for (i = 0, n = objects.length; i < n; i++) { Array.set(array, i, objects[i]); } } else if (Byte.TYPE.equals(arrayType)) { Object[] objects = values.toArray(); array = Array.newInstance(Byte.TYPE, objects.length); for (i = 0, n = objects.length; i < n; i++) { Array.set(array, i, objects[i]); } } else if (Boolean.TYPE.equals(arrayType)) { Object[] objects = values.toArray(); array = Array.newInstance(Boolean.TYPE, objects.length); for (i = 0, n = objects.length; i < n; i++) { Array.set(array, i, objects[i]); } } else if (Character.TYPE.equals(arrayType)) { Object[] objects = values.toArray(); array = Array.newInstance(Character.TYPE, objects.length); for (i = 0, n = objects.length; i < n; i++) { Array.set(array, i, objects[i]); } } return array == null ? values.toArray((Object[])Array.newInstance(getComponentType().getTypeClass(), values.size())) : array; } @Override public void writeObject(Object values, MessageWriter writer, Context context) throws DatabindingException { writeObject(values, writer, context, null); } /** * Write an array type, using the desired element name in the flattened case. * @param values values to write. * @param writer writer to sent it to. * @param context the aegis context. * @param flatElementName name to use for the element if flat. * @throws DatabindingException */ public void writeObject(Object values, MessageWriter writer, Context context, QName flatElementName) throws DatabindingException { boolean forceXsiWrite = false; if (values == null) { return; } if (values instanceof Collection) { Collection<?> c = (Collection<?>)values; values = c.toArray(); } AegisType type = getComponentType(); if (type == null) { throw new DatabindingException("Couldn't find type for array."); } if (Constants.XSD_ANYTYPE.equals(type.getSchemaType())) { forceXsiWrite = true; } String ns = null; if (type.isAbstract()) { ns = getSchemaType().getNamespaceURI(); } else { ns = type.getSchemaType().getNamespaceURI(); } /* * This is not the right name in the 'flat' case. In the flat case, * we need the element name that would have been attached * one level out. */ String name; if (isFlat()) { name = flatElementName.getLocalPart(); ns = flatElementName.getNamespaceURI(); // override the namespace. } else { name = type.getSchemaType().getLocalPart(); } Class<?> arrayType = type.getTypeClass(); boolean oldXsiWrite = context.getGlobalContext().isWriteXsiTypes(); try { if (forceXsiWrite) { context.getGlobalContext().setWriteXsiTypes(true); } int i; int n; if (Object.class.isAssignableFrom(arrayType)) { Object[] objects = (Object[])values; for (i = 0, n = objects.length; i < n; i++) { writeValue(objects[i], writer, context, type, name, ns); } } else if (Integer.TYPE.equals(arrayType)) { int[] objects = (int[])values; for (i = 0, n = objects.length; i < n; i++) { writeValue(Integer.valueOf(objects[i]), writer, context, type, name, ns); } } else if (Long.TYPE.equals(arrayType)) { long[] objects = (long[])values; for (i = 0, n = objects.length; i < n; i++) { writeValue(Long.valueOf(objects[i]), writer, context, type, name, ns); } } else if (Short.TYPE.equals(arrayType)) { short[] objects = (short[])values; for (i = 0, n = objects.length; i < n; i++) { writeValue(Short.valueOf(objects[i]), writer, context, type, name, ns); } } else if (Double.TYPE.equals(arrayType)) { double[] objects = (double[])values; for (i = 0, n = objects.length; i < n; i++) { writeValue(Double.valueOf(objects[i]), writer, context, type, name, ns); } } else if (Float.TYPE.equals(arrayType)) { float[] objects = (float[])values; for (i = 0, n = objects.length; i < n; i++) { writeValue(Float.valueOf(objects[i]), writer, context, type, name, ns); } } else if (Byte.TYPE.equals(arrayType)) { byte[] objects = (byte[])values; for (i = 0, n = objects.length; i < n; i++) { writeValue(Byte.valueOf(objects[i]), writer, context, type, name, ns); } } else if (Boolean.TYPE.equals(arrayType)) { boolean[] objects = (boolean[])values; for (i = 0, n = objects.length; i < n; i++) { writeValue(Boolean.valueOf(objects[i]), writer, context, type, name, ns); } } else if (Character.TYPE.equals(arrayType)) { char[] objects = (char[])values; for (i = 0, n = objects.length; i < n; i++) { writeValue(Character.valueOf(objects[i]), writer, context, type, name, ns); } } } finally { context.getGlobalContext().setWriteXsiTypes(oldXsiWrite); } } protected void writeValue(Object value, MessageWriter writer, Context context, AegisType type, String name, String ns) throws DatabindingException { type = TypeUtil.getWriteType(context.getGlobalContext(), value, type); MessageWriter cwriter; if (!type.isFlatArray()) { cwriter = writer.getElementWriter(name, ns); } else { cwriter = writer; } if (value == null && type.isNillable()) { cwriter.writeXsiNil(); } else { type.writeObject(value, cwriter, context); } if (!type.isFlatArray()) { cwriter.close(); } } @Override public void writeSchema(XmlSchema root) { if (isFlat()) { return; // there is no extra level of type. } if (hasDefinedArray(root)) { return; } XmlSchemaComplexType complex = new XmlSchemaComplexType(root, true); complex.setName(getSchemaType().getLocalPart()); XmlSchemaSequence seq = new XmlSchemaSequence(); complex.setParticle(seq); AegisType componentType = getComponentType(); XmlSchemaElement element = new XmlSchemaElement(root, false); element.setName(componentType.getSchemaType().getLocalPart()); element.setSchemaTypeName(componentType.getSchemaType()); seq.getItems().add(element); if (componentType.isNillable()) { element.setNillable(true); } element.setMinOccurs(getMinOccurs()); element.setMaxOccurs(getMaxOccurs()); } /** * Since both an Array and a List can have the same type definition, double check that there isn't already * a defined type already. * * @param root * @return */ private boolean hasDefinedArray(XmlSchema root) { return root.getTypeByName(getSchemaType().getLocalPart()) != null; } /** * We need to write a complex type schema for Beans, so return true. * * @see org.apache.cxf.aegis.type.AegisType#isComplex() */ @Override public boolean isComplex() { return true; } public QName getComponentName() { return componentName; } public void setComponentName(QName componentName) { this.componentName = componentName; } /** * @see org.apache.cxf.aegis.type.AegisType#getDependencies() */ @Override public Set<AegisType> getDependencies() { Set<AegisType> deps = new HashSet<>(); deps.add(getComponentType()); return deps; } /** * Get the <code>AegisType</code> of the elements in the array. * * @return */ public AegisType getComponentType() { Class<?> compType = getTypeClass().getComponentType(); AegisType type; if (componentName == null) { type = getTypeMapping().getType(compType); } else { type = getTypeMapping().getType(componentName); // We couldn't find the type the user specified. One is created // below instead. if (type == null) { LOG.finest("Couldn't find array component type " + componentName + ". Creating one instead."); } } if (type == null) { type = getTypeMapping().getTypeCreator().createType(compType); getTypeMapping().register(type); } return type; } public long getMaxOccurs() { return maxOccurs; } public void setMaxOccurs(long maxOccurs) { this.maxOccurs = maxOccurs; } public long getMinOccurs() { return minOccurs; } public void setMinOccurs(long minOccurs) { this.minOccurs = minOccurs; } public boolean isFlat() { return isFlatArray(); } public void setFlat(boolean flat) { setWriteOuter(!flat); setFlatArray(flat); } @Override public boolean hasMaxOccurs() { return true; } @Override public boolean hasMinOccurs() { return true; } }