/* * 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.IOException; import java.io.InputStream; import java.net.URI; import java.net.URL; import java.rmi.NoSuchObjectException; import java.util.zip.GZIPInputStream; import java.util.zip.ZipInputStream; 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.RemoteInputStream; import edu.emory.mathcs.util.remote.io.server.impl.RemoteInputStreamSrvImpl; /** * Represents a command to move data into a graph (Load) or server (Restore). * @param SourceType The type of object that provides the input for the command. * * @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 DataInputTx extends DataTx { /** String constant for the extension of gzip files. */ private static final String GZIP_EXTENSION = ".gz"; /** String constant for the extension of zip files. */ private static final String ZIP_EXTENSION = ".zip"; /** A stream to enable an API to load or restore data directly. */ protected InputStream overrideInputStream = null; /** * Create a new data transfer command for moving data into a graph or server. * If local is <code>true</code> then source may be null, but an overriding input * stream must be set before executing the operation. * @param source The source of data to insert. * @param destination The graph or server to load data into. * @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 source will be a file or stream on the * local system that is marshalled to the remote server. If <code>false</code>, * it will be a file on the remote server filesystem. */ public DataInputTx(URI source, URI destination, URI serverGraphURI, boolean local) { super(source, destination, serverGraphURI, local); if (!local && source == null) throw new IllegalArgumentException("Need a valid remote source"); } /** * Allows an API to set an input stream for loading or restoring, instead of * getting it from the source URI. * @param overrideStream The stream to use as the data source. */ public void setOverrideInputStream(InputStream overrideStream) { this.overrideInputStream = overrideStream; } /** * Perform the input transfer with the configured datastream. * @return The number of statements affected, or <code>null</code> if this is not relevant. */ protected Long doTx(Connection conn, InputStream inputStream) throws QueryException { return conn.execute(getExecutable(inputStream)); } /** * Perform the input transfer with the configured source. */ protected Long doTx(Connection conn, URI src) throws QueryException { return conn.execute(getExecutable(src)); } /** * Get the operation that will transfer from the given source stream. */ protected abstract SessionOp<Long,QueryException> getExecutable(InputStream inputStream); /** * Get the operation that will transfer from the given source object. */ protected abstract SessionOp<Long,QueryException> getExecutable(URI src); /** * Wrap the local source data (input stream or file URI) in an RMI object for marshalling, * and send over the connection. Used by Load and Restore. Delegates to the {@link #doTx(Connection, InputStream)} * abstract method to send the data over the connection. * @param conn The connection to the server. * @param compressable If <code>true</code> and the source is a file URI, file decompression will be * applied to the contents of the source file before sending over the connection. * @return The number of statements inserted. * @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 long sendMarshalledData(Connection conn, boolean compressable) throws QueryException, IOException { if (logger.isInfoEnabled()) logger.info("Sending local resource : " + getSource()); InputStream inputStream = getLocalInputStream(compressable); // If the connection is local, then no need to wrap if (!conn.isRemote()) { try { return doTx(conn, inputStream); } finally { inputStream.close(); } } RemoteInputStreamSrvImpl srv = null; RemoteInputStream remoteInputStream = null; try { // open and wrap the inputstream srv = new RemoteInputStreamSrvImpl(inputStream); Rmi.export(srv); remoteInputStream = new RemoteInputStream(srv); // call back to the implementing class return doTx(conn, remoteInputStream); } finally { // clean up the RMI object if (srv != null) { try { Rmi.unexportObject(srv, false); } catch (NoSuchObjectException ex) { /* nothing to clean up, so continue */ }; } try { if (remoteInputStream != null) remoteInputStream.close(); } catch (Exception e) { logger.warn("Unable to cleanly close remote data stream", e); } } } /** * Gets the local input stream for a Load or Restore operation. If an input stream has been * specified by {@link #setOverrideInputStream(InputStream)} then it will be returned; otherwise, * an input stream will be opened for the source file URI. * @param compressable If <code>true</code> and the source is a file URI, file decompression will be * applied to its contents before returning. * @return A stream for the local data source. * @throws QueryException If no valid data source was set. * @throws IOException If an error occurred opening the local source. */ protected InputStream getLocalInputStream(boolean compressable) throws QueryException, IOException { // Use provided input stream if there is one. InputStream stream = overrideInputStream; if (stream == null) { // No input stream was provided, need to open from local source URI. URI sourceUri = getSource(); if (sourceUri == null) { throw new QueryException("Attempt to execute data operation without a valid local source"); } // is the file/stream compressed? URL sourceUrl = sourceUri.toURL(); if (compressable) { stream = adjustForCompression(sourceUrl); } else { stream = sourceUrl.openStream(); } } return stream; } /** * Gets a stream for a file URL. Determines if the stream is compressed by inspecting * the fileName extension. * * @param fileLocation String The URL for the file being loaded * @throws IOException An error while reading from the input stream. * @return InputStream A new input stream which supplies uncompressed data. */ private InputStream adjustForCompression(URL fileLocation) throws IOException { if (fileLocation == null) throw new IllegalArgumentException("File name is null"); InputStream stream = fileLocation.openStream(); // wrap the stream in a decompressor if the suffixes indicate this should happen. String fileName = fileLocation.toString(); if (fileName.toLowerCase().endsWith(GZIP_EXTENSION)) { stream = new GZIPInputStream(stream); } else if (fileName.toLowerCase().endsWith(ZIP_EXTENSION)) { stream = new ZipInputStream(stream); } assert stream != null; return stream; } }