/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.synapse.transport.vfs;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMOutputFormat;
import org.apache.axis2.AxisFault;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.description.Parameter;
import org.apache.axis2.description.TransportOutDescription;
import org.apache.axis2.format.BinaryFormatter;
import org.apache.axis2.format.PlainTextFormatter;
import org.apache.axis2.transport.MessageFormatter;
import org.apache.axis2.transport.OutTransportInfo;
import org.apache.axis2.transport.base.*;
import org.apache.axis2.util.MessageProcessorSelector;
import org.apache.commons.io.output.CountingOutputStream;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.vfs2.*;
import org.apache.commons.vfs2.impl.StandardFileSystemManager;
import org.apache.synapse.commons.vfs.VFSConstants;
import org.apache.synapse.commons.vfs.VFSParamDTO;
import org.apache.synapse.commons.vfs.VFSUtils;
import org.apache.synapse.commons.vfs.VFSOutTransportInfo ;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
/**
* axis2.xml - transport definition
* <transportSender name="file" class="org.apache.synapse.transport.vfs.VFSTransportSender">
* <parameter name="transport.vfs.Locking">enable|disable</parameter> ?
* </transportSender>
*/
public class VFSTransportSender extends AbstractTransportSender implements ManagementSupport {
public static final String TRANSPORT_NAME = "vfs";
/** The VFS file system manager */
private FileSystemManager fsManager = null;
/**
* By default file locking in VFS transport is turned on at a global level
*
* NOTE: DO NOT USE THIS FLAG, USE PollTableEntry#isFileLockingEnabled() TO CHECK WHETHR
* FILE LOCKING IS ENABLED
*/
private boolean globalFileLockingFlag = true;
private VFSParamDTO vfsParamDTO = null;
/**
* Map to hold lock object for each host per service when operating in synchronous write mode
*/
private static final ConcurrentHashMap<String,WriteLockObject> lockingObjects = new ConcurrentHashMap<>();
/**
* The public constructor
*/
public VFSTransportSender() {
log = LogFactory.getLog(VFSTransportSender.class);
}
/**
* Initialize the VFS file system manager and be ready to send messages
* @param cfgCtx the axis2 configuration context
* @param transportOut the transport-out description
* @throws AxisFault on error
*/
public void init(ConfigurationContext cfgCtx, TransportOutDescription transportOut)
throws AxisFault {
super.init(cfgCtx, transportOut);
try {
StandardFileSystemManager fsm = new StandardFileSystemManager();
fsm.setConfiguration(getClass().getClassLoader().getResource("providers.xml"));
fsm.init();
fsManager = fsm;
Parameter lckFlagParam = transportOut.getParameter(VFSConstants.TRANSPORT_FILE_LOCKING);
if (lckFlagParam != null) {
String strLockingFlag = lckFlagParam.getValue().toString();
// by-default enabled, if explicitly specified as "disable" make it disable
if (VFSConstants.TRANSPORT_FILE_LOCKING_DISABLED.equals(strLockingFlag)) {
globalFileLockingFlag = false;
}
}
Parameter strAutoLock = transportOut.getParameter(VFSConstants.TRANSPORT_AUTO_LOCK_RELEASE);
boolean autoLockRelease = false;
boolean autoLockReleaseSameNode = true;
Long autoLockReleaseInterval = null;
if (strAutoLock != null && strAutoLock.getValue() != null && !strAutoLock.getValue().toString().isEmpty()) {
try {
autoLockRelease = Boolean.parseBoolean(strAutoLock.getValue().toString());
} catch (Exception e) {
autoLockRelease = false;
log.warn("VFS Auto lock removal not set properly. Given value is : "
+ strAutoLock + ", defaults to - " + autoLockRelease, e);
}
if (autoLockRelease) {
Parameter strAutoLockInterval = transportOut
.getParameter(VFSConstants.TRANSPORT_AUTO_LOCK_RELEASE_INTERVAL);
if (strAutoLockInterval != null && strAutoLockInterval.getValue() != null
&& !strAutoLockInterval.getValue().toString().isEmpty()) {
try {
autoLockReleaseInterval = Long.parseLong(strAutoLockInterval.getValue().toString());
} catch (Exception e) {
autoLockReleaseInterval = null;
log.warn(
"VFS Auto lock release interval is not set properly. Given value is : "
+ strAutoLockInterval + ", defaults to - null", e);
}
}
Parameter strAutoLockReleaseSameNode = transportOut
.getParameter(VFSConstants.TRANSPORT_AUTO_LOCK_RELEASE_SAME_NODE);
if (strAutoLockReleaseSameNode != null && strAutoLockReleaseSameNode.getValue() != null
&& !strAutoLockReleaseSameNode.getValue().toString().isEmpty()) {
try {
autoLockReleaseSameNode = Boolean
.parseBoolean(strAutoLockReleaseSameNode.getValue().toString());
} catch (Exception e) {
autoLockReleaseSameNode = true;
log.warn(
"VFS Auto lock removal same node property not set properly. Given value is : "
+ autoLockReleaseSameNode + ", defaults to - " + autoLockReleaseSameNode, e);
}
}
}
}
vfsParamDTO = new VFSParamDTO();
vfsParamDTO.setAutoLockRelease(autoLockRelease);
vfsParamDTO.setAutoLockReleaseInterval(autoLockReleaseInterval);
vfsParamDTO.setAutoLockReleaseSameNode(autoLockReleaseSameNode);
} catch (FileSystemException e) {
handleException("Error initializing the file transport : " + e.getMessage(), e);
}
}
/**
* Send the given message over the VFS transport
*
* @param msgCtx the axis2 message context
* @throws AxisFault on error
*/
public void sendMessage(MessageContext msgCtx, String targetAddress,
OutTransportInfo outTransportInfo) throws AxisFault {
if (waitForSynchronousResponse(msgCtx)) {
throw new AxisFault("The VFS transport doesn't support synchronous responses. " +
"Please use the appropriate (out only) message exchange pattern.");
}
VFSOutTransportInfo vfsOutInfo = null;
if (targetAddress != null) {
vfsOutInfo = new VFSOutTransportInfo(targetAddress, globalFileLockingFlag);
} else if (outTransportInfo != null && outTransportInfo instanceof VFSOutTransportInfo) {
vfsOutInfo = (VFSOutTransportInfo) outTransportInfo;
}
WriteLockObject lockObject = null;
String baseUri = null;
if (vfsOutInfo != null) {
if (vfsOutInfo.getSendFileSynchronously()) {
baseUri = getBaseUri(targetAddress);
if (baseUri != null) {
if (!lockingObjects.containsKey(baseUri)) {
lockingObjects.putIfAbsent(baseUri, new WriteLockObject());
log.debug("New locking object created for Synchronous write|MapSize:" + lockingObjects.size());
}
lockObject = lockingObjects.get(baseUri);
lockObject.incrementUsers();
}
}
}
try {
if (lockObject == null) {
writeFile(msgCtx, vfsOutInfo);
} else {
synchronized (lockObject) {
writeFile(msgCtx, vfsOutInfo);
}
}
} finally {
if (lockObject != null && (lockObject.decrementAndGetUsers() == 0)) {
lockingObjects.remove(baseUri);
if (log.isDebugEnabled()) {
log.debug("locking object removed for after Synchronous write|MapSize:" + lockingObjects.size());
}
}
}
}
private void writeFile(MessageContext msgCtx, VFSOutTransportInfo vfsOutInfo) throws AxisFault {
FileSystemOptions fso = null;
try {
fso = VFSUtils.attachFileSystemOptions(vfsOutInfo.getOutFileSystemOptionsMap(), fsManager);
} catch (Exception e) {
log.error("Error while attaching VFS file system properties. " + e.getMessage());
}
if (vfsOutInfo != null) {
FileObject replyFile = null;
try {
boolean wasError = true;
int retryCount = 0;
int maxRetryCount = vfsOutInfo.getMaxRetryCount();
long reconnectionTimeout = vfsOutInfo.getReconnectTimeout();
boolean append = vfsOutInfo.isAppend();
while (wasError) {
try {
retryCount++;
replyFile = fsManager.resolveFile(vfsOutInfo.getOutFileURI(), fso);
if (replyFile == null) {
log.error("replyFile is null");
throw new FileSystemException("replyFile is null");
}
wasError = false;
} catch (FileSystemException e) {
log.error("cannot resolve replyFile", e);
if(maxRetryCount <= retryCount) {
handleException("cannot resolve replyFile repeatedly: "
+ e.getMessage(), e);
}
}
if (wasError) {
try {
Thread.sleep(reconnectionTimeout);
} catch (InterruptedException e2) {
e2.printStackTrace();
}
}
}
//If the reply folder does not exists create the folder structure
if (vfsOutInfo.isForceCreateFolder(msgCtx)) {
String strPath = vfsOutInfo.getOutFileURI();
int iIndex = strPath.indexOf("?");
if(iIndex > -1){
strPath = strPath.substring(0, iIndex);
}
//Need to add a slash otherwise vfs consider this as a file
if(!strPath.endsWith("/") || !strPath.endsWith("\\")){
strPath += "/";
}
FileObject replyFolder = fsManager.resolveFile(strPath, fso);
if(!replyFolder.exists()){
replyFile.createFolder();
}
}
if (replyFile.exists()) {
if (replyFile.getType() == FileType.FOLDER) {
// we need to write a file containing the message to this folder
FileObject responseFile = fsManager.resolveFile(replyFile,
VFSUtils.getFileName(msgCtx, vfsOutInfo));
// if file locking is not disabled acquire the lock
// before uploading the file
if (vfsOutInfo.isFileLockingEnabled()) {
acquireLockForSending(responseFile, vfsOutInfo, fso);
if (!responseFile.exists()) {
responseFile.createFile();
}
populateResponseFile(responseFile, msgCtx,append, true, fso);
VFSUtils.releaseLock(fsManager, responseFile, fso);
} else {
if (!responseFile.exists()) {
responseFile.createFile();
}
populateResponseFile(responseFile, msgCtx,append, false, fso);
}
} else if (replyFile.getType() == FileType.FILE) {
// if file locking is not disabled acquire the lock
// before uploading the file
if (vfsOutInfo.isFileLockingEnabled()) {
acquireLockForSending(replyFile, vfsOutInfo, fso);
populateResponseFile(replyFile, msgCtx, append, true, fso);
VFSUtils.releaseLock(fsManager, replyFile, fso);
} else {
populateResponseFile(replyFile, msgCtx, append, false, fso);
}
} else {
handleException("Unsupported reply file type : " + replyFile.getType() +
" for file : " + VFSUtils.maskURLPassword(vfsOutInfo.getOutFileURI()));
}
} else {
// if file locking is not disabled acquire the lock before uploading the file
if (vfsOutInfo.isFileLockingEnabled()) {
acquireLockForSending(replyFile, vfsOutInfo, fso);
replyFile.createFile();
populateResponseFile(replyFile, msgCtx, append, true, fso);
VFSUtils.releaseLock(fsManager, replyFile, fso);
} else {
replyFile.createFile();
populateResponseFile(replyFile, msgCtx, append, false, fso);
}
}
} catch (FileSystemException e) {
handleException("Error resolving reply file : " +
VFSUtils.maskURLPassword(vfsOutInfo.getOutFileURI()), e);
} finally {
if (replyFile != null) {
try {
if (fsManager!= null && replyFile.getParent() != null && replyFile.getParent().getFileSystem() != null) {
if (fsManager != null && replyFile.getName() != null &&
replyFile.getName().getScheme() != null) {
fsManager.closeFileSystem(replyFile.getParent().getFileSystem());
}
}
replyFile.close();
} catch (Exception ex) {
log.warn("File system manager already closed", ex);
}
}
}
} else {
handleException("Unable to determine out transport information to send message");
}
}
private MessageFormatter getMessageFormatter(MessageContext msgContext){
OMElement firstChild = msgContext.getEnvelope().getBody().getFirstElement();
if (firstChild != null) {
if (BaseConstants.DEFAULT_BINARY_WRAPPER.equals(firstChild.getQName())) {
return new BinaryFormatter();
} else if (BaseConstants.DEFAULT_TEXT_WRAPPER.equals(firstChild.getQName())) {
return new PlainTextFormatter();
}
}
try {
return MessageProcessorSelector.getMessageFormatter(msgContext);
} catch (AxisFault axisFault) {
throw new BaseTransportException("Unable to get the message formatter to use");
}
}
private void populateResponseFile(FileObject responseFile, MessageContext msgContext,
boolean append, boolean lockingEnabled, FileSystemOptions fso) throws AxisFault {
MessageFormatter messageFormatter = getMessageFormatter(msgContext);
OMOutputFormat format = BaseUtils.getOMOutputFormat(msgContext);
try {
CountingOutputStream os = new CountingOutputStream(
responseFile.getContent().getOutputStream(append));
try {
messageFormatter.writeTo(msgContext, format, os, true);
} finally {
os.close();
}
//setting last modified
Long lastModified = VFSUtils.getLastModified(msgContext);
if (lastModified != null) {
try {
if (log.isDebugEnabled()) {
log.debug("Set last modified to " + lastModified);
}
responseFile.getContent().setLastModifiedTime(lastModified);
} catch (Exception e) {
log.warn("Could not set last modified.", e);
}
}
// update metrics
metrics.incrementMessagesSent(msgContext);
metrics.incrementBytesSent(msgContext, os.getByteCount());
} catch (FileSystemException e) {
if (lockingEnabled) {
VFSUtils.releaseLock(fsManager, responseFile, fso);
}
metrics.incrementFaultsSending();
handleException("IO Error while creating response file : " + VFSUtils.maskURLPassword(responseFile.getName().getURI()), e);
} catch (IOException e) {
if (lockingEnabled) {
VFSUtils.releaseLock(fsManager, responseFile, fso);
}
metrics.incrementFaultsSending();
handleException("IO Error while creating response file : " + VFSUtils.maskURLPassword(responseFile.getName().getURI()), e);
}
}
private void acquireLockForSending(FileObject responseFile, VFSOutTransportInfo vfsOutInfo, FileSystemOptions fso)
throws AxisFault {
int tryNum = 0;
// wait till we get the lock
while (!VFSUtils.acquireLock(fsManager, responseFile, fso, false)) {
if (vfsOutInfo.getMaxRetryCount() == tryNum++) {
handleException("Couldn't send the message to file : "
+ VFSUtils.maskURLPassword(responseFile.getName().getURI()) + ", unable to acquire the " +
"lock even after " + tryNum + " retries");
} else {
log.warn("Couldn't get the lock for the file : "
+ VFSUtils.maskURLPassword(responseFile.getName().getURI()) + ", retry : " + tryNum
+ " scheduled after : " + vfsOutInfo.getReconnectTimeout());
try {
Thread.sleep(vfsOutInfo.getReconnectTimeout());
} catch (InterruptedException ignore) {}
}
}
}
/**
* This method extracts base uri of vfs string.
*
* @param targetAddress target address of the vfs connection
* @return base uri for the vfs connection
*/
private String getBaseUri(String targetAddress) {
//Remove vfs part from the uri
if (targetAddress.contains("vfs:")) {
targetAddress = targetAddress.substring(targetAddress.indexOf("vfs:") + 4);
}
int index = targetAddress.indexOf("://");
if (index > -1) {
int endIndex = targetAddress.indexOf('/', index + 3);
if (endIndex > -1) {
return targetAddress.substring(0, endIndex);
}
}
return null;
}
}