/*******************************************************************************
* Copyright (c) 2009 MATERNA Information & Communications. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html. For further
* project-related information visit http://www.ws4d.org. The most recent
* version of the JMEDS framework can be obtained from
* http://sourceforge.net/projects/ws4d-javame.
******************************************************************************/
package org.ws4d.java.attachment;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.ws4d.java.DPWSFramework;
import org.ws4d.java.communication.ContextID;
import org.ws4d.java.configuration.AttachmentProperties;
import org.ws4d.java.io.fs.FileSystem;
import org.ws4d.java.structures.HashMap;
import org.ws4d.java.structures.Iterator;
import org.ws4d.java.types.InternetMediaType;
import org.ws4d.java.util.Log;
import org.ws4d.java.util.ObjectPool;
import org.ws4d.java.util.ObjectPool.InstanceCreator;
/**
*
*/
public class DefaultAttachmentStore extends AttachmentStore {
private static final AttachmentProperties PROPS = AttachmentProperties.getInstance();
private static final ObjectPool BUFFERS = new ObjectPool(new InstanceCreator() {
/*
* (non-Javadoc
* )
* @see org
* .ws4d . java.
* util.
* ObjectPool .
* InstanceCreator
* #
* createInstance
* ()
*/
public Object createInstance() {
return new byte[PROPS.getReadBufferSize()];
}
}, 1);
// key = StoreKey, value = DefaultAttachment instance
private final HashMap attachments = new HashMap();
// key = StoreKey, value = the same StoreKey
private final HashMap lockKeys = new HashMap();
private final FileSystem fs;
/**
* Returns the number of bytes read in. The stream <code>from</code> is
* always completely read out unless a <code>java.io.IOException</code>
* occurs.
*
* @param from the stream to read from
* @param out the stream in which to write everything to
* @return the number of bytes read
* @throws IOException if reading from <code>from</code> or writing to
* <code>out</code> failed for any reason
*/
static int readOut(InputStream from, OutputStream out) throws IOException {
try {
return readOut(from, -1, out);
} catch (AttachmentException e) {
/*
* shouldn't ever occur, as we don't impose a limit on the amount of
* bytes to read
*/
throw new IOException(e.getMessage());
}
}
/**
* Returns the number of bytes read in. The stream <code>from</code> is
* always completely read out unless a <code>java.io.IOException</code>
* occurs.
*
* @param from the stream to read from
* @param out the stream in which to write everything to
* @param buffy the buffer to use when copying bytes from <code>from</code>
* to <code>out</code>
* @return the number of bytes read
* @throws IOException if reading from <code>from</code> or writing to
* <code>out</code> failed for any reason
*/
static int readOut(InputStream from, OutputStream out, byte[] buffy) throws IOException {
try {
return readOut(from, -1, out, buffy);
} catch (AttachmentException e) {
/*
* shouldn't ever occur, as we don't impose a limit on the amount of
* bytes to read
*/
throw new IOException(e.getMessage());
}
}
/**
* Returns the number of bytes read in. The stream <code>from</code> is
* always completely read out unless a <code>java.io.IOException</code>
* occurs. That is, even if this method throws an
* {@link AttachmentException} because of a violation to the maximum
* acceptable byte count, it still reads out everything from
* <code>from</code>.
*
* @param from the stream to read from
* @param maxSizeToAccept the maximum size in bytes to accept
* @param out the stream in which to write everything to
* @return the number of bytes read
* @throws AttachmentException if <code>from</code> contained more bytes
* than specified by <code>maxSizeToAccept</code>
* @throws IOException if reading from <code>from</code> or writing to
* <code>out</code> failed for any reason
*/
static int readOut(InputStream from, int maxSizeToAccept, OutputStream out) throws AttachmentException, IOException {
return readOut(from, maxSizeToAccept, out, (byte[]) BUFFERS.acquire());
}
/**
* Returns the number of bytes read in. The stream <code>from</code> is
* always completely read out unless a <code>java.io.IOException</code>
* occurs. That is, even if this method throws an
* {@link AttachmentException} because of violation to the maximum
* acceptable bytes count, it will still have read out everything from
* <code>from</code>.
*
* @param from the stream to read from
* @param maxSizeToAccept the maximum size in bytes to accept
* @param out the stream in which to write everything to
* @param buffy the buffer to use when copying bytes from <code>from</code>
* to <code>out</code>
* @return the number of bytes read
* @throws AttachmentException if <code>from</code> contained more bytes
* than specified by <code>maxSizeToAccept</code>
* @throws IOException if reading from <code>from</code> or writing to
* <code>out</code> failed for any reason
*/
private static int readOut(InputStream from, int maxSizeToAccept, OutputStream out, byte[] buffy) throws AttachmentException, IOException {
try {
int size = 0;
int j = from.read(buffy);
AttachmentException toThrow = null;
while (j > 0) {
size += j;
if (maxSizeToAccept > 0 && size > maxSizeToAccept && toThrow == null) {
toThrow = new AttachmentException("Attachment size exceeds maximum allowed limit (" + maxSizeToAccept + ")");
}
if (toThrow == null) {
out.write(buffy, 0, j);
// especially for attachment support
// out.flush();
}
j = from.read(buffy);
}
out.flush();
if (toThrow == null) {
return size;
}
throw toThrow;
} finally {
BUFFERS.release(buffy);
}
}
/**
*
*/
public DefaultAttachmentStore() {
super();
FileSystem fs = null;
try {
fs = DPWSFramework.getLocalFileSystem();
fs.deleteFile(PROPS.getStorePath());
} catch (IOException e) {
/*
* no file system available within current runtime or framework not
* started
*/
Log.error("No local file system available, attachment store policy POLICY_EXT_STORAGE will not work.");
}
this.fs = fs;
}
/*
* (non-Javadoc)
* @see
* org.ws4d.java.attachment.AttachmentStore#store(org.ws4d.java.communication
* .MIMEContextID, java.lang.String, java.lang.String, java.lang.String,
* java.io.InputStream)
*/
public void store(ContextID context, String cid, String contentType, String transferEncoding, InputStream from) {
/*
* there are FOUR feasible possibilities for obtaining an attachment's
* raw data:
*/
/*
* 1) store attachment within memory (byte buffer) and allow access to
* it via Attachment.getBytes() only
*/
/*
* 2) store attachment within memory (byte buffer) and allow access to
* it via Attachment.getBytes() AND Attachment.getInputStream(), which
* essentially wraps the byte array within a ByteArrayInputStream
*/
/*
* 3) store attachment within external storage (e.g. file system) and
* allow access to it via Attachment.getInputStream() only
*/
/*
* 4) store attachment within external storage (e.g. file system) and
* allow access to it via Attachment.getInputStream() AND
* Attachment.getBytes(), which reads out the entire stream and stores
* it within a byte array
*/
AbstractAttachment attachment;
int storePolicy = getStorePolicy();
if (storePolicy == POLICY_EXT_STORAGE && fs == null) {
storePolicy = POLICY_MEM_BUFFER;
Log.warn("No platform support available for requested store policy POLICY_EXT_STORAGE, reverting to POLICY_MEM_BUFFER");
}
InternetMediaType mimeType = new InternetMediaType(contentType);
if (isStreamingMediaType(mimeType)) {
attachment = new InputStreamAttachment(from);
} else if (storePolicy == POLICY_EXT_STORAGE) {
// store content of 'from' within file system repository
// we make cid unique within FS store by means of a timestamp
String filePath = PROPS.getStorePath() + fs.fileSeparator() + System.currentTimeMillis() + "_" + fs.escapeFileName(context.getInstanceId() + ":" + context.getMessageNumber() + ":" + cid);
try {
OutputStream out = fs.writeFile(filePath);
readOut(from, PROPS.getMaxAttachmentSize(), out);
out.flush();
out.close();
attachment = new FileAttachment(filePath, false);
} catch (AttachmentException e) {
fs.deleteFile(filePath);
attachment = new MemoryAttachment(e);
} catch (IOException e) {
AttachmentException ae = new AttachmentException("Reading from stream or writing into attachment store failed: " + e);
Log.error(ae.toString());
attachment = new MemoryAttachment(ae);
}
} else {
// POLICY_MEM_BUFFER is the default one
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
readOut(from, PROPS.getMaxMemBufferSize(), out);
out.close();
attachment = new MemoryAttachment(out.toByteArray());
} catch (AttachmentException e) {
attachment = new MemoryAttachment(e);
} catch (IOException e) {
AttachmentException ae = new AttachmentException("Reading from stream failed: " + e);
Log.error(ae.toString());
attachment = new MemoryAttachment(ae);
}
}
attachment.setContentId(cid);
attachment.setContentType(mimeType);
attachment.setTransferEncoding(transferEncoding);
StoreKey key = new StoreKey(context, cid);
StoreKey lockKey = null;
synchronized (attachments) {
attachments.put(key, attachment);
lockKey = (StoreKey)lockKeys.remove(key);
}
if (lockKey != null) {
lockKey.notifyWaiters();
}
}
/*
* (non-Javadoc)
* @see org.ws4d.java.attachment.AttachmentStore#isAvailable(org.ws4d.java.
* communication.MIMEContextID, java.lang.String)
*/
public boolean isAvailable(ContextID context, String cid) {
StoreKey key = new StoreKey(context, cid);
synchronized (attachments) {
return attachments.containsKey(key);
}
}
/*
* (non-Javadoc)
* @see
* org.ws4d.java.attachment.AttachmentStore#resolve(org.ws4d.java.communication
* .MIMEContextID, java.lang.String)
*/
public IncomingAttachment resolve(ContextID context, String cid) throws AttachmentException {
IncomingAttachment attachment;
StoreKey key = new StoreKey(context, cid);
StoreKey lockKey;
synchronized (attachments) {
attachment = (IncomingAttachment) attachments.get(key);
if (attachment == null) {
lockKey = (StoreKey)lockKeys.get(key);
if (lockKey == null) {
lockKey = key;
lockKeys.put(lockKey, lockKey);
}
} else {
return attachment;
}
}
synchronized (lockKey) {
while (lockKey.waiting) {
try {
lockKey.wait();
} catch (InterruptedException e) {
// void
}
}
}
synchronized (attachments) {
attachment = (IncomingAttachment) attachments.get(key);
}
if (attachment == null) {
throw new AttachmentException("Attachment not found for " + context + " and content ID " + cid);
}
return attachment;
}
/*
* (non-Javadoc)
* @see org.ws4d.java.attachment.AttachmentStore#cleanup()
*/
public void cleanup() {
if (fs != null) {
fs.deleteFile(PROPS.getStorePath());
synchronized (attachments) {
attachments.clear();
for (Iterator iter = lockKeys.values().iterator(); iter.hasNext();) {
((StoreKey)iter.next()).notifyWaiters();
}
lockKeys.clear();
}
}
}
private static class StoreKey {
final ContextID context;
final String cid;
int hashCode;
public volatile boolean waiting = true;
/**
* @param context
* @param cid
*/
StoreKey(ContextID context, String cid) {
this.context = context;
this.cid = cid;
final int prime = 31;
hashCode = prime + cid.hashCode();
hashCode = prime * hashCode + context.hashCode();
}
public int hashCode() {
return hashCode;
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
StoreKey other = (StoreKey) obj;
if (!cid.equals(other.cid)) {
return false;
}
if (!context.equals(other.context)) {
return false;
}
return true;
}
public synchronized void notifyWaiters() {
waiting = false;
notifyAll();
}
}
}