/* $HeadURL:: $
* $Id$
*
* Copyright (c) 2009-2010 DuraSpace
* http://duraspace.org
*
* In collaboration with Topaz Inc.
* http://www.topazproject.org
*
* 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.akubraproject.rmi.server;
import java.io.IOException;
import java.io.InputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.akubraproject.BlobStoreConnection;
import org.akubraproject.rmi.remote.PartialBuffer;
import org.akubraproject.rmi.remote.RemoteBlob;
import org.akubraproject.rmi.remote.RemoteBlobCreator;
/**
* Server side implementation of RemoteBlobCreator.
*
* @author Pradeep Krishnan
*/
public class ServerBlobCreator extends UnicastExportable implements RemoteBlobCreator {
private static final Logger log = LoggerFactory.getLogger(ServerBlobCreator.class);
private static final long serialVersionUID = 1L;
private final ExecutorService readerService;
private final ExecutorService writerService;
private final Future<RemoteBlob> reader;
private final PipedOutputStream out;
/**
* Creates a new ServerBlobCreator object.
*
* @param con the server side blob store connection
* @param estimatedSize the size estimate on the new blob from client
* @param hints the blob creation hints from client
* @param exporter the exporter to use
*
* @throws IOException on an error in creation
*/
public ServerBlobCreator(final BlobStoreConnection con, final long estimatedSize,
final Map<String, String> hints, Exporter exporter)
throws IOException {
super(exporter);
out = new PipedOutputStream();
final InputStream in = new PipedInputStream(out);
readerService =
Executors.newSingleThreadExecutor(new ThreadFactory() {
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "akubra-rmi-blob-creator");
t.setDaemon(true);
return t;
}
});
reader =
readerService.submit(new Callable<RemoteBlob>() {
public RemoteBlob call() throws Exception {
if (log.isDebugEnabled())
log.debug("Started blob creator");
return new ServerBlob(con.getBlob(in, estimatedSize, hints), getExporter());
}
});
/*
* Note: there needs to be a single thread that writes to PipedOutputStream.
* See PipedOutputStream and PipedInputStream source. So all rmi calls for the
* stream are scheduled on this single-threaded execution service.
*/
writerService =
Executors.newSingleThreadExecutor(new ThreadFactory() {
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "akubra-rmi-blob-writer");
t.setDaemon(true);
return t;
}
});
if (log.isDebugEnabled())
log.debug("Server blob creator is ready");
}
private RemoteBlob getBlob() throws IOException {
try {
if (log.isDebugEnabled())
log.debug("Waiting for Server blob ...");
return reader.get();
} catch (InterruptedException e) {
throw new IOException("Interrupted while waiting for reader", e);
} catch (ExecutionException e) {
Throwable t = e.getCause();
if (t instanceof IOException)
throw (IOException) t;
if (t instanceof RuntimeException)
throw (RuntimeException) t;
throw new IOException("Unexpected exception in reader", t);
}
}
public RemoteBlob shutDown(boolean abort) throws IOException {
if (log.isDebugEnabled())
log.debug(abort ? "Aborting server blob creator" : "Shuting down server blob creator");
unExport(abort);
if (abort)
reader.cancel(abort);
RemoteBlob rb = abort ? null : getBlob();
readerService.shutdownNow();
writerService.shutdownNow();
if (abort) {
/*
* Wait for termination so that the 'cancel' above can do the
* necessary cleanup.
*/
try {
if (!readerService.awaitTermination(5, TimeUnit.SECONDS))
throw new IOException("Failed to terminate reader service");
} catch (InterruptedException e) {
throw new IOException("Interrupted while awaiting termination of reader", e);
}
}
return rb;
}
public void close() throws IOException {
execute(new Callable<Void>() {
public Void call() throws Exception {
out.close();
return null;
}
});
}
@Override
public void unreferenced() {
try {
shutDown(true);
} catch (IOException e) {
log.warn("Error during abort", e);
}
}
public void flush() throws IOException {
execute(new Callable<Void>() {
public Void call() throws Exception {
out.flush();
return null;
}
});
}
public void write(final byte[] b) throws IOException {
execute(new Callable<Void>() {
public Void call() throws Exception {
out.write(b);
return null;
}
});
}
public void write(final int b) throws IOException {
execute(new Callable<Void>() {
public Void call() throws Exception {
out.write(b);
return null;
}
});
}
public void write(final PartialBuffer b) throws IOException {
execute(new Callable<Void>() {
public Void call() throws Exception {
out.write(b.getBuffer(), b.getOffset(), b.getLength());
return null;
}
});
}
private <T> T execute(Callable<T> action) throws IOException {
try {
return writerService.submit(action).get();
} catch (InterruptedException e) {
throw new IOException("Interrupted while waiting for writer", e);
} catch (ExecutionException e) {
Throwable t = e.getCause();
if (t instanceof IOException)
throw (IOException) t;
if (t instanceof RuntimeException)
throw (RuntimeException) t;
throw new IOException("Unexpected exception in writer", t);
}
}
}