mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-06-02 17:35:18 +00:00
executeInUserTransaction(). Changed the on close callback for write listeners to use a RetryingTransaction. The point of this exercise is to make it possible for clients of the core server to ignore transient resource contention failures. CIFS, for example, will be able to take advantage of this, since a transient error condition currently results in a dead share. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@4597 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
501 lines
16 KiB
Java
501 lines
16 KiB
Java
/*
|
|
* Copyright (C) 2005 Alfresco, Inc.
|
|
*
|
|
* Licensed under the Mozilla Public License version 1.1
|
|
* with a permitted attribution clause. You may obtain a
|
|
* copy of the License at
|
|
*
|
|
* http://www.alfresco.org/legal/license.txt
|
|
*
|
|
* 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.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 org.alfresco.error.StackTraceUtil;
|
|
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
|
import org.alfresco.repo.transaction.TransactionUtil;
|
|
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.alfresco.service.transaction.TransactionService;
|
|
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 TransactionService transactionService;
|
|
private RetryingTransactionHelper transactionHelper;
|
|
|
|
private String contentUrl;
|
|
private String mimetype;
|
|
private String encoding;
|
|
|
|
/**
|
|
* @param contentUrl the content URL
|
|
*/
|
|
protected AbstractContentAccessor(String contentUrl)
|
|
{
|
|
if (contentUrl == null || contentUrl.length() == 0)
|
|
{
|
|
throw new IllegalArgumentException("contentUrl must be a valid String");
|
|
}
|
|
this.contentUrl = contentUrl;
|
|
|
|
// the default encoding is Java's default encoding
|
|
encoding = "UTF-8";
|
|
}
|
|
|
|
@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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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("]");
|
|
return sb.toString();
|
|
}
|
|
|
|
public ContentData getContentData()
|
|
{
|
|
ContentData property = new ContentData(contentUrl, mimetype, getSize(), encoding);
|
|
return property;
|
|
}
|
|
|
|
/**
|
|
* Provides access to transactions for implementing classes
|
|
*
|
|
* @return Returns a source of user transactions
|
|
*/
|
|
// protected TransactionService getTransactionService()
|
|
// {
|
|
// return transactionService;
|
|
// }
|
|
|
|
/**
|
|
* Set the transaction provider to be used by {@link ContentStreamListener listeners}.
|
|
*
|
|
* @param transactionService the transaction service to wrap callback code in
|
|
*/
|
|
// public void setTransactionService(TransactionService transactionService)
|
|
// {
|
|
// this.transactionService = transactionService;
|
|
// }
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* 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<ContentStreamListener> 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<ContentStreamListener> listeners;
|
|
|
|
public ChannelCloseCallbackAdvise(List<ContentStreamListener> 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;
|
|
}
|
|
RetryingTransactionHelper.Callback cb =
|
|
new RetryingTransactionHelper.Callback()
|
|
{
|
|
public Object execute()
|
|
{
|
|
for (ContentStreamListener listener : listeners)
|
|
{
|
|
listener.contentStreamClosed();
|
|
}
|
|
return null;
|
|
}
|
|
};
|
|
// TransactionUtil.TransactionWork<Object> work = new TransactionUtil.TransactionWork<Object>()
|
|
// {
|
|
// public Object doWork()
|
|
// {
|
|
// // call the listeners
|
|
// for (ContentStreamListener listener : listeners)
|
|
// {
|
|
// listener.contentStreamClosed();
|
|
// }
|
|
// return null;
|
|
// }
|
|
// };
|
|
if (transactionHelper != null)
|
|
{
|
|
// Execute in transaction.
|
|
transactionHelper.doInTransaction(cb, false);
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
cb.execute();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
throw new ContentIOException("Failed to executed channel close callbacks", e);
|
|
}
|
|
}
|
|
// done
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("" + listeners.size() + " content listeners called: close");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wraps a <code>FileChannel</code> to provide callbacks to listeners when the
|
|
* channel is {@link java.nio.channels.Channel#close() closed}.
|
|
* <p>
|
|
* This class is unfortunately necessary as the {@link FileChannel} doesn't have
|
|
* an single interface defining its methods, making it difficult to put an
|
|
* advice around the methods that require overriding.
|
|
*
|
|
* @author Derek Hulley
|
|
*/
|
|
protected class CallbackFileChannel extends FileChannel
|
|
{
|
|
/** the channel to route all calls to */
|
|
private FileChannel delegate;
|
|
/** listeners waiting for the stream close */
|
|
private List<ContentStreamListener> listeners;
|
|
|
|
/**
|
|
* @param delegate the channel that will perform the work
|
|
* @param listeners listeners for events coming from this channel
|
|
*/
|
|
public CallbackFileChannel(
|
|
FileChannel delegate,
|
|
List<ContentStreamListener> listeners)
|
|
{
|
|
if (delegate == null)
|
|
{
|
|
throw new IllegalArgumentException("FileChannel delegate is required");
|
|
}
|
|
if (delegate instanceof CallbackFileChannel)
|
|
{
|
|
throw new IllegalArgumentException("FileChannel delegate may not be a CallbackFileChannel");
|
|
}
|
|
|
|
this.delegate = delegate;
|
|
this.listeners = listeners;
|
|
}
|
|
|
|
/**
|
|
* Closes the channel and makes the callbacks to the listeners
|
|
*/
|
|
@Override
|
|
protected void implCloseChannel() throws IOException
|
|
{
|
|
delegate.close();
|
|
fireChannelClosed();
|
|
}
|
|
|
|
/**
|
|
* Helper method to notify stream listeners
|
|
*/
|
|
private void fireChannelClosed()
|
|
{
|
|
if (listeners.size() == 0)
|
|
{
|
|
// nothing to do
|
|
return;
|
|
}
|
|
// We're now doing this in a retrying transaction, which means
|
|
// that the body of execute() must be idempotent.
|
|
RetryingTransactionHelper.Callback cb =
|
|
new RetryingTransactionHelper.Callback()
|
|
{
|
|
public Object execute()
|
|
{
|
|
for (ContentStreamListener listener : listeners)
|
|
{
|
|
listener.contentStreamClosed();
|
|
}
|
|
return null;
|
|
}
|
|
};
|
|
// TransactionUtil.TransactionWork<Object> work = new TransactionUtil.TransactionWork<Object>()
|
|
// {
|
|
// public Object doWork()
|
|
// {
|
|
// // call the listeners
|
|
// for (ContentStreamListener listener : listeners)
|
|
// {
|
|
// listener.contentStreamClosed();
|
|
// }
|
|
// return null;
|
|
// }
|
|
// };
|
|
// We're now doing this inside a Retrying transaction.
|
|
// NB
|
|
if (transactionHelper != null)
|
|
{
|
|
// just create a transaction
|
|
transactionHelper.doInTransaction(cb, false);
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
cb.execute();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
throw new ContentIOException("Failed to executed channel close callbacks", e);
|
|
}
|
|
}
|
|
// done
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("" + listeners.size() + " content listeners called: close");
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void force(boolean metaData) throws IOException
|
|
{
|
|
delegate.force(metaData);
|
|
}
|
|
|
|
@Override
|
|
public FileLock lock(long position, long size, boolean shared) throws IOException
|
|
{
|
|
return delegate.lock(position, size, shared);
|
|
}
|
|
|
|
@Override
|
|
public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException
|
|
{
|
|
return delegate.map(mode, position, size);
|
|
}
|
|
|
|
@Override
|
|
public long position() throws IOException
|
|
{
|
|
return delegate.position();
|
|
}
|
|
|
|
@Override
|
|
public FileChannel position(long newPosition) throws IOException
|
|
{
|
|
return delegate.position(newPosition);
|
|
}
|
|
|
|
@Override
|
|
public int read(ByteBuffer dst) throws IOException
|
|
{
|
|
return delegate.read(dst);
|
|
}
|
|
|
|
@Override
|
|
public int read(ByteBuffer dst, long position) throws IOException
|
|
{
|
|
return delegate.read(dst, position);
|
|
}
|
|
|
|
@Override
|
|
public long read(ByteBuffer[] dsts, int offset, int length) throws IOException
|
|
{
|
|
return delegate.read(dsts, offset, length);
|
|
}
|
|
|
|
@Override
|
|
public long size() throws IOException
|
|
{
|
|
return delegate.size();
|
|
}
|
|
|
|
@Override
|
|
public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException
|
|
{
|
|
return delegate.transferFrom(src, position, count);
|
|
}
|
|
|
|
@Override
|
|
public long transferTo(long position, long count, WritableByteChannel target) throws IOException
|
|
{
|
|
return delegate.transferTo(position, count, target);
|
|
}
|
|
|
|
@Override
|
|
public FileChannel truncate(long size) throws IOException
|
|
{
|
|
return delegate.truncate(size);
|
|
}
|
|
|
|
@Override
|
|
public FileLock tryLock(long position, long size, boolean shared) throws IOException
|
|
{
|
|
return delegate.tryLock(position, size, shared);
|
|
}
|
|
|
|
@Override
|
|
public int write(ByteBuffer src) throws IOException
|
|
{
|
|
return delegate.write(src);
|
|
}
|
|
|
|
@Override
|
|
public int write(ByteBuffer src, long position) throws IOException
|
|
{
|
|
return delegate.write(src, position);
|
|
}
|
|
|
|
@Override
|
|
public long write(ByteBuffer[] srcs, int offset, int length) throws IOException
|
|
{
|
|
return delegate.write(srcs, offset, length);
|
|
}
|
|
}
|
|
}
|