diff options
Diffstat (limited to 'branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format')
17 files changed, 1869 insertions, 0 deletions
diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/BinaryBuilder.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/BinaryBuilder.java new file mode 100644 index 0000000000..3f47fd855b --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/BinaryBuilder.java @@ -0,0 +1,78 @@ +/* + * 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.tuscany.sca.binding.ws.axis2.format; + +import java.io.IOException; +import java.io.InputStream; + +import javax.activation.DataHandler; +import javax.activation.DataSource; +import javax.xml.namespace.QName; + +import org.apache.axiom.attachments.ByteArrayDataSource; +import org.apache.axiom.om.OMAbstractFactory; +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.OMFactory; +import org.apache.axis2.AxisFault; +import org.apache.axis2.context.MessageContext; +import org.apache.axis2.description.Parameter; +import org.apache.commons.io.IOUtils; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.BaseConstants; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.BaseUtils; + +/** + * Message builder for binary payloads. + * <p> + * This builder processes the input message as binary and wraps + * the data in a wrapper element. The name of the wrapper element can + * be configured as a service parameter (see {@link BaseConstants#WRAPPER_PARAM}). + * It defaults to {@link BaseConstants#DEFAULT_BINARY_WRAPPER}. + */ +public class BinaryBuilder implements DataSourceMessageBuilder { + public OMElement processDocument(DataSource dataSource, + String contentType, + MessageContext msgContext) throws AxisFault { + QName wrapperQName = BaseConstants.DEFAULT_BINARY_WRAPPER; + if (msgContext.getAxisService() != null) { + Parameter wrapperParam = msgContext.getAxisService().getParameter(BaseConstants.WRAPPER_PARAM); + if (wrapperParam != null) { + wrapperQName = BaseUtils.getQNameFromString(wrapperParam.getValue()); + } + } + OMFactory factory = OMAbstractFactory.getOMFactory(); + OMElement wrapper = factory.createOMElement(wrapperQName, null); + DataHandler dataHandler = new DataHandler(dataSource); + wrapper.addChild(factory.createOMText(dataHandler, true)); + msgContext.setDoingMTOM(true); + return wrapper; + } + + public OMElement processDocument(InputStream inputStream, + String contentType, + MessageContext msgContext) throws AxisFault { + // TODO: this could be further optimized by deferring the read operation + byte[] msgBytes; + try { + msgBytes = IOUtils.toByteArray(inputStream); + } catch (IOException ex) { + throw new AxisFault("Unable to read message payload", ex); + } + return processDocument(new ByteArrayDataSource(msgBytes), contentType, msgContext); + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/BinaryFormatter.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/BinaryFormatter.java new file mode 100644 index 0000000000..1a20d61d4b --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/BinaryFormatter.java @@ -0,0 +1,96 @@ +/* + * 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.tuscany.sca.binding.ws.axis2.format; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URL; + +import javax.activation.DataHandler; +import javax.activation.DataSource; + +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.OMNode; +import org.apache.axiom.om.OMOutputFormat; +import org.apache.axiom.om.OMText; +import org.apache.axis2.AxisFault; +import org.apache.axis2.context.MessageContext; +import org.apache.axis2.transport.http.util.URLTemplatingUtil; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.BaseConstants; + +public class BinaryFormatter implements MessageFormatterEx { + public byte[] getBytes(MessageContext messageContext, OMOutputFormat format) throws AxisFault { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + writeTo(messageContext, format, baos, true); + return baos.toByteArray(); + } + + private DataHandler getDataHandler(MessageContext messageContext) { + OMElement firstChild = messageContext.getEnvelope().getBody().getFirstElement(); + if (BaseConstants.DEFAULT_BINARY_WRAPPER.equals(firstChild.getQName())) { + OMNode omNode = firstChild.getFirstOMChild(); + if (omNode != null && omNode instanceof OMText) { + Object dh = ((OMText)omNode).getDataHandler(); + if (dh != null && dh instanceof DataHandler) { + return (DataHandler)dh; + } + } + } + return null; + } + + public void writeTo(MessageContext messageContext, OMOutputFormat format, + OutputStream outputStream, boolean preserve) throws AxisFault { + DataHandler dh = getDataHandler(messageContext); + if (dh != null) { + try { + ((DataHandler)dh).writeTo(outputStream); + } catch (IOException e) { + throw new AxisFault("Error serializing binary content of element : " + + BaseConstants.DEFAULT_BINARY_WRAPPER, e); + } + } + } + + public String getContentType(MessageContext messageContext, + OMOutputFormat format, String soapAction) { + DataHandler dh = getDataHandler(messageContext); + if (dh != null) { + return dh.getContentType(); + } else { + return null; + } + } + + public URL getTargetAddress(MessageContext messageContext, + OMOutputFormat format, URL targetURL) throws AxisFault { + return URLTemplatingUtil.getTemplatedURL(targetURL, messageContext, false); + } + + public String formatSOAPAction(MessageContext messageContext, + OMOutputFormat format, String soapAction) { + return null; + } + + public DataSource getDataSource(MessageContext messageContext, + OMOutputFormat format, String soapAction) throws AxisFault { + return getDataHandler(messageContext).getDataSource(); + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/DataSourceMessageBuilder.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/DataSourceMessageBuilder.java new file mode 100644 index 0000000000..4a299df50d --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/DataSourceMessageBuilder.java @@ -0,0 +1,77 @@ +/* + * 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.tuscany.sca.binding.ws.axis2.format; + +import javax.activation.DataSource; + +import org.apache.axiom.om.OMElement; +import org.apache.axis2.AxisFault; +import org.apache.axis2.builder.Builder; +import org.apache.axis2.context.MessageContext; + +/** + * Message builder able to build messages from {@link DataSource} objects. + * This interface can be optionally implemented by {@link Builder} + * implementations that support building messages from {@link DataSource} objects. + * Since by definition the data from a {@link DataSource} can be read multiple + * times, this interface can be used by message builders to avoid storing the + * message content in memory. + * <p> + * If a message builder implements this interface and the transport is able to + * provide the message payload as a data source, then the method defined by this + * interface should be preferred over the method defined by {@link Builder}. + * <p> + * Implementing this interface helps optimizing message processing with transports + * that use messaging providers that store messages in memory or on the file system. + * Examples are JMS and VFS. + * <p> + * The builder will typically expose the data source directly or indirectly through + * the returned {@link OMElement}, e.g. by adding to the tree an {@link org.apache.axiom.om.OMText} + * or {@link org.apache.axiom.om.OMDataSource} node referencing the data source. + * This means that the builder will not be able to guarantee that all streams requested + * from the data source are properly closed. Note that code accessing the returned + * {@link OMElement} can't be expected to take care of this since in many cases the fact + * that a data source is being used is completely transparent to that code. + * It is therefore the responsibility of the transport to make sure that all resources linked to + * the data source itself as well as any open stream requested from that data source are properly + * released after the message has been processed. Depending on the type of transport, there are + * three possible cases: + * <ol> + * <li>All resources allocated to the data source or streams requested from it are + * memory based. In that case the garbage collector will take care of freeing + * these resources and the transport should simply pass the data source object + * to the builder.</li> + * <li>There are operation system resources linked to the data source and open + * streams will become invalid when these resources are freed, i.e. + * it is not required that all streams be closed explicitly. + * In this case the transport only needs to take care to properly dispose of + * the data source after the message has been processed by the Axis2 engine.</li> + * <li>Requesting a stream from the data source allocates operation system resources + * (e.g. a network connection) that remain linked to the stream, i.e. all streams requested + * from the data source must be closed properly. In that case the transport should use + * {@link ManagedDataSourceFactory#create(DataSource)} to wrap the original data source + * before passing it to the builder. After the message has been processed it should + * then call {@link ManagedDataSource#destroy()} on the wrapper to close all remaining + * open streams.</li> + * </ol> + */ +public interface DataSourceMessageBuilder extends Builder { + public OMElement processDocument(DataSource dataSource, String contentType, + MessageContext messageContext) throws AxisFault; +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/ElementHelper.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/ElementHelper.java new file mode 100644 index 0000000000..c3cd09b67a --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/ElementHelper.java @@ -0,0 +1,111 @@ +/* + * 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.tuscany.sca.binding.ws.axis2.format; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.io.Writer; + +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.OMNode; +import org.apache.axiom.om.OMSourcedElement; +import org.apache.axiom.om.OMText; + +/** + * Utility class with methods to work on {@link OMElement} objects. + * <p> + * NOTICE: The code in this class will be moved to Axiom (or somewhere else). Use with care! + */ +public class ElementHelper { + private ElementHelper() {} + + /** + * Returns a stream representing the concatenation of the text nodes that are children of a + * given element. + * The stream returned by this method produces exactly the same character sequence as the + * the stream created by the following expression: + * <pre>new StringReader(element.getText())</pre> + * The difference is that the stream implementation returned by this method is guaranteed + * to have constant memory usage and is optimized for performance. + * + * @param element the element to read the text nodes from + * @param cache whether to enable caching when accessing the element + * @return a stream representing the concatenation of the text nodes + * + * @see OMElement#getText() + */ + public static Reader getTextAsStream(OMElement element, boolean cache) { + // If the element is not an OMSourcedElement and has not more than one child, then the most + // efficient way to get the Reader is to build a StringReader + if (!(element instanceof OMSourcedElement) && (!cache || element.isComplete())) { + OMNode child = element.getFirstOMChild(); + if (child == null) { + return new StringReader(""); + } else if (child.getNextOMSibling() == null) { + return new StringReader(child instanceof OMText ? ((OMText)child).getText() : ""); + } + } + // In all other cases, extract the data from the XMLStreamReader + return new TextFromElementReader(cache ? element.getXMLStreamReader() + : element.getXMLStreamReaderWithoutCaching()); + } + + /** + * Write the content of the text nodes that are children of a given element to a + * {@link Writer}. + * If <code>cache</code> is true, this method has the same effect as the following instruction: + * <pre>out.write(element.getText())</pre> + * The difference is that this method is guaranteed to have constant memory usage and is + * optimized for performance. + * + * @param element the element to read the text nodes from + * @param out the stream to write the content to + * @param cache whether to enable caching when accessing the element + * @throws XMLStreamException if an error occurs when reading from the element + * @throws IOException if an error occurs when writing to the stream + * + * @see OMElement#getText() + */ + public static void writeTextTo(OMElement element, Writer out, boolean cache) + throws XMLStreamException, IOException { + + XMLStreamReader reader = cache ? element.getXMLStreamReader() + : element.getXMLStreamReaderWithoutCaching(); + int depth = 0; + while (reader.hasNext()) { + switch (reader.next()) { + case XMLStreamReader.CHARACTERS: + case XMLStreamReader.CDATA: + if (depth == 1) { + out.write(reader.getText()); + } + break; + case XMLStreamReader.START_ELEMENT: + depth++; + break; + case XMLStreamReader.END_ELEMENT: + depth--; + } + } + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/ManagedDataSource.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/ManagedDataSource.java new file mode 100644 index 0000000000..d45b741b0a --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/ManagedDataSource.java @@ -0,0 +1,36 @@ +/* + * 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.tuscany.sca.binding.ws.axis2.format; + +import javax.activation.DataSource; + +/** + * Managed data source. + * This type of data source keeps track of the streams that have been + * requested using {@link DataSource#getInputStream()} and allows to + * forcibly close these streams. Any existing data source can be converted + * to a managed data source using {@link ManagedDataSourceFactory#create(DataSource)}. + */ +public interface ManagedDataSource extends DataSource { + /** + * Close all streams that have been requested from this data source + * and that are not yet closed. + */ + void destroy(); +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/ManagedDataSourceFactory.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/ManagedDataSourceFactory.java new file mode 100644 index 0000000000..0c44721ac7 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/ManagedDataSourceFactory.java @@ -0,0 +1,131 @@ +/* + * 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.tuscany.sca.binding.ws.axis2.format; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import javax.activation.DataSource; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Utility class to create {@link ManagedDataSource} objects. + */ +public class ManagedDataSourceFactory { + private static class ManagedInputStream extends FilterInputStream { + private DataSourceManager manager; + + public ManagedInputStream(DataSourceManager manager, InputStream parent) { + super(parent); + this.manager = manager; + } + + @Override + public void close() throws IOException { + if (manager != null) { + manager.notifyStreamClosed(this); + manager = null; + } + super.close(); + } + } + + private static class DataSourceManager implements InvocationHandler { + private static final Log log = LogFactory.getLog(DataSourceManager.class); + + private static final Method getInputStreamMethod; + private static final Method destroyMethod; + + static { + try { + getInputStreamMethod = DataSource.class.getMethod("getInputStream"); + destroyMethod = ManagedDataSource.class.getMethod("destroy"); + } catch (NoSuchMethodException ex) { + throw new NoSuchMethodError(ex.getMessage()); + } + } + + private final DataSource dataSource; + private final List<ManagedInputStream> openStreams = Collections.synchronizedList( + new LinkedList<ManagedInputStream>()); + + public DataSourceManager(DataSource dataSource) { + this.dataSource = dataSource; + } + + public void notifyStreamClosed(ManagedInputStream managedInputStream) { + if (!openStreams.remove(managedInputStream)) { + throw new IllegalStateException(); + } + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + try { + if (method.equals(getInputStreamMethod)) { + InputStream in = (InputStream)method.invoke(dataSource, args); + ManagedInputStream in2 = new ManagedInputStream(this, in); + openStreams.add(in2); + return in2; + } else if (method.equals(destroyMethod)) { + while (!openStreams.isEmpty()) { + try { + openStreams.get(0).close(); + } catch (IOException ex) { + log.warn("Exception when closing open stream from managed data source", ex); + } + } + return null; + } else { + return method.invoke(dataSource, args); + } + } catch (InvocationTargetException ex) { + throw ex.getCause(); + } + } + + } + + /** + * Create a {@link ManagedDataSource} proxy for an existing data source. + * This will create a dynamic proxy implementing the same interfaces as + * the original data source. + * + * @param ds the original data source + * @return a data source proxy implementing {@link ManagedDataSource} + */ + public static ManagedDataSource create(DataSource ds) { + Class<?>[] orgIfaces = ds.getClass().getInterfaces(); + Class<?>[] ifaces = new Class[orgIfaces.length+1]; + ifaces[0] = ManagedDataSource.class; + System.arraycopy(orgIfaces, 0, ifaces, 1, orgIfaces.length); + return (ManagedDataSource)Proxy.newProxyInstance( + ManagedDataSourceFactory.class.getClassLoader(), ifaces, + new DataSourceManager(ds)); + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/MessageFormatterEx.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/MessageFormatterEx.java new file mode 100644 index 0000000000..2e6b691938 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/MessageFormatterEx.java @@ -0,0 +1,44 @@ +/* + * 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.tuscany.sca.binding.ws.axis2.format; + +import javax.activation.DataSource; + +import org.apache.axiom.om.OMOutputFormat; +import org.apache.axis2.AxisFault; +import org.apache.axis2.context.MessageContext; +import org.apache.axis2.transport.MessageFormatter; + +/** + * Message formatter with extended capabilities. + * This interface adds new methods to the {@link MessageFormatter} + * interface, allowing transport to optimize data transfers. + */ +public interface MessageFormatterEx extends MessageFormatter { + /** + * Get the formatted message as a {@link DataSource} object. + * + * @param messageContext + * @param format + * @param soapAction + * @return + * @throws AxisFault + */ + DataSource getDataSource(MessageContext messageContext, OMOutputFormat format, String soapAction) throws AxisFault; +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/MessageFormatterExAdapter.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/MessageFormatterExAdapter.java new file mode 100644 index 0000000000..e93df23974 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/MessageFormatterExAdapter.java @@ -0,0 +1,83 @@ +/* + * 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.tuscany.sca.binding.ws.axis2.format; + +import java.io.OutputStream; +import java.net.URL; + +import javax.activation.DataSource; + +import org.apache.axiom.attachments.ByteArrayDataSource; +import org.apache.axiom.om.OMOutputFormat; +import org.apache.axis2.AxisFault; +import org.apache.axis2.context.MessageContext; +import org.apache.axis2.transport.MessageFormatter; + +/** + * Adapter to add the {@link MessageFormatterEx} interface to an + * existing {@link MessageFormatter}. + * It implements the {@link MessageFormatterEx#getDataSource(MessageContext, OMOutputFormat, String)} method + * using {@link MessageFormatter#getBytes(MessageContext, OMOutputFormat)} and + * {@link MessageFormatter#getContentType(MessageContext, OMOutputFormat, String)}. + */ +public class MessageFormatterExAdapter implements MessageFormatterEx { + private final MessageFormatter messageFormatter; + + public MessageFormatterExAdapter(MessageFormatter messageFormatter) { + this.messageFormatter = messageFormatter; + } + + public DataSource getDataSource(MessageContext messageContext, + OMOutputFormat format, + String soapAction) throws AxisFault { + return new ByteArrayDataSource( + getBytes(messageContext, format), + getContentType(messageContext, format, soapAction)); + } + + public String formatSOAPAction(MessageContext messageContext, + OMOutputFormat format, + String soapAction) { + return messageFormatter.formatSOAPAction(messageContext, format, soapAction); + } + + public byte[] getBytes(MessageContext messageContext, + OMOutputFormat format) throws AxisFault { + return messageFormatter.getBytes(messageContext, format); + } + + public String getContentType(MessageContext messageContext, + OMOutputFormat format, + String soapAction) { + return messageFormatter.getContentType(messageContext, format, soapAction); + } + + public URL getTargetAddress(MessageContext messageContext, + OMOutputFormat format, + URL targetURL) throws AxisFault { + return messageFormatter.getTargetAddress(messageContext, format, targetURL); + } + + public void writeTo(MessageContext messageContext, + OMOutputFormat format, + OutputStream outputStream, + boolean preserve) throws AxisFault { + messageFormatter.writeTo(messageContext, format, outputStream, preserve); + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/PlainTextBuilder.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/PlainTextBuilder.java new file mode 100644 index 0000000000..c2851bac4c --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/PlainTextBuilder.java @@ -0,0 +1,114 @@ +/* + * 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.tuscany.sca.binding.ws.axis2.format; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; + +import javax.activation.DataSource; +import javax.xml.namespace.QName; + +import org.apache.axiom.om.OMAbstractFactory; +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.OMFactory; +import org.apache.axiom.om.impl.llom.OMSourcedElementImpl; +import org.apache.axis2.AxisFault; +import org.apache.axis2.builder.BuilderUtil; +import org.apache.axis2.context.MessageContext; +import org.apache.axis2.description.Parameter; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.BaseConstants; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.BaseUtils; + +/** + * Message builder for plain text payloads. + * <p> + * This builder processes the input message as plain text and wraps + * the text in a wrapper element. The name of the wrapper element can + * be configured as a service parameter (see {@link BaseConstants#WRAPPER_PARAM}). + * It defaults to {@link BaseConstants#DEFAULT_TEXT_WRAPPER}. + * If the content is provided as an {@link InputStream} and the content type specifies a + * <tt>charset</tt> parameter (e.g. <tt>text/plain; charset=ISO-8859-15</tt>), + * this information is used to decode the text. + * If the content is provided as an {@link InputStream} but no <tt>charset</tt> parameter + * is specified on the content type, the default charset encoding specified by + * {@link MessageContext#DEFAULT_CHAR_SET_ENCODING} is used. + */ +public class PlainTextBuilder implements TextMessageBuilder, DataSourceMessageBuilder { + private static QName getWrapperQName(MessageContext msgContext) { + QName wrapperQName = BaseConstants.DEFAULT_TEXT_WRAPPER; + if (msgContext.getAxisService() != null) { + Parameter wrapperParam + = msgContext.getAxisService().getParameter(BaseConstants.WRAPPER_PARAM); + if (wrapperParam != null) { + wrapperQName = BaseUtils.getQNameFromString(wrapperParam.getValue()); + } + } + return wrapperQName; + } + + public OMElement processDocument(InputStream inputStream, + String contentType, + MessageContext msgContext) throws AxisFault { + + OMFactory factory = OMAbstractFactory.getOMFactory(); + String charSetEnc = BuilderUtil.getCharSetEncoding(contentType); + QName wrapperQName = getWrapperQName(msgContext); + Reader reader; + try { + reader = new InputStreamReader(inputStream, charSetEnc); + } catch (UnsupportedEncodingException ex) { + throw new AxisFault("Unsupported encoding: " + charSetEnc, ex); + } + return new OMSourcedElementImpl(wrapperQName, factory, + new WrappedTextNodeOMDataSourceFromReader(wrapperQName, reader)); + } + + public OMElement processDocument(Reader reader, + String contentType, + MessageContext msgContext) throws AxisFault { + + OMFactory factory = OMAbstractFactory.getOMFactory(); + QName wrapperQName = getWrapperQName(msgContext); + return new OMSourcedElementImpl(wrapperQName, factory, + new WrappedTextNodeOMDataSourceFromReader(wrapperQName, reader)); + } + + public OMElement processDocument(String content, + String contentType, + MessageContext msgContext) throws AxisFault { + OMFactory factory = OMAbstractFactory.getOMFactory(); + OMElement wrapper = factory.createOMElement(getWrapperQName(msgContext), null); + factory.createOMText(wrapper, content); + return wrapper; + } + + public OMElement processDocument(DataSource dataSource, + String contentType, + MessageContext msgContext) throws AxisFault { + + OMFactory factory = OMAbstractFactory.getOMFactory(); + Charset cs = Charset.forName(BuilderUtil.getCharSetEncoding(contentType)); + QName wrapperQName = getWrapperQName(msgContext); + return new OMSourcedElementImpl(wrapperQName, factory, + new WrappedTextNodeOMDataSourceFromDataSource(wrapperQName, dataSource, cs)); + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/PlainTextFormatter.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/PlainTextFormatter.java new file mode 100644 index 0000000000..3dc8684471 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/PlainTextFormatter.java @@ -0,0 +1,98 @@ +/* + * 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.tuscany.sca.binding.ws.axis2.format; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.net.URL; + +import javax.activation.DataSource; +import javax.xml.stream.XMLStreamException; + +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.OMOutputFormat; +import org.apache.axis2.AxisFault; +import org.apache.axis2.context.MessageContext; +import org.apache.axis2.transport.http.util.URLTemplatingUtil; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.BaseConstants; + +public class PlainTextFormatter implements MessageFormatterEx { + + public byte[] getBytes(MessageContext messageContext, OMOutputFormat format) throws AxisFault { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + writeTo(messageContext, format, baos, true); + return baos.toByteArray(); + } + + public void writeTo(MessageContext messageContext, OMOutputFormat format, OutputStream outputStream, boolean preserve) throws AxisFault { + OMElement textElt = messageContext.getEnvelope().getBody().getFirstElement(); + if (BaseConstants.DEFAULT_TEXT_WRAPPER.equals(textElt.getQName())) { + try { + Writer out = new OutputStreamWriter(outputStream, format.getCharSetEncoding()); + ElementHelper.writeTextTo(textElt, out, preserve); + out.flush(); + } catch (IOException e) { + throw new AxisFault("Error writing text message to stream", e); + } catch (XMLStreamException e) { + throw new AxisFault("Error extracting the text payload from the message", e); + } + } + } + + public String getContentType(MessageContext messageContext, OMOutputFormat format, String soapAction) { + String encoding = format.getCharSetEncoding(); + String contentType = "text/plain"; + + if (encoding != null) { + contentType += "; charset=" + encoding; + } + + // if soap action is there (can be there is soap response MEP is used) add it. + if ((soapAction != null) + && !"".equals(soapAction.trim()) + && !"\"\"".equals(soapAction.trim())) { + contentType = contentType + ";action=\"" + soapAction + "\";"; + } + + return contentType; + } + + public URL getTargetAddress(MessageContext msgCtxt, OMOutputFormat format, URL targetURL) throws AxisFault { + // Check whether there is a template in the URL, if so we have to replace then with data + // values and create a new target URL. + targetURL = URLTemplatingUtil.getTemplatedURL(targetURL, msgCtxt, false); + return targetURL; + } + + public String formatSOAPAction(MessageContext messageContext, OMOutputFormat format, String soapAction) { + return null; + } + + public DataSource getDataSource(MessageContext messageContext, + OMOutputFormat format, String soapAction) throws AxisFault { + return new TextFromElementDataSource( + messageContext.getEnvelope().getBody().getFirstElement(), + format.getCharSetEncoding(), + getContentType(messageContext, format, soapAction)); + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/TextFromElementDataSource.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/TextFromElementDataSource.java new file mode 100644 index 0000000000..a5597eeea8 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/TextFromElementDataSource.java @@ -0,0 +1,65 @@ +/* + * 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.tuscany.sca.binding.ws.axis2.format; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import javax.activation.DataSource; + +import org.apache.axiom.om.OMElement; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.streams.ReaderInputStream; + +/** + * Data source that represents the text of a given {@link OMElement}. + * <p> + * The expression + * <pre>new TextFromElementDataSource(element, charset, contentType)</pre> + * produces a DataSource implementation that is equivalent to + * <pre>new ByteArrayDataSource(element.getText().getBytes(charset), contentType)</pre> + * but that is more efficient. + */ +public class TextFromElementDataSource implements DataSource { + private final OMElement element; + private final String charset; + private final String contentType; + + public TextFromElementDataSource(OMElement element, String charset, String contentType) { + this.element = element; + this.charset = charset; + this.contentType = contentType; + } + + public String getContentType() { + return contentType; + } + + public String getName() { + return null; + } + + public InputStream getInputStream() throws IOException { + return new ReaderInputStream(ElementHelper.getTextAsStream(element, true), charset); + } + + public OutputStream getOutputStream() throws IOException { + throw new UnsupportedOperationException(); + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/TextFromElementReader.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/TextFromElementReader.java new file mode 100644 index 0000000000..e4f0931bfc --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/TextFromElementReader.java @@ -0,0 +1,160 @@ +/* + * 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.tuscany.sca.binding.ws.axis2.format; + +import java.io.IOException; +import java.io.Reader; + +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +import org.apache.axiom.om.OMException; + +/** + * {@link Reader} implementation that extracts the text nodes from an element given by an + * {@link XMLStreamReader}. The expected input is a document with only a document + * element (as produced by {@link org.apache.axiom.om.OMElement.OMElement#getXMLStreamReader()}). + * The class will extract the text nodes that are direct children of that element, i.e. it uses + * the same conventions as {@link org.apache.axiom.om.OMElement.OMElement#getText()}. + * It will call {@link XMLStreamReader#close()} when the end of the document is reached or when + * {@link #close()} is called. + * <p> + * The main purpose of this class is to provide a convenient and efficient way to get the text + * content of an element without converting it first to a string, i.e. without using + * {@link org.apache.axiom.om.OMElement.OMElement#getText()}. This is important for potentially + * large contents, for which this class guarantees constant memory usage. + * <p> + * Note that this class should in general not be used directly. Instead, + * {@link ElementHelper#getTextAsStream(org.apache.axiom.om.OMElement)} + * should be called to get the most efficient stream implementation for a given an element. + * <p> + * NOTICE: The code in this class will be moved to Axiom (or somewhere else). Use with care! + */ +public class TextFromElementReader extends Reader { + private final XMLStreamReader stream; + + /** + * Flag indicating that we have reached the end of the document and that the underlying + * parser has been closed. + */ + private boolean endOfStream; + + /** + * The current depth relative to the document element (not the document). A value greater than + * 0 indicates that we are inside a nested element and that we need to skip text nodes. + */ + private int skipDepth; + + /** + * The current position in the character data of the event, or -1 if all the character data + * has been consumed and a new event needs to be requested from the parser. + */ + private int sourceStart = -1; + + /** + * Constructor. + * + * @param stream the stream to extract the text nodes from + * @throws OMException if the stream doesn't start with the expected events + */ + public TextFromElementReader(XMLStreamReader stream) { + this.stream = stream; + try { + if (stream.getEventType() != XMLStreamReader.START_DOCUMENT) { + throw new OMException("Expected START_DOCUMENT as first event from parser"); + } + if (stream.next() != XMLStreamReader.START_ELEMENT) { + throw new OMException("Expected START_ELEMENT event"); + } + } catch (XMLStreamException ex) { + throw new OMException(ex); + } + } + + @Override + public int read(char[] cbuf, int off, int len) throws IOException { + if (endOfStream) { + return -1; + } + int read = 0; + try { + while (true) { + if (sourceStart == -1) { + eventLoop: while (true) { + int type = stream.next(); + switch (type) { + case XMLStreamReader.CHARACTERS: + case XMLStreamReader.CDATA: + if (skipDepth == 0) { + sourceStart = 0; + break eventLoop; + } + break; + case XMLStreamReader.START_ELEMENT: + skipDepth++; + break; + case XMLStreamReader.END_ELEMENT: + if (skipDepth == 0) { + if (stream.next() == XMLStreamReader.END_DOCUMENT) { + endOfStream = true; + stream.close(); + return read == 0 ? -1 : read; + } else { + throw new IOException( + "End of document expected after element"); + } + } else { + skipDepth--; + } + } + } + } + int c = stream.getTextCharacters(sourceStart, cbuf, off, len); + sourceStart += c; + off += c; + len -= c; + read += c; + if (len > 0) { + sourceStart = -1; + } else { + return read; + } + } + } catch (XMLStreamException ex) { + IOException ex2 = new IOException("Got an exception from the underlying parser " + + "while reading the content of an element"); + ex2.initCause(ex); + throw ex2; + } + } + + @Override + public void close() throws IOException { + if (!endOfStream) { + try { + stream.close(); + } catch (XMLStreamException ex) { + IOException ex2 = new IOException("Error when trying to close underlying parser"); + ex2.initCause(ex); + throw ex2; + } + } + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/TextMessageBuilder.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/TextMessageBuilder.java new file mode 100644 index 0000000000..5c7623d7e2 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/TextMessageBuilder.java @@ -0,0 +1,48 @@ +/* + * 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.tuscany.sca.binding.ws.axis2.format; + +import java.io.Reader; + +import org.apache.axiom.om.OMElement; +import org.apache.axis2.AxisFault; +import org.apache.axis2.builder.Builder; +import org.apache.axis2.context.MessageContext; + +/** + * Message builder able to build messages from a character stream. + * This interface can be optionally implemented by {@link Builder} + * implementations that support building a message from a character + * stream. + * <p> + * The character stream can either be provided as a string or a + * {@link Reader} object. The caller should use a {@link Reader} object + * except if the content of the message is available as a string anyway. + * <p> + * This interface is currently used by the JMS transport to process + * {@link javax.jms.TextMessage} instances. + */ +public interface TextMessageBuilder extends Builder { + public OMElement processDocument(Reader reader, String contentType, + MessageContext messageContext) throws AxisFault; + + public OMElement processDocument(String content, String contentType, + MessageContext messageContext) throws AxisFault; +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/TextMessageBuilderAdapter.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/TextMessageBuilderAdapter.java new file mode 100644 index 0000000000..692c4900b7 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/TextMessageBuilderAdapter.java @@ -0,0 +1,78 @@ +/* + * 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.tuscany.sca.binding.ws.axis2.format; + +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; + +import javax.mail.internet.ContentType; +import javax.mail.internet.ParseException; + +import org.apache.axiom.om.OMElement; +import org.apache.axis2.AxisFault; +import org.apache.axis2.Constants; +import org.apache.axis2.builder.Builder; +import org.apache.axis2.context.MessageContext; +import org.apache.tuscany.sca.binding.ws.axis2.transport.base.streams.ReaderInputStream; + +/** + * Adapter to add the {@link TextMessageBuilder} interface to an + * existing {@link Builder}. + * It implements the {@link TextMessageBuilder#processDocument(Reader, String, MessageContext)} + * and {@link TextMessageBuilder#processDocument(String, String, MessageContext)} by converting + * the character stream to a byte stream using {@link ReaderInputStream}. + * + * TODO: specifying encoding + */ +public class TextMessageBuilderAdapter implements TextMessageBuilder { + private final Builder builder; + + public TextMessageBuilderAdapter(Builder builder) { + this.builder = builder; + } + + public OMElement processDocument(InputStream inputStream, String contentType, + MessageContext messageContext) throws AxisFault { + return builder.processDocument(inputStream, contentType, messageContext); + } + + public OMElement processDocument(Reader reader, String contentType, + MessageContext messageContext) throws AxisFault { + String charset; + try { + ContentType ct = new ContentType(contentType); + charset = ct.getParameter("charset"); + } catch (ParseException ex) { + charset = null; + } + if (charset == null) { + charset = MessageContext.DEFAULT_CHAR_SET_ENCODING; + } + messageContext.setProperty(Constants.Configuration.CHARACTER_SET_ENCODING, charset); + return processDocument(new ReaderInputStream(reader, charset), contentType, + messageContext); + } + + public OMElement processDocument(String content, String contentType, + MessageContext messageContext) throws AxisFault { + return processDocument(new StringReader(content), contentType, messageContext); + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/WrappedTextNodeOMDataSourceFromDataSource.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/WrappedTextNodeOMDataSourceFromDataSource.java new file mode 100644 index 0000000000..d8a5e65f59 --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/WrappedTextNodeOMDataSourceFromDataSource.java @@ -0,0 +1,114 @@ +/* + * 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.tuscany.sca.binding.ws.axis2.format; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.nio.charset.Charset; + +import javax.activation.DataSource; +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLStreamWriter; + +import org.apache.axiom.om.OMDataSourceExt; +import org.apache.axiom.om.OMOutputFormat; +import org.apache.axiom.om.ds.OMDataSourceExtBase; +import org.apache.axiom.om.impl.MTOMXMLStreamWriter; +import org.apache.axiom.om.impl.serialize.StreamingOMSerializer; +import org.apache.axiom.om.util.StAXUtils; + +/** + * {@link org.apache.axiom.om.OMDataSource} implementation that represents a text node wrapped + * inside an element. The text data is provided by a {@link DataSource} object. + */ +public class WrappedTextNodeOMDataSourceFromDataSource extends OMDataSourceExtBase { + private final QName wrapperElementName; + private final DataSource binaryData; + private final Charset charset; + + public WrappedTextNodeOMDataSourceFromDataSource(QName wrapperElementName, DataSource binaryData, + Charset charset) { + this.wrapperElementName = wrapperElementName; + this.binaryData = binaryData; + this.charset = charset; + } + + @Override + public void serialize(OutputStream out, OMOutputFormat format) throws XMLStreamException { + XMLStreamWriter writer = new MTOMXMLStreamWriter(out, format); + serialize(writer); + writer.flush(); + } + + @Override + public void serialize(Writer writer, OMOutputFormat format) throws XMLStreamException { + MTOMXMLStreamWriter xmlWriter = + new MTOMXMLStreamWriter(StAXUtils.createXMLStreamWriter(writer)); + xmlWriter.setOutputFormat(format); + serialize(xmlWriter); + xmlWriter.flush(); + } + + @Override + public void serialize(XMLStreamWriter xmlWriter) throws XMLStreamException { + StreamingOMSerializer serializer = new StreamingOMSerializer(); + serializer.serialize(getReader(), xmlWriter); + } + + public XMLStreamReader getReader() throws XMLStreamException { + InputStream is; + try { + is = binaryData.getInputStream(); + } + catch (IOException ex) { + throw new XMLStreamException(ex); + } + return new WrappedTextNodeStreamReader(wrapperElementName, new InputStreamReader(is, charset)); + } + + public Object getObject() { + return binaryData; + } + + public boolean isDestructiveRead() { + return false; + } + + public boolean isDestructiveWrite() { + return false; + } + + public byte[] getXMLBytes(String encoding) throws UnsupportedEncodingException { + throw new UnsupportedOperationException(); + } + + public void close() { + } + + public OMDataSourceExt copy() { + return new WrappedTextNodeOMDataSourceFromDataSource(wrapperElementName, binaryData, charset); + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/WrappedTextNodeOMDataSourceFromReader.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/WrappedTextNodeOMDataSourceFromReader.java new file mode 100644 index 0000000000..21c35f610c --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/WrappedTextNodeOMDataSourceFromReader.java @@ -0,0 +1,99 @@ +/* + * 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.tuscany.sca.binding.ws.axis2.format; + +import java.io.IOException; +import java.io.Reader; +import java.io.UnsupportedEncodingException; +import java.io.Writer; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLStreamWriter; + +import org.apache.axiom.om.OMDataSourceExt; +import org.apache.axiom.om.OMOutputFormat; +import org.apache.axiom.om.ds.OMDataSourceExtBase; +import org.apache.axiom.om.impl.MTOMXMLStreamWriter; +import org.apache.axiom.om.impl.serialize.StreamingOMSerializer; +import org.apache.axiom.om.util.StAXUtils; + +/** + * {@link org.apache.axiom.om.OMDataSource} implementation that represents a text node wrapped + * inside an element. The text data is provided by a {@link Reader} object. Since the stream + * can only be read once, this data source is destructive. + */ +public class WrappedTextNodeOMDataSourceFromReader extends OMDataSourceExtBase { + private final QName wrapperElementName; + private final Reader reader; + + public WrappedTextNodeOMDataSourceFromReader(QName wrapperElementName, Reader reader) { + this.wrapperElementName = wrapperElementName; + this.reader = reader; + } + + @Override + public void serialize(Writer writer, OMOutputFormat format) throws XMLStreamException { + MTOMXMLStreamWriter xmlWriter = + new MTOMXMLStreamWriter(StAXUtils.createXMLStreamWriter(writer)); + xmlWriter.setOutputFormat(format); + serialize(xmlWriter); + xmlWriter.flush(); + } + + @Override + public void serialize(XMLStreamWriter xmlWriter) throws XMLStreamException { + StreamingOMSerializer serializer = new StreamingOMSerializer(); + serializer.serialize(getReader(), xmlWriter); + } + + public XMLStreamReader getReader() throws XMLStreamException { + return new WrappedTextNodeStreamReader(wrapperElementName, reader); + } + + public Object getObject() { + return null; + } + + public boolean isDestructiveRead() { + return true; + } + + public boolean isDestructiveWrite() { + return true; + } + + public byte[] getXMLBytes(String encoding) throws UnsupportedEncodingException { + throw new UnsupportedOperationException(); + } + + public void close() { + try { + reader.close(); + } catch (IOException ex) { + // Ignore + } + } + + public OMDataSourceExt copy() { + throw new UnsupportedOperationException(); + } +} diff --git a/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/WrappedTextNodeStreamReader.java b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/WrappedTextNodeStreamReader.java new file mode 100644 index 0000000000..c656949b1f --- /dev/null +++ b/branches/sca-java-1.x/modules/binding-ws-axis2-jms/src/main/java/org/apache/tuscany/sca/binding/ws/axis2/format/WrappedTextNodeStreamReader.java @@ -0,0 +1,437 @@ +/* + * 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.tuscany.sca.binding.ws.axis2.format; + +import java.io.IOException; +import java.io.Reader; +import java.util.Collections; + +import javax.xml.XMLConstants; +import javax.xml.namespace.NamespaceContext; +import javax.xml.namespace.QName; +import javax.xml.stream.Location; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +import org.apache.axiom.om.impl.EmptyOMLocation; +import org.apache.axiom.om.impl.llom.util.NamespaceContextImpl; +import org.apache.commons.io.IOUtils; + +/** + * {@link XMLInputStreamReader} implementation that + * represents a text node wrapped inside an element. The text data is provided by a + * {@link java.io.Reader Reader}. + * <p> + * It will produce the following sequence of XML events: + * <ul> + * <li>START_DOCUMENT</li> + * <li>START_ELEMENT</li> + * <li>(CHARACTER)*</li> + * <li>END_ELEMENT</li> + * <li>END_DOCMENT</li> + * </ul> + * The class is implemented as a simple state machine, where the state is identified + * by the current event type. The initial state is <tt>START_DOCUMENT</tt> and the + * following transitions are triggered by {@link #next()}: + * <ul> + * <li>START_DOCUMENT → START_ELEMENT</li> + * <li>START_ELEMENT → END_ELEMENT (if character stream is empty)</li> + * <li>START_ELEMENT → CHARACTERS (if character stream is not empty)</li> + * <li>CHARACTERS → CHARACTERS (if data available in stream)</li> + * <li>CHARACTERS → END_ELEMENT (if end of stream reached)</li> + * <li>END_ELEMENT → END_DOCUMENT</li> + * </ul> + * Additionally, {@link #getElementText()} triggers the following transition: + * <ul> + * <li>START_ELEMENT → END_ELEMENT</li> + * </ul> + * Note that since multiple consecutive CHARACTERS events may be returned, this + * "parser" is not coalescing. + * + */ +// TODO: This class has been copied from Synapse (package org.apache.synapse.util). +// Once it has been moved to Axis2 or Axiom, remove the duplicate from Synapse. +public class WrappedTextNodeStreamReader implements XMLStreamReader { + /** + * Location object returned by {@link #getLocation()}. + * It always returns -1 for the location and null for the publicId and systemId. + */ + private final static Location EMPTY_LOCATION = new EmptyOMLocation(); + + /** + * The qualified name of the wrapper element. + */ + private final QName wrapperElementName; + + /** + * The Reader object that represents the text data. + */ + private final Reader reader; + + /** + * The maximum number of characters to return for each CHARACTER event. + */ + private final int chunkSize; + + /** + * The type of the current XML event. + */ + private int eventType = START_DOCUMENT; + + /** + * The character data for the current event. This is only set if the current + * event is a CHARACTER event. The size of the array is determined by + * {@link #chunkSize} + */ + private char[] charData; + + /** + * The length of the character data in {@link #charData}. + */ + private int charDataLength; + + /** + * The namespace context applicable in the scope of the wrapper element. + * Beside the default mappings for xml and xmlns, it only contains the + * mapping for the namespace of the wrapper element. + * This attribute is initialized lazily by {@link #getNamespaceContext()}. + */ + private NamespaceContext namespaceContext; + + /** + * Create a new instance. + * + * @param wrapperElementName the qualified name of the wrapper element + * @param reader the Reader object holding the character data to be wrapped + * @param chunkSize the maximum number of characters that are returned for each CHARACTER event + */ + public WrappedTextNodeStreamReader(QName wrapperElementName, Reader reader, int chunkSize) { + this.wrapperElementName = wrapperElementName; + this.reader = reader; + this.chunkSize = chunkSize; + } + + /** + * Create a new instance with chunk size 4096. + * + * @param wrapperElementName the qualified name of the wrapper element + * @param reader the Reader object holding the character data to be wrapped + */ + public WrappedTextNodeStreamReader(QName wrapperElementName, Reader reader) { + this(wrapperElementName, reader, 4096); + } + + public Object getProperty(String name) throws IllegalArgumentException { + // We don't define any properties + return null; + } + + // + // Methods to manipulate the parser state + // + + public boolean hasNext() throws XMLStreamException { + return eventType != END_DOCUMENT; + } + + public int next() throws XMLStreamException { + // Determine next event type based on current event type. If current event type + // is START_ELEMENT or CHARACTERS, pull new data from the reader. + switch (eventType) { + case START_DOCUMENT: + eventType = START_ELEMENT; + break; + case START_ELEMENT: + charData = new char[chunkSize]; + // No break here! + case CHARACTERS: + try { + charDataLength = reader.read(charData); + } + catch (IOException ex) { + throw new XMLStreamException(ex); + } + if (charDataLength == -1) { + charData = null; + eventType = END_ELEMENT; + } else { + eventType = CHARACTERS; + } + break; + case END_ELEMENT: + eventType = END_DOCUMENT; + break; + default: + throw new IllegalStateException(); + } + return eventType; + } + + public int nextTag() throws XMLStreamException { + // We don't have white space, comments or processing instructions + throw new XMLStreamException("Current event is not white space"); + } + + public int getEventType() { + return eventType; + } + + public boolean isStartElement() { return eventType == START_ELEMENT; } + public boolean isEndElement() { return eventType == END_ELEMENT; } + public boolean isCharacters() { return eventType == CHARACTERS; } + public boolean isWhiteSpace() { return false; } + public boolean hasText() { return eventType == CHARACTERS; } + public boolean hasName() { return eventType == START_ELEMENT || eventType == END_ELEMENT; } + + public void require(int type, String namespaceURI, String localName) throws XMLStreamException { + if (type != eventType + || (namespaceURI != null && !namespaceURI.equals(getNamespaceURI())) + || (localName != null && !namespaceURI.equals(getLocalName()))) { + throw new XMLStreamException("Unexpected event type"); + } + } + + public Location getLocation() { + // We do not support location information + return EMPTY_LOCATION; + } + + public void close() throws XMLStreamException { + // Javadoc says that this method should not close the underlying input source, + // but we need to close the reader somewhere. + try { + reader.close(); + } + catch (IOException ex) { + throw new XMLStreamException(ex); + } + } + + // + // Methods related to the xml declaration. + // + + public String getEncoding() { + // Encoding is not known (not relevant?) + return null; + } + + public String getCharacterEncodingScheme() { + // Encoding is not known (not relevant?) + return null; + } + + public String getVersion() { + // Version is not relevant + return null; + } + + public boolean standaloneSet() { + return false; + } + + public boolean isStandalone() { + return true; + } + + // + // Methods related to the namespace context + // + + public NamespaceContext getNamespaceContext() { + if (namespaceContext == null) { + namespaceContext = new NamespaceContextImpl(Collections.singletonMap(wrapperElementName.getPrefix(), wrapperElementName.getNamespaceURI())); + } + return namespaceContext; + } + + public String getNamespaceURI(String prefix) { + String namespaceURI = getNamespaceContext().getNamespaceURI(prefix); + // NamespaceContext#getNamespaceURI and XMLStreamReader#getNamespaceURI have slightly + // different semantics for unbound prefixes. + return namespaceURI.equals(XMLConstants.NULL_NS_URI) ? null : prefix; + } + + // + // Methods related to elements + // + + private void checkStartElement() { + if (eventType != START_ELEMENT) { + throw new IllegalStateException(); + } + } + + public String getAttributeValue(String namespaceURI, String localName) { + checkStartElement(); + return null; + } + + public int getAttributeCount() { + checkStartElement(); + return 0; + } + + public QName getAttributeName(int index) { + checkStartElement(); + throw new ArrayIndexOutOfBoundsException(); + } + + public String getAttributeLocalName(int index) { + checkStartElement(); + throw new ArrayIndexOutOfBoundsException(); + } + + public String getAttributePrefix(int index) { + checkStartElement(); + throw new ArrayIndexOutOfBoundsException(); + } + + public String getAttributeNamespace(int index) { + checkStartElement(); + throw new ArrayIndexOutOfBoundsException(); + } + + public String getAttributeType(int index) { + checkStartElement(); + throw new ArrayIndexOutOfBoundsException(); + } + + public String getAttributeValue(int index) { + checkStartElement(); + throw new ArrayIndexOutOfBoundsException(); + } + + public boolean isAttributeSpecified(int index) { + checkStartElement(); + throw new ArrayIndexOutOfBoundsException(); + } + + private void checkElement() { + if (eventType != START_ELEMENT && eventType != END_ELEMENT) { + throw new IllegalStateException(); + } + } + + public QName getName() { + return null; + } + + public String getLocalName() { + checkElement(); + return wrapperElementName.getLocalPart(); + } + + public String getPrefix() { + return wrapperElementName.getPrefix(); + } + + public String getNamespaceURI() { + checkElement(); + return wrapperElementName.getNamespaceURI(); + } + + public int getNamespaceCount() { + checkElement(); + // There is one namespace declared on the wrapper element + return 1; + } + + public String getNamespacePrefix(int index) { + checkElement(); + if (index == 0) { + return wrapperElementName.getPrefix(); + } else { + throw new IndexOutOfBoundsException(); + } + } + + public String getNamespaceURI(int index) { + checkElement(); + if (index == 0) { + return wrapperElementName.getNamespaceURI(); + } else { + throw new IndexOutOfBoundsException(); + } + } + + public String getElementText() throws XMLStreamException { + if (eventType == START_ELEMENT) { + // Actually the purpose of this class is to avoid storing + // the character data entirely in memory, but if the caller + // wants a String, we don't have the choice... + try { + String result = IOUtils.toString(reader); + eventType = END_ELEMENT; + return result; + } + catch (IOException ex) { + throw new XMLStreamException(ex); + } + } else { + throw new XMLStreamException("Current event is not a START_ELEMENT"); + } + } + + private void checkCharacters() { + if (eventType != CHARACTERS) { + throw new IllegalStateException(); + } + } + + public String getText() { + checkCharacters(); + return new String(charData, 0, charDataLength); + } + + public char[] getTextCharacters() { + checkCharacters(); + return charData; + } + + public int getTextStart() { + checkCharacters(); + return 0; + } + + public int getTextLength() { + checkCharacters(); + return charDataLength; + } + + public int getTextCharacters(int sourceStart, char[] target, int targetStart, int length) throws XMLStreamException { + checkCharacters(); + int c = Math.min(charDataLength-sourceStart, length); + System.arraycopy(charData, sourceStart, target, targetStart, c); + return c; + } + + // + // Methods related to processing instructions + // + + public String getPIData() { + throw new IllegalStateException(); + } + + public String getPITarget() { + throw new IllegalStateException(); + } +} |