/* * Copyright 2009 Revelytix. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.mulgara.query.operation; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.URI; import java.rmi.NoSuchObjectException; import org.mulgara.connection.Connection; import org.mulgara.connection.Connection.SessionOp; import org.mulgara.query.QueryException; import org.mulgara.util.Rmi; import edu.emory.mathcs.util.remote.io.RemoteOutputStream; import edu.emory.mathcs.util.remote.io.server.impl.RemoteOutputStreamSrvImpl; /** * Represents a command to move data out of a graph (Export) or server (Backup). * * @created Jun 27, 2008 * @author Alex Hall * @copyright © 2008 <a href="http://www.revelytix.com">Revelytix, Inc.</a> * @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a> */ public abstract class DataOutputTx extends DataTx { /** String constant for the file URL protocol. */ protected static final String FILE_SCHEME = "file"; /** A stream to enable an API to export or backup data directly. */ private OutputStream overrideOutputStream = null; /** * Create a new data transfer command for moving data otu of a graph or server. * If local is <code>true</code> then destination may be null, but an overriding output * stream must be set before executing the operation. * @param source The graph or server to get data from. * @param destination The destination of the graph or server content. * @param serverGraphURI The URI of the server or graph being operated on. This * parameter is primarily for use by the TqlAutoInterpreter for discovering * server URI's of commands, and may be omitted if working directly with an * existing {@link Connection}. * @param local If <code>true</code>, the destination will be a file or stream on the * local system that is marshalled from the remote server. If <code>false</code>, * it will be a file on the remote server filesystem. */ public DataOutputTx(URI source, URI destination, URI serverGraphURI, boolean local) { super(source, destination, serverGraphURI, local); if (!local && destination == null) throw new IllegalArgumentException("Need a valid remote destination"); if (destination != null && !destination.getScheme().equals(FILE_SCHEME)) { throw new IllegalArgumentException("Output must be sent to a file"); } } /** * Allows an API to set an output stream for exporting or backing up, instead of * getting it from the destination URI. * @param overrideStream The stream to use as the destination. */ public void setOverrideOutputStream(OutputStream overrideStream) { this.overrideOutputStream = overrideStream; } /** * Perform the output transfer with the configured datastream. */ protected void doTx(Connection conn, OutputStream outputStream) throws QueryException { conn.execute(getOp(outputStream)); } /** * Perform the output transfer to the configured destination URI. */ protected void doTx(Connection conn, URI destUri) throws QueryException { conn.execute(getOp(destUri)); } /** * Get the operation that will be trasnfer to the output stream. */ protected abstract SessionOp<Object,QueryException> getOp(final OutputStream outputStream); /** * Get the operation that will be transfer to the destination URI. */ protected abstract SessionOp<Object,QueryException> getOp(final URI destUri); /** * Wrap the local destination object data (output stream or file URI) in an RMI object for marshalling, * and receive over the connection. Delegates to the {@link #doTx(Connection, OutputStream)} * abstract method to send the data over the connection. * @param conn The connection to the server. * @throws QueryException There was an error working with data at the server end. * @throws IOException There was an error transferring data over the network. */ protected void getMarshalledData(Connection conn) throws QueryException, IOException { if (logger.isInfoEnabled()) logger.info("Receiving local resource : " + getDestination()); RemoteOutputStreamSrvImpl srv = null; RemoteOutputStream remoteOutputStream = null; try { OutputStream outputStream = getLocalOutputStream(); // open and wrap the output stream srv = new RemoteOutputStreamSrvImpl(outputStream); Rmi.export(srv); remoteOutputStream = new RemoteOutputStream(srv); // call back to the implementing class doTx(conn, remoteOutputStream); } finally { // cleanup the output if (remoteOutputStream != null) { try { remoteOutputStream.close(); } catch (IOException ioe ) { logger.warn("Unable to cleanly close remote data stream", ioe); } } // cleanup the RMI for the output stream if (srv != null) { try { Rmi.unexportObject(srv, false); } catch (NoSuchObjectException ex) { /* nothing to clean up, so continue */ }; try { srv.close(); } catch (IOException e) { logger.warn("Unable to cleanly close data stream to remote object", e); } } } } /** * Gets the local output stream for an Export or Backup operation. If an output stream has been * specified by {@link #setOverrideOutputStream(OutputStream)} then it will be returned; otherwise, * an output stream will be opened for the destination file URI. * @return A stream for the local data destination. * @throws QueryException If no valid data destination was set. * @throws IOException If an error occurred opening the local destination. */ private OutputStream getLocalOutputStream() throws QueryException, IOException { // Use provided output stream if there is one. OutputStream stream = overrideOutputStream; if (stream == null) { // No output stream was provided, need to open from local destination URI. URI destUri = getDestination(); if (destUri == null) { throw new QueryException("Attempt to execute data operation without a valid local destination"); } String destinationFile = this.getDestination().toURL().getPath(); try { stream = new FileOutputStream(destinationFile); } catch (FileNotFoundException ex) { throw new QueryException("File " + destinationFile + " cannot be created for output.", ex); } } return stream; } }