blob: a9e62d50b0e10e4dc52238a502b0360f53079068 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.uima.adapter.vinci;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import org.apache.uima.UIMAFramework;
import org.apache.uima.UimaContext;
import org.apache.uima.adapter.vinci.util.Constants;
import org.apache.uima.cas.CAS;
import org.apache.uima.cas.impl.OutOfTypeSystemData;
import org.apache.uima.cas.impl.XCASDeserializer;
import org.apache.uima.cas.impl.XCASSerializer;
import org.apache.uima.util.CasPool;
import org.apache.uima.util.Level;
import org.apache.vinci.debug.Debug;
import org.apache.vinci.transport.FrameLeaf;
import org.apache.vinci.transport.KeyValuePair;
import org.apache.vinci.transport.TransportConstants;
import org.apache.vinci.transport.Transportable;
import org.apache.vinci.transport.VinciFrame;
import org.apache.vinci.transport.XTalkTransporter;
import org.apache.vinci.transport.document.XTalkToSAX;
public class CASTransportable extends DefaultHandler implements Transportable {
private CasPool myCasPool;
private CAS myCas;
private byte[] mybuf = new byte[512]; // temporary work buffer
private OutOfTypeSystemData outOfTypeSystemData;
private boolean incomingCommand, incomingError, incomingExtraData;
private String lastqName;
private String command;
private String error;
private int ready;
private ContentHandler handler;
private VinciFrame extraDataFrame;
public UimaContext uimaContext; // needed for sofa mappings
public boolean includeDocText;
public boolean ignoreResponse = false; // for performance testing only.
/**
* This constructor is used on the service side - a CAS Pool reference is provided. We don't check
* a CAS out of the pool until we get a request.
*
* @param casPool
* @param outOfTypeSystemData
* @param uimaContext
* @param includeDocText
*/
public CASTransportable(CasPool casPool, OutOfTypeSystemData outOfTypeSystemData,
UimaContext uimaContext, boolean includeDocText) {
// Debug.p("Creating new CASTransportable.");
this.myCasPool = casPool;
this.myCas = null;
this.outOfTypeSystemData = outOfTypeSystemData;
this.uimaContext = uimaContext;
this.extraDataFrame = new VinciFrame();
this.includeDocText = includeDocText;
}
/**
* This constructor is used on the client side, where we have a dedicated CAS instance for the
* request.
*
* @param cas
* @param outOfTypeSystemData
* @param uimaContext
* @param includeDocText
*/
public CASTransportable(CAS cas, OutOfTypeSystemData outOfTypeSystemData,
UimaContext uimaContext, boolean includeDocText) {
// Debug.p("Creating new CASTransportable.");
this.myCas = cas;
this.myCasPool = null;
this.outOfTypeSystemData = outOfTypeSystemData;
this.uimaContext = uimaContext;
this.extraDataFrame = new VinciFrame();
this.includeDocText = includeDocText;
}
public VinciFrame getExtraDataFrame() {
return extraDataFrame;
}
public OutOfTypeSystemData getOutOfTypeSystemData() {
return this.outOfTypeSystemData;
}
public String getCommand() {
return command;
}
public void setCommand(String command) {
this.command = command;
}
public CAS getCas() {
return myCas;
}
/**
* This nested class handles serializing the CAS to XTalk through events provided by an
* XCASSerializer.
*/
class XTalkSerializer extends DefaultHandler {
OutputStream os;
XCASSerializer serializer;
boolean started;
XTalkSerializer(OutputStream os, XCASSerializer s) {
this.os = os;
this.serializer = s;
}
public void startDocument() throws SAXException {
try {
os.write(XTalkTransporter.HEADER);
XTalkTransporter.stringToBin("vinci:FRAME", os, mybuf);
XTalkTransporter.writeInt(0, os); // no attributes
if (command == null) {
XTalkTransporter.writeInt(1, os); // 1 child (DATA)
} else {
XTalkTransporter.writeInt(2, os); // 2 children (vinci:COMMAND & DATA)
// Write the vinci:COMMAND
os.write(XTalkTransporter.ELEMENT_MARKER);
XTalkTransporter.stringToBin(TransportConstants.COMMAND_KEY, os, mybuf);
XTalkTransporter.writeInt(0, os); // no attributes
XTalkTransporter.writeInt(1, os); // 1 child (pcdata)
os.write(XTalkTransporter.STRING_MARKER);
XTalkTransporter.stringToBin(command, os, mybuf);
}
// write the DATA sub-frame header
os.write(XTalkTransporter.ELEMENT_MARKER);
XTalkTransporter.stringToBin("DATA", os, mybuf);
XTalkTransporter.writeInt(0, os); // no attributes
int children = 1 + extraDataFrame.getKeyValuePairCount();
XTalkTransporter.writeInt(children, os); // 1 child (KEYS) + extra data fields...
started = false; // triggers first startElement() call to write "KEYS" instead of "CAS"
// Write extra data...
for (int i = 0; i < extraDataFrame.getKeyValuePairCount(); i++) {
KeyValuePair k = extraDataFrame.getKeyValuePair(i);
os.write(XTalkTransporter.ELEMENT_MARKER);
XTalkTransporter.stringToBin(k.getKey(), os, mybuf);
XTalkTransporter.writeInt(0, os); // no attributes
XTalkTransporter.writeInt(1, os); // 1 child (pcdata)
os.write(XTalkTransporter.STRING_MARKER);
XTalkTransporter.stringToBin(k.getValueAsString(), os, mybuf);
}
} catch (IOException e) {
throw wrapAsSAXException(e);
}
}
void attributesToXTalk(org.xml.sax.Attributes attributes) throws IOException {
int size = attributes.getLength();
XTalkTransporter.writeInt(size, os);
// Debug.p("Serializing " + size + " attributes.");
for (int i = 0; i < size; i++) {
XTalkTransporter.stringToBin(attributes.getQName(i), os, mybuf);
XTalkTransporter.stringToBin(attributes.getValue(i), os, mybuf);
}
}
public void endElement(String uri, String name, String qName) throws SAXException {
// Debug only
// Debug.p("Ending element: " + qName);
}
public void startElement(String uri, String name, String qName, org.xml.sax.Attributes atts)
throws SAXException {
try {
// Debug.p("Start element: " + qName + " : " + serializer.getNumChildren());
os.write(XTalkTransporter.ELEMENT_MARKER);
if (!started) {
Debug.Assert(XCASSerializer.casTagName.equals(qName));
started = true;
// for some reason we have to replace "CAS" with "KEYS" as the CAS root tag.
XTalkTransporter.stringToBin(Constants.KEYS, os);
started = true;
} else {
XTalkTransporter.stringToBin(qName, os);
}
attributesToXTalk(atts);
XTalkTransporter.writeInt(serializer.getNumChildren(), os); // HACK to find out # of
// children
} catch (IOException e) {
throw wrapAsSAXException(e);
}
}
public void characters(char[] ch, int start, int length) throws SAXException {
// Debug.p("Chars: " + new String(ch, start, length));
try {
os.write(XTalkTransporter.STRING_MARKER);
XTalkTransporter.stringToBin(ch, start, length, os, mybuf);
} catch (IOException e) {
throw wrapAsSAXException(e);
}
}
}
public KeyValuePair fromStream(InputStream is) throws IOException {
// Debug.p("CASTransportable.fromStream");
boolean done = false;
try {
XTalkToSAX converter = new XTalkToSAX();
// Debug.p("parsing...");
converter.parse(is, this);
// Debug.p("...done parsing.");
done = true;
} catch (SAXException e) {
//if SAXException wraps an IOException, throw the IOException. This is
//important since different types of IOExceptions (e.g. SocketTimeoutExceptions)
//are treated differently by Vinci
throw convertToIOException(e);
} finally {
if (!done) {
cleanup(); // release the cas back to the pool if we didn't parse successfully.
}
}
if (error != null) {
return new KeyValuePair(TransportConstants.ERROR_KEY, new FrameLeaf(error));
}
// Debug.p("Testing: " + extraDataFrame.toXML());
return null;
}
/**
* Serialize the CAS to the stream in XTalk format. After serialization is complete the cas is
* returned to the pool (if it was allocated from a pool.)
*/
public void toStream(OutputStream os) throws IOException {
try {
UIMAFramework.getLogger().log(Level.FINEST, "Serializing CAS.");
XCASSerializer xcasSerializer = new XCASSerializer(myCas.getTypeSystem(), this.uimaContext);
// Not sure why we need to do the next two lines:
xcasSerializer.setDocumentTypeName(Constants.VINCI_DETAG);
xcasSerializer.setDocumentTextFeature(null);
XTalkSerializer s = new XTalkSerializer(os, xcasSerializer);
try {
xcasSerializer.serialize(myCas, s, includeDocText, outOfTypeSystemData);
} catch (org.xml.sax.SAXException e) {
//if SAXException wraps an IOException, throw the IOException. This is
//important since different types of IOExceptions (e.g. SocketTimeoutExceptions)
//are treated differently by Vinci
throw convertToIOException(e);
}
UIMAFramework.getLogger().log(Level.FINEST, "CAS Serialization Complete.");
} catch (IOException e) {
UIMAFramework.getLogger().log(Level.WARNING, e.getMessage(), e);
throw e;
} catch (RuntimeException e) {
UIMAFramework.getLogger().log(Level.WARNING, e.getMessage(), e);
throw e;
} finally {
if (myCasPool != null) {
myCasPool.releaseCas(myCas);
myCas = null;
UIMAFramework.getLogger().log(Level.FINEST, "Released CAS back to pool.");
}
}
}
public void cleanup() {
if (myCas != null && myCasPool != null) {
myCasPool.releaseCas(myCas);
myCas = null;
}
}
protected void finalize() {
// Though unlikely, there could be unusual cases where
// toStream is not ever invoked, so in these cases this
// finalizer will ensure the cas is returned to the pool to
// avoid a leak.
if (myCas != null && myCasPool != null) {
Debug.p("WARNING: releasing cas in finalizer.");
myCasPool.releaseCas(myCas);
}
}
public void startElement(String uri, String name, String qName, org.xml.sax.Attributes atts)
throws SAXException {
// Debug.p("Start element: " + qName);
if (ready > 0) {
handler.startElement(uri, name, qName, atts);
} else {
if (TransportConstants.COMMAND_KEY.equals(qName)) {
incomingCommand = true;
} else if (TransportConstants.ERROR_KEY.equals(qName) || "Error".equals(qName)) {
incomingError = true;
} else {
lastqName = qName;
incomingExtraData = true;
}
}
if (Constants.KEYS.equals(qName)) {
// the data inside the KEYS element is the contents of an incoming CAS.
// So this is where we need to grab a CAS from the CasPool and initialize
//the XCASDeserializer.
if (myCas == null) {
myCas = myCasPool.getCas(0);
}
myCas.reset();
XCASDeserializer deser = new XCASDeserializer(myCas.getTypeSystem(), this.uimaContext);
deser.setDocumentTypeName("Detag:DetagContent");
if (!ignoreResponse) {
handler = deser.getXCASHandler(myCas, outOfTypeSystemData);
} else {
handler = new DefaultHandler();
}
handler.startDocument();
handler.startElement("", "CAS", "CAS", null);
//set the ready flag to indicate that following elements are CAS data
ready++;
}
}
public void endElement(String uri, String name, String qName) throws SAXException {
// Debug.p("End element: " + qName);
if (Constants.KEYS.equals(qName)) {
ready--;
if (ready == 0) {
handler.endElement("", "CAS", "CAS");
handler.endDocument();
}
}
if (ready > 0) {
handler.endElement(uri, name, qName);
}
}
public void characters(char[] ch, int start, int length) throws SAXException {
// Debug.p("characters: " + new String(ch, start, length) + " : " + incomingCommand);
if (ready > 0) {
handler.characters(ch, start, length);
} else if (incomingCommand) {
command = new String(ch, start, length);
incomingCommand = false;
} else if (incomingError) {
error = new String(ch, start, length);
incomingError = false;
} else if (incomingExtraData) {
extraDataFrame.fadd(lastqName, new String(ch, start, length));
incomingExtraData = false;
}
}
/*
* (non-Javadoc)
*
* @see org.xml.sax.helpers.DefaultHandler#startDocument()
*/
public void startDocument() throws SAXException {
this.ready = 0;
}
/*
* (non-Javadoc)
*
* @see org.xml.sax.helpers.DefaultHandler#endDocument()
*/
public void endDocument() throws SAXException {
}
/**
* Create a SAXException that wraps the given IOException.
* The wrapping is done using the standard Java 1.4 mechanism,
* so that getCause() will work. Note that new SAXException(Exception)
* does NOT work.
* @param e an IOException to wrap
* @return a SAX exception for which <code>getCause()</code> will return <code>e</code>.
*/
public SAXException wrapAsSAXException(IOException e) {
SAXException saxEx =new SAXException(e.getMessage());
saxEx.initCause(e);
return saxEx;
}
/**
* Converts a Throwable to an IOException. If <code>t</code> is an IOException,
* then <code>t</code> is returned. If not, then if <code>t</code> was caused
* by an IOException (directly or indirectly), then that IOException is returned.
* Otherwise, a new IOException is created which wraps (is caused by) <code>t</code>.
* @param t the throwable to convert
* @return an IOException which is either t, one of the causes of t, or a new IOException
* that wraps t.
*/
private IOException convertToIOException(Throwable t) {
//if t is itself an IOException, just return it
if (t instanceof IOException) {
return (IOException)t;
}
//search for a cause that is an IOException. If one is found, return that.
Throwable cause = t.getCause();
while (cause != null) {
if (cause instanceof IOException) {
return (IOException)cause;
}
cause = cause.getCause();
}
//otherwise, wrap t in a new IOException
IOException ioex = new IOException();
ioex.initCause(t);
return ioex;
}
}