/*
* Copyright (C) 2005-2007 Alfresco Software Limited.
*
* 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/
package org.alfresco.repo.content;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.List;
import java.util.Locale;
import org.alfresco.error.StackTraceUtil;
import org.alfresco.i18n.I18NUtil;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.repository.ContentAccessor;
import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.ContentIOException;
import org.alfresco.service.cmr.repository.ContentStreamListener;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.AfterReturningAdvice;
/**
* Provides basic support for content accessors.
*
* @author Derek Hulley
*/
public abstract class AbstractContentAccessor implements ContentAccessor
{
private static Log logger = LogFactory.getLog(AbstractContentAccessor.class);
private static final Log loggerTrace = LogFactory.getLog(AbstractContentAccessor.class.getName() + ".trace");
static
{
if (loggerTrace.isDebugEnabled())
{
loggerTrace.warn("Trace channel assignment logging is on and will affect performance");
}
}
private StackTraceElement[] traceLoggerChannelAssignTrace;
/** when set, ensures that listeners are executed within a transaction */
private RetryingTransactionHelper transactionHelper;
private String contentUrl;
private String mimetype;
private String encoding;
private Locale locale;
/**
* @param contentUrl the content URL
*/
protected AbstractContentAccessor(String contentUrl)
{
if (contentUrl == null || contentUrl.length() == 0)
{
throw new IllegalArgumentException("contentUrl is invalid:" + contentUrl);
}
this.contentUrl = contentUrl;
// the default encoding is Java's default encoding
encoding = "UTF-8";
// the default locale
locale = I18NUtil.getLocale();
}
@Override
protected void finalize() throws Throwable
{
if (loggerTrace.isDebugEnabled() && traceLoggerChannelAssignTrace != null)
{
// check that the channel is closed if it was used
if (isChannelOpen())
{
StringBuilder sb = new StringBuilder(1024);
StackTraceUtil.buildStackTrace(
"Content IO Channel was opened but not closed: \n" + this,
traceLoggerChannelAssignTrace,
sb,
-1);
loggerTrace.error(sb);
}
}
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder(100);
sb.append("ContentAccessor")
.append("[ contentUrl=").append(getContentUrl())
.append(", mimetype=").append(getMimetype())
.append(", size=").append(getSize())
.append(", encoding=").append(getEncoding())
.append(", locale=").append(getLocale())
.append("]");
return sb.toString();
}
public ContentData getContentData()
{
ContentData property = new ContentData(contentUrl, mimetype, getSize(), encoding, locale);
return property;
}
public void setRetryingTransactionHelper(RetryingTransactionHelper helper)
{
this.transactionHelper = helper;
}
/**
* Derived classes can call this method to ensure that necessary trace logging is performed
* when the IO Channel is opened.
*/
protected final void channelOpened()
{
// trace debug
if (loggerTrace.isDebugEnabled())
{
Exception e = new Exception();
e.fillInStackTrace();
traceLoggerChannelAssignTrace = e.getStackTrace();
}
}
public String getContentUrl()
{
return contentUrl;
}
public String getMimetype()
{
return mimetype;
}
/**
* @param mimetype the underlying content's mimetype - null if unknown
*/
public void setMimetype(String mimetype)
{
this.mimetype = mimetype;
}
/**
* @return Returns the content encoding - null if unknown
*/
public String getEncoding()
{
return encoding;
}
/**
* @param encoding the underlying content's encoding - null if unknown
*/
public void setEncoding(String encoding)
{
this.encoding = encoding;
}
/**
* @return Returns the content locale or null if unkown
*/
public Locale getLocale()
{
return locale;
}
/**
* @param locale the content's locale, if known.
*/
public void setLocale(Locale locale)
{
this.locale = locale;
}
/**
* Generate a callback instance of the {@link FileChannel FileChannel}.
*
* @param directChannel the delegate that to perform the actual operations
* @param listeners the listeners to call
* @return Returns a new channel that functions just like the original, except
* that it issues callbacks to the listeners
* @throws ContentIOException
*/
protected FileChannel getCallbackFileChannel(
FileChannel directChannel,
List listeners)
throws ContentIOException
{
FileChannel ret = new CallbackFileChannel(directChannel, listeners);
// done
return ret;
}
/**
* Advise that listens for the completion of specific methods on the
* {@link java.nio.channels.ByteChannel} interface.
*
* @author Derek Hulley
*/
protected class ChannelCloseCallbackAdvise implements AfterReturningAdvice
{
private List listeners;
public ChannelCloseCallbackAdvise(List listeners)
{
this.listeners = listeners;
}
/**
* Provides transactional callbacks to the listeners
*/
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable
{
// check for specific events
if (method.getName().equals("close"))
{
fireChannelClosed();
}
}
private void fireChannelClosed()
{
if (listeners.size() == 0)
{
// nothing to do
return;
}
RetryingTransactionCallback