/** Copyright (C) 2012 Delcyon, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.delcyon.capo.resourcemanager.remote; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.Hashtable; import java.util.logging.Level; import org.w3c.dom.Document; import org.w3c.dom.Element; import com.delcyon.capo.CapoApplication; import com.delcyon.capo.CapoApplication.Location; import com.delcyon.capo.datastream.StreamUtil; import com.delcyon.capo.preferences.Preference; import com.delcyon.capo.preferences.PreferenceInfo; import com.delcyon.capo.preferences.PreferenceInfoHelper; import com.delcyon.capo.preferences.PreferenceProvider; import com.delcyon.capo.protocol.client.CapoConnection; import com.delcyon.capo.protocol.client.XMLServerResponse; import com.delcyon.capo.protocol.client.XMLServerResponseProcessor; import com.delcyon.capo.protocol.client.XMLServerResponseProcessorProvider; import com.delcyon.capo.resourcemanager.ResourceDescriptor; import com.delcyon.capo.resourcemanager.ResourceURI; import com.delcyon.capo.resourcemanager.remote.RemoteResourceDescriptorMessage.MessageType; import com.delcyon.capo.xml.XMLStreamProcessor; import com.delcyon.capo.xml.XPath; import com.delcyon.capo.xml.cdom.VariableContainer; /** * @author jeremiah * */ @PreferenceProvider(preferences=RemoteResourceResponseProcessor.Preferences.class) @XMLServerResponseProcessorProvider(documentElementNames={"RemoteResourceResponse"}, namespaceURIs={}) public class RemoteResourceResponseProcessor implements XMLServerResponseProcessor, VariableContainer { public enum Preferences implements Preference { @PreferenceInfo(arguments={"sec"}, defaultValue="30", description="The number of seconds to wait before timing out a remote output stream", longOption="OUTPUT_STREAM_TIMEOUT", option="OUTPUT_STREAM_TIMEOUT") OUTPUT_STREAM_TIMEOUT; @Override public String[] getArguments() { return PreferenceInfoHelper.getInfo(this).arguments(); } @Override public String getDefaultValue() { return PreferenceInfoHelper.getInfo(this).defaultValue(); } @Override public String getDescription() { return PreferenceInfoHelper.getInfo(this).description(); } @Override public String getLongOption() { return PreferenceInfoHelper.getInfo(this).longOption(); } @Override public String getOption() { return PreferenceInfoHelper.getInfo(this).option(); } @Override public Location getLocation() { return PreferenceInfoHelper.getInfo(this).location(); } } @SuppressWarnings("unchecked") public synchronized static Hashtable<String, ThreadedInputStreamReader> getThreadedInputStreamReaderHashtable() { Hashtable<String, ThreadedInputStreamReader> threadedInputStreamReaderHashtable = (Hashtable<String, ThreadedInputStreamReader>) CapoApplication.getGlobalObject("threadedInputStreamReaderHashtable"); if (threadedInputStreamReaderHashtable == null) { threadedInputStreamReaderHashtable = new Hashtable<String, ThreadedInputStreamReader>(); CapoApplication.setGlobalObject("threadedInputStreamReaderHashtable",threadedInputStreamReaderHashtable); } return threadedInputStreamReaderHashtable; } @SuppressWarnings("unchecked") private synchronized static Hashtable<String, CapoConnection> getCapoVarConnectionHashtable() { Hashtable<String, CapoConnection> capoVarConnectionHashtable = (Hashtable<String, CapoConnection>) CapoApplication.getGlobalObject("capoVarConnectionHashtable"); if (capoVarConnectionHashtable == null) { capoVarConnectionHashtable = new Hashtable<String, CapoConnection>(); CapoApplication.setGlobalObject("capoVarConnectionHashtable",capoVarConnectionHashtable); } return capoVarConnectionHashtable; } @SuppressWarnings("unchecked") private synchronized static Hashtable<String, XMLStreamProcessor> getCapoVarXMLStreamProcessorHashtable() { Hashtable<String, XMLStreamProcessor> capoVarXMLStreamProcessorHashtable = (Hashtable<String, XMLStreamProcessor>) CapoApplication.getGlobalObject("capoVarXMLStreamProcessorHashtable"); if (capoVarXMLStreamProcessorHashtable == null) { capoVarXMLStreamProcessorHashtable = new Hashtable<String, XMLStreamProcessor>(); CapoApplication.setGlobalObject("capoVarXMLStreamProcessorHashtable",capoVarXMLStreamProcessorHashtable); } return capoVarXMLStreamProcessorHashtable; } @SuppressWarnings("unchecked") private synchronized static Hashtable<String, ResourceDescriptor> getResourceDescriptorHashtable() { Hashtable<String, ResourceDescriptor> resourceDescriptorHashtable = (Hashtable<String, ResourceDescriptor>) CapoApplication.getGlobalObject("resourceDescriptorHashtable"); if (resourceDescriptorHashtable == null) { resourceDescriptorHashtable = new Hashtable<String, ResourceDescriptor>(); CapoApplication.setGlobalObject("resourceDescriptorHashtable",resourceDescriptorHashtable); } return resourceDescriptorHashtable; } private Document responseDocument = null; private XMLServerResponse xmlServerResponse; private String sessionID; @SuppressWarnings("unused") private HashMap<String, String> sessionHashMap = null; @Override public boolean isStreamProcessor() { return false; } @Override public Document getResponseDocument() { return responseDocument; } @Override public void init(Document responseDocument, XMLServerResponse xmlServerResponse,HashMap<String, String> sessionHashMap) throws Exception { this.responseDocument = responseDocument; this.xmlServerResponse = xmlServerResponse; this.sessionHashMap = sessionHashMap; } @Override public void process() throws Exception { RemoteResourceDescriptorMessage message = new RemoteResourceDescriptorMessage(XPath.unwrapDocument(responseDocument,false)); RemoteResourceDescriptorMessage reply = new RemoteResourceDescriptorMessage(); reply.setMessageType(MessageType.SUCCESS); sessionID = message.getSessionID(); ResourceDescriptor resourceDescriptor = getResourceDescriptorHashtable().get(sessionID); try { CapoConnection capoConnection = null; RemoteResourceRequest request = null; ThreadedInputStreamReader threadedInputSreamReader = null; CapoApplication.logger.log(Level.FINER, sessionID+":"+message.getMessageType()+"==>"+message.getResourceURI().getResourceURIString()); switch (message.getMessageType()) { case SETUP: resourceDescriptor = CapoApplication.getDataManager().getResourceDescriptor(null, message.getResourceURI().getResourceURIString()); reply.setResourceURI(resourceDescriptor.getResourceURI()); reply.setResourceType(resourceDescriptor.getResourceType()); getResourceDescriptorHashtable().put(sessionID, resourceDescriptor); break; case INIT: resourceDescriptor.init(null, this, message.getLifeCycle(), message.isIterate(), message.getResourceParameters()); break; case GET_RESOURCE_STATE: reply.setResourceState(resourceDescriptor.getResourceState()); break; case OPEN: resourceDescriptor.open(this, message.getResourceParameters()); break; case CLOSE: waitforOutputStreamToFinish(sessionID); resourceDescriptor.close(this, message.getResourceParameters()); break; case RELEASE: waitforOutputStreamToFinish(sessionID); resourceDescriptor.release(this, message.getResourceParameters()); //closeVarConnection(); getResourceDescriptorHashtable().remove(sessionID); break; case RESET: resourceDescriptor.reset(message.getPreviousState()); break; case ADVANCE_STATE: resourceDescriptor.advanceState(message.getDesiredState(), this, message.getResourceParameters()); break; case GET_RESOURCE_METADATA: reply.setResourceMetaData(resourceDescriptor.getResourceMetaData(this, message.getResourceParameters())); break; case GET_CONTENT_METADATA: reply.setContentMetaData(resourceDescriptor.getContentMetaData(this, message.getResourceParameters())); break; case GET_OUTPUT_METADATA: waitforOutputStreamToFinish(sessionID); reply.setOutputMetaData(resourceDescriptor.getOutputMetaData(this, message.getResourceParameters())); break; case GET_INPUTSTREAM: capoConnection = new CapoConnection(); request = new RemoteResourceRequest(capoConnection.getOutputStream(),capoConnection.getInputStream()); request.setType(MessageType.GET_INPUTSTREAM); request.setSessionId(sessionID); RemoteResourceDescriptorMessage inputStreamMessage = new RemoteResourceDescriptorMessage(); inputStreamMessage.setMessageType(MessageType.GET_INPUTSTREAM); inputStreamMessage.prepareResponse(); request.appendElement(inputStreamMessage.getResponseDocument().getDocumentElement()); request.send(); threadedInputSreamReader = new ThreadedInputStreamReader(sessionID,resourceDescriptor.getInputStream(this, message.getResourceParameters()), capoConnection.getOutputStream()); threadedInputSreamReader.start(); break; case GET_OUTPUTSTREAM: capoConnection = new CapoConnection(); if(CapoApplication.logger.isLoggable(Level.FINE)) { capoConnection.dumpOnClose(true); } request = new RemoteResourceRequest(capoConnection.getOutputStream(),capoConnection.getInputStream()); request.setType(MessageType.GET_OUTPUTSTREAM); request.setSessionId(sessionID); RemoteResourceDescriptorMessage outputStreamMessage = new RemoteResourceDescriptorMessage(); outputStreamMessage.setMessageType(MessageType.GET_OUTPUTSTREAM); outputStreamMessage.prepareResponse(); request.appendElement(outputStreamMessage.getResponseDocument().getDocumentElement()); request.send(); threadedInputSreamReader = new ThreadedInputStreamReader(sessionID,capoConnection.getInputStream(),resourceDescriptor.getOutputStream(this, message.getResourceParameters())); threadedInputSreamReader.setCapoConnection(capoConnection); if (getThreadedInputStreamReaderHashtable().contains(sessionID)) { throw new Exception("Stream Reader already exists! Somebody didn't close something!"); } getThreadedInputStreamReaderHashtable().put(sessionID, threadedInputSreamReader); threadedInputSreamReader.start(); break; case ADD_RESOURCE_PARAMETERS: resourceDescriptor.addResourceParameters(this, message.getResourceParameters()); break; case IS_STREAM_SUPPORETED_FORMAT: reply.setStreamSupportedFormat(resourceDescriptor.isSupportedStreamFormat(message.getStreamType(), message.getStreamFormat())); break; case STEP: reply.setStepSuccess(resourceDescriptor.next(this, message.getResourceParameters())); break; case READ_XML: reply.setXMLElement(resourceDescriptor.readXML(this, message.getResourceParameters())); break; case WRITE_XML: resourceDescriptor.writeXML(this, message.getXMLElement(), message.getResourceParameters()); break; case READ_BLOCK: reply.setBlock(resourceDescriptor.readBlock(this, message.getResourceParameters())); break; case WRITE_BLOCK: resourceDescriptor.writeBlock(this, message.getBlock(), message.getResourceParameters()); break; case PROCESS_INPUT: resourceDescriptor.processInput(this, message.getResourceParameters()); break; case PROCESS_OUTPUT: resourceDescriptor.processOutput(this, message.getResourceParameters()); break; case PERFORM_ACTION: reply.setActionResult(resourceDescriptor.performAction(this, message.getAction(), message.getResourceParameters())); break; case IS_SUPPORTED_ACTION: reply.setActionResult(resourceDescriptor.isSupportedAction(message.getAction())); break; case GET_LIFCYCLE: reply.setLifeCycle(resourceDescriptor.getLifeCycle()); break; case GET_STREAM_STATE: reply.setStreamState(resourceDescriptor.getStreamState(message.getStreamType())); break; case GET_SUPPORTED_STREAM_FORMATS: reply.setSupportedStreamFormats(resourceDescriptor.getSupportedStreamFormats(message.getStreamType())); break; case GET_SUPPORTED_STREAM_TYPES: reply.setSupportedStreamTypes(resourceDescriptor.getSupportedStreamTypes()); break; case IS_SUPPORTED_STREAM_TYPE: reply.setSupportedStreamType(resourceDescriptor.isSupportedStreamType(message.getStreamType())); break; default: throw new UnsupportedOperationException(message.getMessageType().toString()); } } catch (Exception exception) { exception.printStackTrace(); reply.setMessageType(MessageType.FAILURE); reply.setException(exception); } reply.prepareResponse(); //XPath.dumpNode(reply.getResponseDocument(), System.out); xmlServerResponse.writeDocument(reply.getResponseDocument()); } private void waitforOutputStreamToFinish(String sessionID) throws Exception { ThreadedInputStreamReader threadedInputStreamReader = getThreadedInputStreamReaderHashtable().get(sessionID); if (threadedInputStreamReader != null) { CapoApplication.logger.log(Level.FINE, "Waiting for OutputStream timeout"); int loopCount = 0; int waitTime = 100; int timeoutSeconds = CapoApplication.getConfiguration().getIntValue(Preferences.OUTPUT_STREAM_TIMEOUT); while(threadedInputStreamReader.isFinished() == false) { loopCount++; Thread.sleep(waitTime); if (loopCount * waitTime > timeoutSeconds * 1000) { CapoApplication.logger.log(Level.WARNING, "OutputStream timeout"); break; } } CapoApplication.logger.log(Level.FINE, "Done Waiting for OutputStream waited("+(waitTime*loopCount)+"ms)"); getThreadedInputStreamReaderHashtable().remove(sessionID); } } public class ThreadedInputStreamReader extends Thread { private InputStream inputStream; private OutputStream outputStream; private String sessionID; private boolean finished = false; @SuppressWarnings("unused") private CapoConnection capoConnection; public ThreadedInputStreamReader(String sessionID,InputStream inputStream, OutputStream outputStream) { super("ThreadedInputStreamReader SID:"+sessionID); this.inputStream = inputStream; this.outputStream = outputStream; this.sessionID = sessionID; } /** * This is here to keep connection from being garbage collected while still active. * @param capoConnection */ public void setCapoConnection(CapoConnection capoConnection) { this.capoConnection = capoConnection; } public boolean isFinished() { return finished; } @Override public void run() { try { CapoApplication.logger.log(Level.INFO, "Preparing bytes to remote resource descriptor"); long read = StreamUtil.readInputStreamIntoOutputStream(inputStream, outputStream); close(); CapoApplication.logger.log(Level.INFO, "Processed "+read+" bytes with remote resource descriptor"); } catch (Exception exception) { ResourceDescriptor currentResourceDescriptor = getResourceDescriptorHashtable().get(sessionID); ResourceURI uri = null; if (currentResourceDescriptor != null) { uri = currentResourceDescriptor.getResourceURI(); } CapoApplication.logger.log(Level.SEVERE, "Error sending bytes to remote resource descriptor: "+uri+" SID:"+sessionID,exception); } } public void close() throws Exception { outputStream.flush(); outputStream.close(); finished = true; if(this.capoConnection != null) { this.capoConnection.close(); this.capoConnection = null; } } } @Override public String getVarValue(String varName) throws Exception { XMLStreamProcessor xmlStreamProcessor = xmlServerResponse.getXmlStreamProcessor(); RemoteResourceDescriptorMessage request = new RemoteResourceDescriptorMessage(); request.setMessageType(MessageType.GET_VAR_VALUE); request.setSessionID(sessionID); request.setVarName(varName); request.prepareResponse(); RemoteResourceRequest resourceRequest = new RemoteResourceRequest(xmlStreamProcessor); resourceRequest.setType(MessageType.GET_VAR_VALUE); resourceRequest.setSessionId(sessionID); resourceRequest.appendElement((Element) request.getResponseDocument().getDocumentElement().getElementsByTagName("*").item(0)); resourceRequest.send(); RemoteResourceDescriptorMessage message = new RemoteResourceDescriptorMessage(XPath.unwrapDocument(resourceRequest.readResponse(), true)); return message.getValue(); } }