Merge pull request #10 from garyrussell/INT-1945
dir -> directory polishing INT-1945 (S)FTP Gateway - Initial Implementation
This commit is contained in:
@@ -18,8 +18,9 @@
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Spring Integration supports sending and receiving files over FTP/FTPS by providing two types of <emphasis>client</emphasis>
|
||||
side adapters: <emphasis>Inbound Channel Adapter</emphasis> and <emphasis>Outbound Channel Adapter</emphasis>. It also provides
|
||||
Spring Integration supports sending and receiving files over FTP/FTPS by providing three <emphasis>client</emphasis>
|
||||
side endpoints: <emphasis>Inbound Channel Adapter</emphasis>, <emphasis>Outbound Channel Adapter</emphasis>, and
|
||||
<emphasis>Outbound Gateway</emphasis>. It also provides
|
||||
convenient namespace-based configuration options for defining these <emphasis>client</emphasis> components.
|
||||
</para>
|
||||
<para>
|
||||
@@ -242,20 +243,110 @@ xsi:schemaLocation="http://www.springframework.org/schema/integration/ftp
|
||||
</para>
|
||||
|
||||
</section>
|
||||
<section id="ftp-outbound-gateway">
|
||||
<title>FTP Outbound Gateway</title>
|
||||
|
||||
<para>
|
||||
The <emphasis>FTP Outbound Gateway</emphasis> provides a limited set of commands to interact with a remote FTP/FTPS server.
|
||||
<para>
|
||||
Commands supported are:
|
||||
<itemizedlist>
|
||||
<listitem>ls (list files)</listitem>
|
||||
<listitem>get (retrieve file(s))</listitem>
|
||||
<listitem>rm (remove file(s))</listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
<para>
|
||||
ls supports the following options:
|
||||
<itemizedlist>
|
||||
<listitem>-1 - just retrieve a list of filenames, default is to retrieve a
|
||||
list of <classname>FileInfo</classname> objects.</listitem>
|
||||
<listitem>-a - include all files (including those starting with '.')</listitem>
|
||||
<listitem>-f - do not sort the list</listitem>
|
||||
<listitem>-dirs - include directories (excluded by default)</listitem>
|
||||
<listitem>-links - include symbolic links (excluded by default)</listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
<para>
|
||||
In addition, filename filtering is provided, in the same manner as the
|
||||
<classname>inbound-channel-adapter</classname>.
|
||||
</para>
|
||||
<para>
|
||||
The message payload resulting from an <emphasis>ls</emphasis> operation is a list of file names,
|
||||
or a list of <classname>FileInfo</classname> objects. These objects provide
|
||||
information such as modified time, permissions etc.
|
||||
</para>
|
||||
<para>
|
||||
The remote directory that the <emphasis>ls</emphasis> command acted on is provided
|
||||
in the <classname>file_remote_dir</classname> header.
|
||||
</para>
|
||||
<para>
|
||||
<emphasis>get</emphasis> supports the following option:
|
||||
<itemizedlist>
|
||||
<listitem>-P - preserve the timestamp of the remote file</listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
<para>
|
||||
The message payload resulting from a <emphasis>get</emphasis> operation is a
|
||||
<classname>File</classname> object representing the retrieved file.
|
||||
</para>
|
||||
<para>
|
||||
The remote directory is provided in the <classname>file_remote_dir</classname> header, and the filename is
|
||||
provided in the <classname>file_remote_file</classname> header.
|
||||
</para>
|
||||
<para></para>
|
||||
<para>
|
||||
The <emphasis>rm</emphasis> command has no options.
|
||||
</para>
|
||||
<para>
|
||||
<note>
|
||||
Filters are not supported with the <emphasis>rm</emphasis> command.
|
||||
</note>
|
||||
</para>
|
||||
<para>
|
||||
The message payload resulting from an <emphasis>rm</emphasis> operation is Boolean.TRUE if the
|
||||
remove was successful, Boolean.FALSE otherwise.
|
||||
The remote directory is provided in the <classname>file_remote_dir</classname> header, and the filename is
|
||||
provided in the <classname>file_remote_file</classname> header.
|
||||
</para>
|
||||
<para></para>
|
||||
<para>
|
||||
In each case, the PATH that these commands act on is provided by the 'expression'
|
||||
property of the gateway.
|
||||
</para>
|
||||
</para>
|
||||
<para>
|
||||
Here is an example of a gateway configured for an ls command...
|
||||
<programlisting language="xml"><![CDATA[<int-ftp:outbound-gateway id="gateway1"
|
||||
session-factory="ftpSessionFactory"
|
||||
request-channel="inbound1"
|
||||
command="ls"
|
||||
command-options="-1"
|
||||
expression="payload"
|
||||
reply-channel="toSplitter"/>
|
||||
]]></programlisting>
|
||||
</para>
|
||||
<para>
|
||||
The payload of the message sent to the toSplitter channel is a list of String objects
|
||||
containing the filename of each file. If the <classname>command-options</classname> was
|
||||
omitted, it would be a list of <classname>FileInfo</classname> objects. Options are
|
||||
provided space-delimited, e.g. <classname>command-options="-1 -dirs -links"</classname>.
|
||||
</para>
|
||||
</section>
|
||||
<section id="ftp-session-caching">
|
||||
<title>FTP Session Caching</title>
|
||||
<para>
|
||||
One of the optimizations implemented by the FTP adapters is session caching. Similar to JDBC pooling of Connections, the FTP Adapters maintain a
|
||||
pool of Sessions by default. However there are times when this behavior is not desired (e.g., security etc.).
|
||||
To disable session caching you can set the <code>cache-sessions</code> attribute to <code>false</code> (the default value is <code>true</code>).
|
||||
<title>FTP Session Caching</title>
|
||||
<para>
|
||||
One of the optimizations implemented by the FTP adapters is session caching. Similar to JDBC pooling of Connections, the FTP Adapters maintain a
|
||||
pool of Sessions by default. However there are times when this behavior is not desired (e.g., security etc.).
|
||||
To disable session caching you can set the <code>cache-sessions</code> attribute to <code>false</code> (the default value is <code>true</code>).
|
||||
<programlisting language="xml"><![CDATA[<int-ftp:inbound-channel-adapter id="ftpInbound"
|
||||
channel="ftpChannel"
|
||||
channel="ftpChannel"
|
||||
. . .
|
||||
cache-sessions="false"
|
||||
. . .
|
||||
</int-ftp:inbound-channel-adapter>]]></programlisting>
|
||||
</int-ftp:inbound-channel-adapter>]]></programlisting>
|
||||
|
||||
The same attribute can also be used with Outbound Channel Adapters.
|
||||
</para>
|
||||
</para>
|
||||
</section>
|
||||
</chapter>
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Spring Integration supports sending and receiving files over SFTP by providing two types of <emphasis>client</emphasis>s -
|
||||
<emphasis>Inbound Channel Adapters</emphasis> and <emphasis>Outbound Channel Adapters</emphasis> as well as convenient
|
||||
namespace configuration to define these <emphasis>client</emphasis>s.
|
||||
Spring Integration supports sending and receiving files over SFTP by providing three <emphasis>client</emphasis>
|
||||
side endpoints:
|
||||
<emphasis>Inbound Channel Adapter</emphasis>, <emphasis>Outbound Channel Adapter</emphasis>, and <emphasis>Outbound Gateway</emphasis>
|
||||
It also provides convenient
|
||||
namespace configuration to define these <emphasis>client</emphasis> components.
|
||||
|
||||
<programlisting language="xml"><![CDATA[xmlns:int-sftp="http://www.springframework.org/schema/integration/sftp"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/integration/sftp
|
||||
@@ -168,7 +170,99 @@ xsi:schemaLocation="http://www.springframework.org/schema/integration/sftp
|
||||
value that computes the <emphasis>file name</emphasis> based on its original name while also appending a suffix: '-foo'.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
|
||||
<section id="sftp-outbound-gateway">
|
||||
<title>SFTP Outbound Gateway</title>
|
||||
|
||||
<para>
|
||||
The <emphasis>SFTP Outbound Gateway</emphasis> provides a limited set of commands to interact with a remote SFTP server.
|
||||
<para>
|
||||
Commands supported are:
|
||||
<itemizedlist>
|
||||
<listitem>ls (list files)</listitem>
|
||||
<listitem>get (retrieve file(s))</listitem>
|
||||
<listitem>rm (remove file(s))</listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
<para>
|
||||
ls supports the following options:
|
||||
<itemizedlist>
|
||||
<listitem>-1 - just retrieve a list of filenames, default is to retrieve a
|
||||
list of <classname>FileInfo</classname> objects.</listitem>
|
||||
<listitem>-a - include all files (including those starting with '.')</listitem>
|
||||
<listitem>-f - do not sort the list</listitem>
|
||||
<listitem>-dirs - include directories (excluded by default)</listitem>
|
||||
<listitem>-links - include symbolic links (excluded by default)</listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
<para>
|
||||
In addition, filename filtering is provided, in the same manner as the
|
||||
<classname>inbound-channel-adapter</classname>.
|
||||
</para>
|
||||
<para>
|
||||
The message payload resulting from an <emphasis>ls</emphasis> operation is a list of file names,
|
||||
or a list of <classname>FileInfo</classname> objects. These objects provide
|
||||
information such as modified time, permissions etc.
|
||||
</para>
|
||||
<para>
|
||||
The remote directory that the <emphasis>ls</emphasis> command acted on is provided
|
||||
in the <classname>file_remote_dir</classname> header.
|
||||
</para>
|
||||
<para>
|
||||
<emphasis>get</emphasis> supports the following option:
|
||||
<itemizedlist>
|
||||
<listitem>-P - preserve the timestamp of the remote file</listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
<para>
|
||||
The message payload resulting from a <emphasis>get</emphasis> operation is a
|
||||
<classname>File</classname> object representing the retrieved file.
|
||||
</para>
|
||||
<para>
|
||||
The remote directory is provided in the <classname>file_remote_dir</classname> header, and the filename is
|
||||
provided in the <classname>file_remote_file</classname> header.
|
||||
</para>
|
||||
<para></para>
|
||||
<para>
|
||||
The <emphasis>rm</emphasis> command has no options.
|
||||
</para>
|
||||
<para>
|
||||
<note>
|
||||
Filters are not supported with the <emphasis>rm</emphasis> command.
|
||||
</note>
|
||||
</para>
|
||||
<para>
|
||||
The message payload resulting from an <emphasis>rm</emphasis> operation is Boolean.TRUE if the
|
||||
remove was successful, Boolean.FALSE otherwise.
|
||||
The remote directory is provided in the <classname>file_remote_dir</classname> header, and the filename is
|
||||
provided in the <classname>file_remote_file</classname> header.
|
||||
</para>
|
||||
<para></para>
|
||||
<para>
|
||||
In each case, the PATH that these commands act on is provided by the 'expression'
|
||||
property of the gateway.
|
||||
</para>
|
||||
</para>
|
||||
<para>
|
||||
Here is an example of a gateway configured for an ls command...
|
||||
<programlisting language="xml"><![CDATA[<int-ftp:outbound-gateway id="gateway1"
|
||||
session-factory="ftpSessionFactory"
|
||||
request-channel="inbound1"
|
||||
command="ls"
|
||||
command-options="-1"
|
||||
expression="payload"
|
||||
reply-channel="toSplitter"/>
|
||||
]]></programlisting>
|
||||
</para>
|
||||
<para>
|
||||
The payload of the message sent to the toSplitter channel is a list of String objects
|
||||
containing the filename of each file. If the <classname>command-options</classname> was
|
||||
omitted, it would be a list of <classname>FileInfo</classname> objects. Options are
|
||||
provided space-delimited, e.g. <classname>command-options="-1 -dirs -links"</classname>.
|
||||
</para>
|
||||
|
||||
</section>
|
||||
|
||||
<section id="sftp-jsch-logging">
|
||||
<title>SFTP/JSCH Logging</title>
|
||||
<para>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -30,4 +30,8 @@ public abstract class FileHeaders {
|
||||
|
||||
public static final String ORIGINAL_FILE = PREFIX + "originalFile";
|
||||
|
||||
public static final String REMOTE_DIRECTORY = PREFIX + "remoteDirectory";
|
||||
|
||||
public static final String REMOTE_FILE = PREFIX + "remoteFile";
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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.springframework.integration.file.config;
|
||||
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.integration.config.xml.AbstractConsumerEndpointParser;
|
||||
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @since 2.1
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractRemoteFileOutboundGatewayParser extends
|
||||
AbstractConsumerEndpointParser {
|
||||
|
||||
@Override
|
||||
protected String getInputChannelAttributeName() {
|
||||
return "request-channel";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(getGatewayClassName());
|
||||
// build the SessionFactory and provide as a constructor argument
|
||||
String cacheSessions = element.getAttribute("cache-sessions");
|
||||
if ("false".equalsIgnoreCase(cacheSessions)) {
|
||||
builder.addConstructorArgReference(element.getAttribute("session-factory"));
|
||||
}
|
||||
else {
|
||||
BeanDefinitionBuilder sessionFactoryBuilder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
"org.springframework.integration.file.remote.session.CachingSessionFactory");
|
||||
sessionFactoryBuilder.addConstructorArgReference(element.getAttribute("session-factory"));
|
||||
builder.addConstructorArgValue(sessionFactoryBuilder.getBeanDefinition());
|
||||
}
|
||||
builder.addConstructorArgValue(element.getAttribute("command"));
|
||||
builder.addConstructorArgValue(element.getAttribute(EXPRESSION_ATTRIBUTE));
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "command-options", "options");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "temporary-file-suffix");
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "reply-channel", "outputChannel");
|
||||
this.configureFilter(builder, element, parserContext);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "remote-file-separator");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "local-directory");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "auto-create-local-directory");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "order");
|
||||
return builder;
|
||||
}
|
||||
|
||||
protected void configureFilter(BeanDefinitionBuilder builder, Element element, ParserContext parserContext) {
|
||||
String filter = element.getAttribute("filter");
|
||||
String fileNamePattern = element.getAttribute("filename-pattern");
|
||||
String fileNameRegex = element.getAttribute("filename-regex");
|
||||
boolean hasFilter = StringUtils.hasText(filter);
|
||||
boolean hasFileNamePattern = StringUtils.hasText(fileNamePattern);
|
||||
boolean hasFileNameRegex = StringUtils.hasText(fileNameRegex);
|
||||
int count = hasFilter ? 1 : 0;
|
||||
count += hasFileNamePattern ? 1 : 0;
|
||||
count += hasFileNameRegex ? 1 : 0;
|
||||
if (count > 1) {
|
||||
parserContext.getReaderContext().error("at most one of 'filename-pattern', " +
|
||||
"'filename-regex', or 'filter' is allowed on remote file inbound adapter", element);
|
||||
} else if (hasFilter) {
|
||||
builder.addPropertyReference("filter", filter);
|
||||
} else if (hasFileNamePattern) {
|
||||
BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
this.getSimplePatternFileListFilterClassname());
|
||||
filterBuilder.addConstructorArgValue(fileNamePattern);
|
||||
builder.addPropertyValue("filter", filterBuilder.getBeanDefinition());
|
||||
} else if (hasFileNameRegex) {
|
||||
BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
this.getRegexPatternFileListFilterClassname());
|
||||
filterBuilder.addConstructorArgValue(fileNameRegex);
|
||||
builder.addPropertyValue("filter", filterBuilder.getBeanDefinition());
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract String getRegexPatternFileListFilterClassname();
|
||||
|
||||
protected abstract String getSimplePatternFileListFilterClassname();
|
||||
|
||||
protected abstract String getGatewayClassName();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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.springframework.integration.file.remote;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Abstract implementation of {@link FileInfo}; provides a setter
|
||||
* for the remote directory and a generic toString implementation.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @since 2.1
|
||||
*/
|
||||
public abstract class AbstractFileInfo<F> implements FileInfo<F>, Comparable<FileInfo<F>> {
|
||||
|
||||
private String remoteDirectory;
|
||||
|
||||
/**
|
||||
* @param remoteDirectory the remoteDirectory to set
|
||||
*/
|
||||
public void setRemoteDirectory(String remoteDirectory) {
|
||||
this.remoteDirectory = remoteDirectory;
|
||||
}
|
||||
|
||||
public String getRemoteDirectory() {
|
||||
return remoteDirectory;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "FileInfo [isDirectory=" + isDirectory() + ", isLink=" + isLink()
|
||||
+ ", Size=" + getSize() + ", ModifiedTime="
|
||||
+ new Date(getModified()) + ", Filename=" + getFilename()
|
||||
+ ", RemoteDirectory=" + getRemoteDirectory() + ", Permissions=" + getPermissions() + "]";
|
||||
}
|
||||
|
||||
public int compareTo(FileInfo<F> o) {
|
||||
return this.getFilename().compareTo(o.getFilename());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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.springframework.integration.file.remote;
|
||||
|
||||
/**
|
||||
* Represents a remote file info - an abstraction over the underlying implementation.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @since 2.1
|
||||
*/
|
||||
public interface FileInfo<F> {
|
||||
|
||||
/**
|
||||
* @return true if the remote file is a directory
|
||||
*/
|
||||
public abstract boolean isDirectory();
|
||||
|
||||
/**
|
||||
* @return true if the remote file is a link
|
||||
*/
|
||||
public abstract boolean isLink();
|
||||
|
||||
/**
|
||||
* @return the size of the remote file
|
||||
*/
|
||||
public abstract long getSize();
|
||||
|
||||
/**
|
||||
* @return the modified time of the remote file
|
||||
*/
|
||||
public abstract long getModified();
|
||||
|
||||
/**
|
||||
* @return the name of the remote file
|
||||
*/
|
||||
public abstract String getFilename();
|
||||
|
||||
/**
|
||||
* @return the remote directory in which the file resides
|
||||
*/
|
||||
public abstract String getRemoteDirectory();
|
||||
|
||||
/**
|
||||
* @return a string representing the permissions of the remote
|
||||
* file (e.g. -rw-r--r--).
|
||||
*/
|
||||
public String getPermissions();
|
||||
|
||||
/**
|
||||
* @return the actual implementation from the underlying
|
||||
* library, more sophisticated access is needed.
|
||||
*/
|
||||
public F getFileInfo();
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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.springframework.integration.file.remote.gateway;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessagingException;
|
||||
import org.springframework.integration.file.FileHeaders;
|
||||
import org.springframework.integration.file.filters.FileListFilter;
|
||||
import org.springframework.integration.file.remote.AbstractFileInfo;
|
||||
import org.springframework.integration.file.remote.session.Session;
|
||||
import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
|
||||
import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Base class for Outbound Gateways that perform remote file operations.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @since 2.1
|
||||
*/
|
||||
public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReplyProducingMessageHandler {
|
||||
|
||||
protected final SessionFactory sessionFactory;
|
||||
|
||||
protected final String command;
|
||||
|
||||
public static final String COMMAND_LS = "ls";
|
||||
|
||||
public static final String COMMAND_GET = "get";
|
||||
|
||||
public static final String COMMAND_RM = "rm";
|
||||
|
||||
protected Set<String> options = new HashSet<String>();
|
||||
|
||||
public static final String OPTION_NAME_ONLY = "-1";
|
||||
|
||||
public static final String OPTION_ALL = "-a";
|
||||
|
||||
public static final String OPTION_NOSORT = "-f";
|
||||
|
||||
public static final String OPTION_SUBDIRS = "-dirs";
|
||||
|
||||
public static final String OPTION_LINKS = "-links";
|
||||
|
||||
public static final String OPTION_PRESERVE_TIMESTAMP = "-P";
|
||||
|
||||
private final ExpressionEvaluatingMessageProcessor<String> processor;
|
||||
|
||||
private volatile String remoteFileSeparator = "/";
|
||||
|
||||
private volatile File localDirectory;
|
||||
|
||||
private volatile boolean autoCreateLocalDirectory = true;
|
||||
|
||||
private volatile String temporaryFileSuffix = ".writing";
|
||||
|
||||
/**
|
||||
* An {@link FileListFilter} that runs against the <emphasis>remote</emphasis> file system view.
|
||||
*/
|
||||
private volatile FileListFilter<F> filter;
|
||||
|
||||
|
||||
public AbstractRemoteFileOutboundGateway(SessionFactory sessionFactory, String command,
|
||||
String expression) {
|
||||
this.sessionFactory = sessionFactory;
|
||||
this.command = command;
|
||||
this.processor = new ExpressionEvaluatingMessageProcessor<String>(
|
||||
new SpelExpressionParser().parseExpression(expression));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param options the options to set
|
||||
*/
|
||||
public void setOptions(String options) {
|
||||
String[] opts = options.split("\\s");
|
||||
for (String opt : opts) {
|
||||
this.options.add(opt.trim());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param remoteFileSeparator the remoteFileSeparator to set
|
||||
*/
|
||||
public void setRemoteFileSeparator(String remoteFileSeparator) {
|
||||
this.remoteFileSeparator = remoteFileSeparator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param localDirectory the localDirectory to set
|
||||
*/
|
||||
public void setLocalDirectory(File localDirectory) {
|
||||
this.localDirectory = localDirectory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param autoCreateLocalDirectory the autoCreateLocalDirectory to set
|
||||
*/
|
||||
public void setAutoCreateLocalDirectory(boolean autoCreateLocalDirectory) {
|
||||
this.autoCreateLocalDirectory = autoCreateLocalDirectory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param temporaryFileSuffix the temporaryFileSuffix to set
|
||||
*/
|
||||
public void setTemporaryFileSuffix(String temporaryFileSuffix) {
|
||||
this.temporaryFileSuffix = temporaryFileSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param filter the filter to set
|
||||
*/
|
||||
public void setFilter(FileListFilter<F> filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInit() {
|
||||
super.onInit();
|
||||
Assert.notNull(this.command, "command must not be null");
|
||||
Assert.isTrue(COMMAND_LS.equals(this.command) || COMMAND_GET.equals(this.command) ||
|
||||
COMMAND_RM.equals(this.command),
|
||||
"command must be one of ls, get, rm");
|
||||
if (COMMAND_RM.equals(this.command)) {
|
||||
Assert.isNull(this.filter, "Filters are not supported with the rm command");
|
||||
} else if (COMMAND_GET.equals(this.command)) {
|
||||
Assert.notNull(this.localDirectory, "localDirectory must not be null");
|
||||
try {
|
||||
if (!this.localDirectory.exists()) {
|
||||
if (this.autoCreateLocalDirectory) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("The '" + this.localDirectory + "' directory doesn't exist; Will create.");
|
||||
}
|
||||
if (!this.localDirectory.mkdirs()) {
|
||||
throw new IOException("Failed to make local directory: " + this.localDirectory);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new FileNotFoundException(this.localDirectory.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new MessagingException(
|
||||
"Failure during initialization of: " + this.getComponentType(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object handleRequestMessage(Message<?> requestMessage) {
|
||||
Session session = this.sessionFactory.getSession();
|
||||
try {
|
||||
if (COMMAND_LS.equals(this.command)) {
|
||||
String dir = this.processor.processMessage(requestMessage);
|
||||
if (!dir.endsWith("/")) {
|
||||
dir += "/";
|
||||
}
|
||||
return MessageBuilder.withPayload(ls(session, dir))
|
||||
.setHeader(FileHeaders.REMOTE_DIRECTORY, dir)
|
||||
.build();
|
||||
} else if (COMMAND_GET.equals(this.command)) {
|
||||
String remoteFilePath = this.processor.processMessage(requestMessage);
|
||||
String remoteFilename = getRemoteFilename(remoteFilePath);
|
||||
String remoteDir = remoteFilePath.substring(0, remoteFilePath.indexOf(remoteFilename));
|
||||
if (remoteDir.length() == 0) {
|
||||
remoteDir = "/";
|
||||
}
|
||||
return MessageBuilder.withPayload(get(session, remoteFilePath, remoteFilename))
|
||||
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
|
||||
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
|
||||
.build();
|
||||
} else if (COMMAND_RM.equals(this.command)) {
|
||||
String remoteFilePath = this.processor.processMessage(requestMessage);
|
||||
String remoteFilename = getRemoteFilename(remoteFilePath);
|
||||
String remoteDir = remoteFilePath.substring(0, remoteFilePath.indexOf(remoteFilename));
|
||||
if (remoteDir.length() == 0) {
|
||||
remoteDir = "/";
|
||||
}
|
||||
return MessageBuilder.withPayload(rm(session, remoteFilePath))
|
||||
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
|
||||
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
|
||||
.build();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new MessagingException(requestMessage, e);
|
||||
} finally {
|
||||
session.close();
|
||||
}
|
||||
}
|
||||
|
||||
protected List<?> ls(Session session, String dir) throws IOException {
|
||||
List<F> lsFiles = new ArrayList<F>();
|
||||
F[] files = session.<F>list(dir);
|
||||
if (!ObjectUtils.isEmpty(files)) {
|
||||
Collection<F> filteredFiles = this.filterFiles(files);
|
||||
for (F file : filteredFiles) {
|
||||
if (file != null) {
|
||||
if (this.options.contains(OPTION_SUBDIRS) || !isDirectory(file)) {
|
||||
lsFiles.add(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
return lsFiles;
|
||||
}
|
||||
if (!this.options.contains(OPTION_LINKS)) {
|
||||
purgeLinks(lsFiles);
|
||||
}
|
||||
if (!this.options.contains(OPTION_ALL)) {
|
||||
purgeDots(lsFiles);
|
||||
}
|
||||
if (this.options.contains(OPTION_NAME_ONLY)) {
|
||||
List<String> results = new ArrayList<String>();
|
||||
for (F file : lsFiles) {
|
||||
results.add(getFilename(file));
|
||||
}
|
||||
if (!this.options.contains(OPTION_NOSORT)) {
|
||||
Collections.sort(results);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
else {
|
||||
List<AbstractFileInfo<F>> canonicalFiles = this.asFileInfoList(lsFiles);
|
||||
for (AbstractFileInfo<F> file : canonicalFiles) {
|
||||
file.setRemoteDirectory(dir);
|
||||
}
|
||||
if (!this.options.contains(OPTION_NOSORT)) {
|
||||
Collections.sort(canonicalFiles);
|
||||
}
|
||||
return canonicalFiles;
|
||||
}
|
||||
}
|
||||
|
||||
protected final List<F> filterFiles(F[] files) {
|
||||
return (this.filter != null) ? this.filter.filterFiles(files) : Arrays.asList(files);
|
||||
}
|
||||
|
||||
protected void purgeLinks(List<F> lsFiles) {
|
||||
Iterator<F> iterator = lsFiles.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
if (this.isLink(iterator.next())) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void purgeDots(List<F> lsFiles) {
|
||||
Iterator<F> iterator = lsFiles.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
if (getFilename(iterator.next()).startsWith(".")) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a remote file to the configured local directory.
|
||||
* @param session
|
||||
* @param remoteFilePath
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
protected File get(Session session, String remoteFilePath, String remoteFilename)
|
||||
throws IOException {
|
||||
F[] files = session.<F>list(remoteFilePath);
|
||||
if (files.length != 1 || isDirectory(files[0]) || isLink(files[0])) {
|
||||
throw new MessagingException(remoteFilePath + " is not a file");
|
||||
}
|
||||
File localFile = new File(this.localDirectory, remoteFilename);
|
||||
if (!localFile.exists()) {
|
||||
String tempFileName = localFile.getAbsolutePath() + this.temporaryFileSuffix;
|
||||
File tempFile = new File(tempFileName);
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(tempFile);
|
||||
try {
|
||||
session.read(remoteFilePath, fileOutputStream);
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (e instanceof RuntimeException){
|
||||
throw (RuntimeException) e;
|
||||
}
|
||||
else {
|
||||
throw new MessagingException("Failure occurred while copying from remote to local directory", e);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
fileOutputStream.close();
|
||||
}
|
||||
catch (Exception ignored2) {
|
||||
}
|
||||
}
|
||||
if (!tempFile.renameTo(localFile)) {
|
||||
throw new MessagingException("Failed to rename local file");
|
||||
}
|
||||
if (this.options.contains(OPTION_PRESERVE_TIMESTAMP)) {
|
||||
localFile.setLastModified(getModified(files[0]));
|
||||
}
|
||||
return localFile;
|
||||
}
|
||||
else {
|
||||
throw new MessagingException("Local file " + localFile + " already exists");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param remoteFilePath
|
||||
* @return
|
||||
*/
|
||||
protected String getRemoteFilename(String remoteFilePath) {
|
||||
String remoteFileName;
|
||||
int index = remoteFilePath.lastIndexOf(this.remoteFileSeparator);
|
||||
if (index < 0) {
|
||||
remoteFileName = remoteFilePath;
|
||||
}
|
||||
else {
|
||||
remoteFileName = remoteFilePath.substring(index + 1);
|
||||
}
|
||||
return remoteFileName;
|
||||
}
|
||||
|
||||
protected boolean rm(Session session, String remoteFilePath)
|
||||
throws IOException {
|
||||
return session.remove(remoteFilePath);
|
||||
}
|
||||
|
||||
abstract protected boolean isDirectory(F file);
|
||||
|
||||
abstract protected boolean isLink(F file);
|
||||
|
||||
abstract protected String getFilename(F file);
|
||||
|
||||
abstract protected long getModified(F file);
|
||||
|
||||
abstract protected List<AbstractFileInfo<F>> asFileInfoList(Collection<F> files);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,498 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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.springframework.integration.file.remote.gateway;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.file.FileHeaders;
|
||||
import org.springframework.integration.file.filters.AbstractSimplePatternFileListFilter;
|
||||
import org.springframework.integration.file.remote.AbstractFileInfo;
|
||||
import org.springframework.integration.file.remote.session.Session;
|
||||
import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
import org.springframework.integration.message.GenericMessage;
|
||||
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @since 2.1
|
||||
*
|
||||
*/
|
||||
public class AbstractRemoteFileOutboundGatewayTests {
|
||||
|
||||
@Test
|
||||
public void testLs() throws Exception {
|
||||
SessionFactory sessionFactory = mock(SessionFactory.class);
|
||||
Session session = mock(Session.class);
|
||||
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway
|
||||
(sessionFactory, "ls", "payload");
|
||||
when(sessionFactory.getSession()).thenReturn(session);
|
||||
TestLsEntry[] files = fileList();
|
||||
when(session.list("testremote/x/")).thenReturn(files);
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<List<TestLsEntry>> out = (Message<List<TestLsEntry>>) gw
|
||||
.handleRequestMessage(new GenericMessage<String>("testremote/x"));
|
||||
assertEquals(2, out.getPayload().size());
|
||||
assertSame(files[1], out.getPayload().get(0)); // sort by default
|
||||
assertSame(files[0], out.getPayload().get(1));
|
||||
assertEquals("testremote/x/",
|
||||
out.getHeaders().get(FileHeaders.REMOTE_DIRECTORY));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
public TestLsEntry[] fileList() {
|
||||
TestLsEntry[] files = new TestLsEntry[6];
|
||||
files[0] = new TestLsEntry("f2", 123, false, false, 1234, "-r--r--r--");
|
||||
files[1] = new TestLsEntry("f1", 1234, false, false, 12345, "-rw-r--r--");
|
||||
files[2] = new TestLsEntry("f3", 12345, true, false, 123456, "drw-r--r--");
|
||||
files[3] = new TestLsEntry("f4", 12346, false, true, 1234567, "lrw-r--r--");
|
||||
files[4] = new TestLsEntry(".f5", 12347, false, false, 12345678, "-rw-r--r--");
|
||||
files[5] = new TestLsEntry(".f6", 12347, true, false, 123456789, "drw-r--r--");
|
||||
return files;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLs_f() throws Exception {
|
||||
SessionFactory sessionFactory = mock(SessionFactory.class);
|
||||
Session session = mock(Session.class);
|
||||
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway
|
||||
(sessionFactory, "ls", "payload");
|
||||
gw.setOptions("-f");
|
||||
when(sessionFactory.getSession()).thenReturn(session);
|
||||
TestLsEntry[] files = fileList();
|
||||
when(session.list("testremote/x/")).thenReturn(files);
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<List<TestLsEntry>> out = (Message<List<TestLsEntry>>) gw
|
||||
.handleRequestMessage(new GenericMessage<String>("testremote/x"));
|
||||
assertEquals(2, out.getPayload().size());
|
||||
assertSame(files[0], out.getPayload().get(0));
|
||||
assertSame(files[1], out.getPayload().get(1));
|
||||
assertEquals("testremote/x/",
|
||||
out.getHeaders().get(FileHeaders.REMOTE_DIRECTORY));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLs_None() throws Exception {
|
||||
SessionFactory sessionFactory = mock(SessionFactory.class);
|
||||
Session session = mock(Session.class);
|
||||
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway
|
||||
(sessionFactory, "ls", "payload");
|
||||
when(sessionFactory.getSession()).thenReturn(session);
|
||||
TestLsEntry[] files = new TestLsEntry[0];
|
||||
when(session.list("testremote/")).thenReturn(files);
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<List<TestLsEntry>> out = (Message<List<TestLsEntry>>) gw
|
||||
.handleRequestMessage(new GenericMessage<String>("testremote"));
|
||||
assertEquals(0, out.getPayload().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLs_1() throws Exception {
|
||||
SessionFactory sessionFactory = mock(SessionFactory.class);
|
||||
Session session = mock(Session.class);
|
||||
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway
|
||||
(sessionFactory, "ls", "payload");
|
||||
gw.setOptions("-1");
|
||||
when(sessionFactory.getSession()).thenReturn(session);
|
||||
TestLsEntry[] files = fileList();
|
||||
when(session.list("testremote/")).thenReturn(files);
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<List<String>> out = (Message<List<String>>) gw
|
||||
.handleRequestMessage(new GenericMessage<String>("testremote"));
|
||||
assertEquals(2, out.getPayload().size());
|
||||
assertEquals("f1", out.getPayload().get(0));
|
||||
assertEquals("f2", out.getPayload().get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLs_1_f() throws Exception { //no sort
|
||||
SessionFactory sessionFactory = mock(SessionFactory.class);
|
||||
Session session = mock(Session.class);
|
||||
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway
|
||||
(sessionFactory, "ls", "payload");
|
||||
gw.setOptions("-1 -f");
|
||||
when(sessionFactory.getSession()).thenReturn(session);
|
||||
TestLsEntry[] files = fileList();
|
||||
when(session.list("testremote/")).thenReturn(files);
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<List<String>> out = (Message<List<String>>) gw
|
||||
.handleRequestMessage(new GenericMessage<String>("testremote"));
|
||||
assertEquals(2, out.getPayload().size());
|
||||
assertEquals("f2", out.getPayload().get(0));
|
||||
assertEquals("f1", out.getPayload().get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLs_1_dirs() throws Exception {
|
||||
SessionFactory sessionFactory = mock(SessionFactory.class);
|
||||
Session session = mock(Session.class);
|
||||
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway
|
||||
(sessionFactory, "ls", "payload");
|
||||
gw.setOptions("-1 -dirs");
|
||||
when(sessionFactory.getSession()).thenReturn(session);
|
||||
TestLsEntry[] files = fileList();
|
||||
when(session.list("testremote/")).thenReturn(files);
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<List<String>> out = (Message<List<String>>) gw
|
||||
.handleRequestMessage(new GenericMessage<String>("testremote"));
|
||||
assertEquals(3, out.getPayload().size());
|
||||
assertEquals("f1", out.getPayload().get(0));
|
||||
assertEquals("f2", out.getPayload().get(1));
|
||||
assertEquals("f3", out.getPayload().get(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLs_1_dirs_links() throws Exception {
|
||||
SessionFactory sessionFactory = mock(SessionFactory.class);
|
||||
Session session = mock(Session.class);
|
||||
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway
|
||||
(sessionFactory, "ls", "payload");
|
||||
gw.setOptions("-1 -dirs -links");
|
||||
when(sessionFactory.getSession()).thenReturn(session);
|
||||
TestLsEntry[] files = fileList();
|
||||
when(session.list("testremote/")).thenReturn(files);
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<List<String>> out = (Message<List<String>>) gw
|
||||
.handleRequestMessage(new GenericMessage<String>("testremote"));
|
||||
assertEquals(4, out.getPayload().size());
|
||||
assertEquals("f1", out.getPayload().get(0));
|
||||
assertEquals("f2", out.getPayload().get(1));
|
||||
assertEquals("f3", out.getPayload().get(2));
|
||||
assertEquals("f4", out.getPayload().get(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLs_1_a_f_dirs_links() throws Exception {
|
||||
SessionFactory sessionFactory = mock(SessionFactory.class);
|
||||
Session session = mock(Session.class);
|
||||
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway
|
||||
(sessionFactory, "ls", "payload");
|
||||
gw.setOptions("-1 -a -f -dirs -links");
|
||||
when(sessionFactory.getSession()).thenReturn(session);
|
||||
TestLsEntry[] files = fileList();
|
||||
when(session.list("testremote/")).thenReturn(files);
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<List<String>> out = (Message<List<String>>) gw
|
||||
.handleRequestMessage(new GenericMessage<String>("testremote"));
|
||||
assertEquals(6, out.getPayload().size());
|
||||
assertEquals("f2", out.getPayload().get(0));
|
||||
assertEquals("f1", out.getPayload().get(1));
|
||||
assertEquals("f3", out.getPayload().get(2));
|
||||
assertEquals("f4", out.getPayload().get(3));
|
||||
assertEquals(".f5", out.getPayload().get(4));
|
||||
assertEquals(".f6", out.getPayload().get(5));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLs_1_a_f_dirs_links_filtered() throws Exception {
|
||||
SessionFactory sessionFactory = mock(SessionFactory.class);
|
||||
Session session = mock(Session.class);
|
||||
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway
|
||||
(sessionFactory, "ls", "payload");
|
||||
gw.setOptions("-1 -a -f -dirs -links");
|
||||
gw.setFilter(new TestPatternFilter("*4"));
|
||||
when(sessionFactory.getSession()).thenReturn(session);
|
||||
TestLsEntry[] files = fileList();
|
||||
when(session.list("testremote/")).thenReturn(files);
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<List<String>> out = (Message<List<String>>) gw
|
||||
.handleRequestMessage(new GenericMessage<String>("testremote"));
|
||||
assertEquals(1, out.getPayload().size());
|
||||
assertEquals("f4", out.getPayload().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGet() throws Exception {
|
||||
SessionFactory sessionFactory = mock(SessionFactory.class);
|
||||
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway
|
||||
(sessionFactory, "get", "payload");
|
||||
gw.setLocalDirectory(new File("/tmp"));
|
||||
gw.afterPropertiesSet();
|
||||
new File("/tmp/f1").delete();
|
||||
when(sessionFactory.getSession()).thenReturn(new Session(){
|
||||
private boolean open = true;
|
||||
public boolean remove(String path) throws IOException {
|
||||
return false;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
public TestLsEntry[] list(String path) throws IOException {
|
||||
return new TestLsEntry[] {
|
||||
new TestLsEntry("f1", 1234, false, false, 12345, "-rw-r--r--")
|
||||
};
|
||||
}
|
||||
public void read(String source, OutputStream outputStream)
|
||||
throws IOException {
|
||||
outputStream.write("testfile".getBytes());
|
||||
}
|
||||
public void write(InputStream inputStream, String destination)
|
||||
throws IOException {
|
||||
}
|
||||
public void mkdir(String directory) throws IOException {
|
||||
}
|
||||
public void rename(String pathFrom, String pathTo)
|
||||
throws IOException {
|
||||
}
|
||||
public void close() {
|
||||
open = false;
|
||||
}
|
||||
public boolean isOpen() {
|
||||
return open;
|
||||
} });
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<File> out = (Message<File>) gw.handleRequestMessage(new GenericMessage<String>("f1"));
|
||||
File outFile = new File("/tmp/f1");
|
||||
assertEquals(outFile, out.getPayload());
|
||||
assertTrue(outFile.exists());
|
||||
outFile.delete();
|
||||
assertEquals("/",
|
||||
out.getHeaders().get(FileHeaders.REMOTE_DIRECTORY));
|
||||
assertEquals("f1",
|
||||
out.getHeaders().get(FileHeaders.REMOTE_FILE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGet_P() throws Exception {
|
||||
SessionFactory sessionFactory = mock(SessionFactory.class);
|
||||
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway
|
||||
(sessionFactory, "get", "payload");
|
||||
gw.setLocalDirectory(new File("/tmp"));
|
||||
gw.setOptions("-P");
|
||||
gw.afterPropertiesSet();
|
||||
new File("/tmp/f1").delete();
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.add(Calendar.MONTH, -1);
|
||||
final Date modified = cal.getTime();
|
||||
when(sessionFactory.getSession()).thenReturn(new Session(){
|
||||
private boolean open = true;
|
||||
public boolean remove(String path) throws IOException {
|
||||
return false;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
public TestLsEntry[] list(String path) throws IOException {
|
||||
return new TestLsEntry[] {
|
||||
new TestLsEntry("f1", 1234, false, false, modified.getTime(), "-rw-r--r--")
|
||||
};
|
||||
}
|
||||
public void read(String source, OutputStream outputStream)
|
||||
throws IOException {
|
||||
outputStream.write("testfile".getBytes());
|
||||
}
|
||||
public void write(InputStream inputStream, String destination)
|
||||
throws IOException {
|
||||
}
|
||||
public void mkdir(String directory) throws IOException {
|
||||
}
|
||||
public void rename(String pathFrom, String pathTo)
|
||||
throws IOException {
|
||||
}
|
||||
public void close() {
|
||||
open = false;
|
||||
}
|
||||
public boolean isOpen() {
|
||||
return open;
|
||||
} });
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<File> out = (Message<File>) gw.handleRequestMessage(new GenericMessage<String>("x/f1"));
|
||||
File outFile = new File("/tmp/f1");
|
||||
assertEquals(outFile, out.getPayload());
|
||||
assertTrue(outFile.exists());
|
||||
assertEquals(modified.getTime() / 1000 * 1000, outFile.lastModified());
|
||||
outFile.delete();
|
||||
assertEquals("x/",
|
||||
out.getHeaders().get(FileHeaders.REMOTE_DIRECTORY));
|
||||
assertEquals("f1",
|
||||
out.getHeaders().get(FileHeaders.REMOTE_FILE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGet_create_dir() throws Exception {
|
||||
new File("/tmp/x/f1").delete();
|
||||
new File("/tmp/x").delete();
|
||||
SessionFactory sessionFactory = mock(SessionFactory.class);
|
||||
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway
|
||||
(sessionFactory, "get", "payload");
|
||||
gw.setLocalDirectory(new File("/tmp/x"));
|
||||
gw.afterPropertiesSet();
|
||||
when(sessionFactory.getSession()).thenReturn(new Session(){
|
||||
private boolean open = true;
|
||||
public boolean remove(String path) throws IOException {
|
||||
return false;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
public TestLsEntry[] list(String path) throws IOException {
|
||||
return new TestLsEntry[] {
|
||||
new TestLsEntry("f1", 1234, false, false, 12345, "-rw-r--r--")
|
||||
};
|
||||
}
|
||||
public void read(String source, OutputStream outputStream)
|
||||
throws IOException {
|
||||
outputStream.write("testfile".getBytes());
|
||||
}
|
||||
public void write(InputStream inputStream, String destination)
|
||||
throws IOException {
|
||||
}
|
||||
public void mkdir(String directory) throws IOException {
|
||||
}
|
||||
public void rename(String pathFrom, String pathTo)
|
||||
throws IOException {
|
||||
}
|
||||
public void close() {
|
||||
open = false;
|
||||
}
|
||||
public boolean isOpen() {
|
||||
return open;
|
||||
} });
|
||||
gw.handleRequestMessage(new GenericMessage<String>("f1"));
|
||||
File out = new File("/tmp/x/f1");
|
||||
assertTrue(out.exists());
|
||||
out.delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRm() throws Exception {
|
||||
SessionFactory sessionFactory = mock(SessionFactory.class);
|
||||
Session session = mock(Session.class);
|
||||
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway
|
||||
(sessionFactory, "rm", "payload");
|
||||
when(sessionFactory.getSession()).thenReturn(session);
|
||||
when(session.remove("testremote/x/f1")).thenReturn(Boolean.TRUE);
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<Boolean> out = (Message<Boolean>) gw
|
||||
.handleRequestMessage(new GenericMessage<String>("testremote/x/f1"));
|
||||
assertEquals(Boolean.TRUE, out.getPayload());
|
||||
verify(session).remove("testremote/x/f1");
|
||||
assertEquals("testremote/x/",
|
||||
out.getHeaders().get(FileHeaders.REMOTE_DIRECTORY));
|
||||
assertEquals("f1",
|
||||
out.getHeaders().get(FileHeaders.REMOTE_FILE));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TestRemoteFileOutboundGateway extends AbstractRemoteFileOutboundGateway<TestLsEntry> {
|
||||
|
||||
public TestRemoteFileOutboundGateway(SessionFactory sessionFactory,
|
||||
String command, String expression) {
|
||||
super(sessionFactory, command, expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDirectory(TestLsEntry file) {
|
||||
return file.isDirectory();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isLink(TestLsEntry file) {
|
||||
return file.isLink();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getFilename(TestLsEntry file) {
|
||||
return file.getFilename();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getModified(TestLsEntry file) {
|
||||
return file.getModified();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<AbstractFileInfo<TestLsEntry>> asFileInfoList(
|
||||
Collection<TestLsEntry> files) {
|
||||
return new ArrayList<AbstractFileInfo<TestLsEntry>>(files);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TestLsEntry extends AbstractFileInfo<TestLsEntry> {
|
||||
|
||||
private String filename;
|
||||
private int size;
|
||||
private boolean dir;
|
||||
private boolean link;
|
||||
private long modified;
|
||||
private String permissions;
|
||||
|
||||
public TestLsEntry(String filename, int size, boolean dir, boolean link,
|
||||
long modified, String permissions) {
|
||||
this.filename = filename;
|
||||
this.size = size;
|
||||
this.dir = dir;
|
||||
this.link = link;
|
||||
this.modified = modified;
|
||||
this.permissions = permissions;
|
||||
}
|
||||
|
||||
public boolean isDirectory() {
|
||||
return this.dir;
|
||||
}
|
||||
|
||||
public long getModified() {
|
||||
return this.modified;
|
||||
}
|
||||
|
||||
public String getFilename() {
|
||||
return this.filename;
|
||||
}
|
||||
|
||||
public boolean isLink() {
|
||||
return this.link;
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
public String getPermissions() {
|
||||
return this.permissions;
|
||||
}
|
||||
|
||||
public TestLsEntry getFileInfo() {
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TestPatternFilter extends AbstractSimplePatternFileListFilter<TestLsEntry>{
|
||||
|
||||
public TestPatternFilter(String path) {
|
||||
super(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getFilename(TestLsEntry file) {
|
||||
return file.getFilename();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -33,6 +33,7 @@ public class FtpNamespaceHandler extends AbstractIntegrationNamespaceHandler {
|
||||
public void init() {
|
||||
registerBeanDefinitionParser("inbound-channel-adapter", new FtpInboundChannelAdapterParser());
|
||||
registerBeanDefinitionParser("outbound-channel-adapter", new RemoteFileOutboundChannelAdapterParser());
|
||||
registerBeanDefinitionParser("outbound-gateway", new FtpOutboundGatewayParser());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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.springframework.integration.ftp.config;
|
||||
|
||||
import org.springframework.integration.file.config.AbstractRemoteFileOutboundGatewayParser;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @since 2.1
|
||||
*
|
||||
*/
|
||||
public class FtpOutboundGatewayParser extends AbstractRemoteFileOutboundGatewayParser {
|
||||
|
||||
private static final String BASE_PACKAGE = "org.springframework.integration.ftp";
|
||||
|
||||
public String getGatewayClassName() {
|
||||
return BASE_PACKAGE + ".gateway.FtpOutboundGateway";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSimplePatternFileListFilterClassname() {
|
||||
return BASE_PACKAGE + ".filters.FtpSimplePatternFileListFilter";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getRegexPatternFileListFilterClassname() {
|
||||
return BASE_PACKAGE + ".filters.FtpRegexPatternFileListFilter";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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.springframework.integration.ftp.gateway;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.net.ftp.FTPFile;
|
||||
import org.springframework.integration.file.remote.AbstractFileInfo;
|
||||
import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOutboundGateway;
|
||||
import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
import org.springframework.integration.ftp.session.FtpFileInfo;
|
||||
|
||||
/**
|
||||
* Outbound Gateway for performing remote file operations via FTP/FTPS.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @since 2.1
|
||||
*/
|
||||
public class FtpOutboundGateway extends AbstractRemoteFileOutboundGateway<FTPFile> {
|
||||
|
||||
public FtpOutboundGateway(SessionFactory sessionFactory, String command,
|
||||
String expression) {
|
||||
super(sessionFactory, command, expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDirectory(FTPFile file) {
|
||||
return file.isDirectory();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isLink(FTPFile file) {
|
||||
return file.isSymbolicLink();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getFilename(FTPFile file) {
|
||||
return file.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getModified(FTPFile file) {
|
||||
return file.getTimestamp().getTimeInMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<AbstractFileInfo<FTPFile>> asFileInfoList(Collection<FTPFile> files) {
|
||||
List<AbstractFileInfo<FTPFile>> canonicalFiles = new ArrayList<AbstractFileInfo<FTPFile>>();
|
||||
for (FTPFile file : files) {
|
||||
canonicalFiles.add(new FtpFileInfo(file));
|
||||
}
|
||||
return canonicalFiles;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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.springframework.integration.ftp.session;
|
||||
|
||||
import org.apache.commons.net.ftp.FTPFile;
|
||||
import org.springframework.integration.file.remote.AbstractFileInfo;
|
||||
import org.springframework.integration.file.remote.FileInfo;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link FileInfo} implementation for FTP/FTPS.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @since 2.1
|
||||
*/
|
||||
public class FtpFileInfo extends AbstractFileInfo<FTPFile> {
|
||||
|
||||
private final FTPFile ftpFile;
|
||||
|
||||
public FtpFileInfo(FTPFile ftpFile) {
|
||||
Assert.notNull(ftpFile, "FTPFile must not be null");
|
||||
this.ftpFile = ftpFile;
|
||||
}
|
||||
|
||||
public boolean isDirectory() {
|
||||
return this.ftpFile.isDirectory();
|
||||
}
|
||||
|
||||
public boolean isLink() {
|
||||
return this.ftpFile.isSymbolicLink();
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return this.ftpFile.getSize();
|
||||
}
|
||||
|
||||
public long getModified() {
|
||||
return this.ftpFile.getTimestamp().getTimeInMillis();
|
||||
}
|
||||
|
||||
public String getFilename() {
|
||||
return this.ftpFile.getName();
|
||||
}
|
||||
|
||||
public String getPermissions() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
if (this.ftpFile.isDirectory()) {
|
||||
sb.append("d");
|
||||
} else if (this.ftpFile.isSymbolicLink()) {
|
||||
sb.append("l");
|
||||
} else {
|
||||
sb.append("-");
|
||||
}
|
||||
if (this.ftpFile.hasPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION)) {
|
||||
sb.append("r");
|
||||
} else {
|
||||
sb.append("-");
|
||||
}
|
||||
if (this.ftpFile.hasPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION)) {
|
||||
sb.append("w");
|
||||
} else {
|
||||
sb.append("-");
|
||||
}
|
||||
if (this.ftpFile.hasPermission(FTPFile.USER_ACCESS, FTPFile.EXECUTE_PERMISSION)) {
|
||||
sb.append("x");
|
||||
} else {
|
||||
sb.append("-");
|
||||
}
|
||||
if (this.ftpFile.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.READ_PERMISSION)) {
|
||||
sb.append("r");
|
||||
} else {
|
||||
sb.append("-");
|
||||
}
|
||||
if (this.ftpFile.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.WRITE_PERMISSION)) {
|
||||
sb.append("w");
|
||||
} else {
|
||||
sb.append("-");
|
||||
}
|
||||
if (this.ftpFile.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.EXECUTE_PERMISSION)) {
|
||||
sb.append("x");
|
||||
} else {
|
||||
sb.append("-");
|
||||
}
|
||||
if (this.ftpFile.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.READ_PERMISSION)) {
|
||||
sb.append("r");
|
||||
} else {
|
||||
sb.append("-");
|
||||
}
|
||||
if (this.ftpFile.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.WRITE_PERMISSION)) {
|
||||
sb.append("w");
|
||||
} else {
|
||||
sb.append("-");
|
||||
}
|
||||
if (this.ftpFile.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.EXECUTE_PERMISSION)) {
|
||||
sb.append("x");
|
||||
} else {
|
||||
sb.append("-");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public FTPFile getFileInfo() {
|
||||
return this.ftpFile;
|
||||
}
|
||||
}
|
||||
@@ -184,7 +184,162 @@ endpoint itself is a Polling Consumer for a channel with a queue.
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
|
||||
<xsd:element name="outbound-gateway">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Builds an outbound gateway used to issue FTP commands.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="id" type="xsd:string"/>
|
||||
<xsd:attribute name="session-factory" type="xsd:string" use="required">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.integration.ftp.session.AbstractFtpSessionFactory"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
Reference to a [org.springframework.integration.ftp.session.AbstractFtpSessionFactory] bean.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="cache-sessions" type="xsd:boolean" default="true">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Specify whether the Sessions should be cached. Default is true.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="command" use="required" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
FTP command - ls, get or rm
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="command-options" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
FTP command options; for ls, -1 means just return the file names (otherwise file
|
||||
metadata is returned, -dirs means include directories (not included by default),
|
||||
-links means include links (not included by default); for get, -P means preserve
|
||||
timestamp from remote file.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="expression" use="required" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
SpEL expression representing the path in the command (e.g. ls path to
|
||||
list the files in directory path).
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="auto-startup" type="xsd:string" default="true">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Lifecycle attribute signaling if this component should be started during Application Context startup.
|
||||
Default is 'true'
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="temporary-file-suffix" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Extension used when downloading files. We change it right after we know it's downloaded.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="request-channel" use="required" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.integration.core.MessageChannel"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
Identifies the request channel attached to this gateway.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="reply-channel" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.integration.core.MessageChannel"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
Identifies the reply channel attached to this gateway.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="filter" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.integration.file.filters.FileListFilter"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
Allows you to specify a reference to
|
||||
[org.springframework.integration.file.filters.FileListFilter] bean.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="filename-pattern" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to provide file name pattern to determine the file names retrieved by the ls command
|
||||
and is based on simple pattern matching algorithm (e.g., "*.txt, fo*.txt" etc.)
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="filename-regex" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to provide Regular Expression to determine the file names retrieved by the ls command.
|
||||
(e.g., "f[o]+\.txt" etc.)
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="remote-file-separator" type="xsd:string" default="/">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to provide remote file/directory separator character. DEFAULT: '/'
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="local-directory" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Identifies directory path (e.g., "/local/mytransfers") where file will be transferred TO.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="auto-create-local-directory" type="xsd:boolean">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Tells this adapter if local directory must be auto-created if it doesn''t exist. Default is TRUE.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="order" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Specifies the order for invocation when this endpoint is connected as a
|
||||
subscriber to a channel. This is particularly relevant when that channel
|
||||
is using a "failover" dispatching strategy, or when a failure in the delivery to one subscriber should signal that
|
||||
the message should not be sent to subscribers with a higher 'order' attribute. It has no effect when this
|
||||
endpoint itself is a Polling Consumer for a channel with a queue.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:complexType name="base-ftp-adapter-type">
|
||||
<xsd:attribute name="id" type="xsd:string"/>
|
||||
<xsd:attribute name="session-factory" type="xsd:string" use="required">
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:int="http://www.springframework.org/schema/integration"
|
||||
xmlns:int-ftp="http://www.springframework.org/schema/integration/ftp"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/integration/ftp http://www.springframework.org/schema/integration/ftp/spring-integration-ftp-2.1.xsd
|
||||
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<bean id="sf" class="org.mockito.Mockito" factory-method="mock">
|
||||
<constructor-arg value="org.springframework.integration.file.remote.session.SessionFactory"/>
|
||||
</bean>
|
||||
|
||||
<int-ftp:outbound-gateway id="gateway1"
|
||||
local-directory="/tmp"
|
||||
session-factory="sf"
|
||||
request-channel="inbound1"
|
||||
reply-channel="outbound"
|
||||
auto-create-local-directory="false"
|
||||
auto-startup="false"
|
||||
cache-sessions="false"
|
||||
filename-pattern="*"
|
||||
remote-file-separator="X"
|
||||
command="ls"
|
||||
command-options="-1 -f"
|
||||
expression="payload"
|
||||
order="1"
|
||||
/>
|
||||
|
||||
<int-ftp:outbound-gateway id="gateway2"
|
||||
local-directory="/tmp"
|
||||
session-factory="sf"
|
||||
request-channel="inbound2"
|
||||
reply-channel="outbound"
|
||||
auto-create-local-directory="false"
|
||||
auto-startup="false"
|
||||
cache-sessions="true"
|
||||
filename-pattern="*"
|
||||
remote-file-separator="X"
|
||||
command="get"
|
||||
command-options="-P"
|
||||
expression="payload"
|
||||
order="2"
|
||||
/>
|
||||
|
||||
<int:channel id="outbound"/>
|
||||
|
||||
</beans>
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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.springframework.integration.ftp.config;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.integration.endpoint.AbstractEndpoint;
|
||||
import org.springframework.integration.file.remote.session.CachingSessionFactory;
|
||||
import org.springframework.integration.ftp.gateway.FtpOutboundGateway;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @since 2.1
|
||||
*
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class FtpOutboundGatewayParserTests {
|
||||
|
||||
@Autowired
|
||||
AbstractEndpoint gateway1;
|
||||
|
||||
@Autowired
|
||||
AbstractEndpoint gateway2;
|
||||
|
||||
@Test
|
||||
public void testGateway1() {
|
||||
FtpOutboundGateway gateway = TestUtils.getPropertyValue(gateway1,
|
||||
"handler", FtpOutboundGateway.class);
|
||||
assertEquals("X", TestUtils.getPropertyValue(gateway, "remoteFileSeparator"));
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "sessionFactory"));
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "outputChannel"));
|
||||
assertEquals(new File("/tmp"), TestUtils.getPropertyValue(gateway, "localDirectory"));
|
||||
assertFalse((Boolean) TestUtils.getPropertyValue(gateway, "autoCreateLocalDirectory"));
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "filter"));
|
||||
assertEquals("ls", TestUtils.getPropertyValue(gateway, "command"));
|
||||
@SuppressWarnings("unchecked")
|
||||
Set<String> options = TestUtils.getPropertyValue(gateway, "options", Set.class);
|
||||
assertTrue(options.contains("-1"));
|
||||
assertTrue(options.contains("-f"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGateway2() {
|
||||
FtpOutboundGateway gateway = TestUtils.getPropertyValue(gateway2,
|
||||
"handler", FtpOutboundGateway.class);
|
||||
assertEquals("X", TestUtils.getPropertyValue(gateway, "remoteFileSeparator"));
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "sessionFactory"));
|
||||
assertTrue(TestUtils.getPropertyValue(gateway, "sessionFactory") instanceof CachingSessionFactory);
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "outputChannel"));
|
||||
assertEquals(new File("/tmp"), TestUtils.getPropertyValue(gateway, "localDirectory"));
|
||||
assertFalse((Boolean) TestUtils.getPropertyValue(gateway, "autoCreateLocalDirectory"));
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "filter"));
|
||||
assertEquals("get", TestUtils.getPropertyValue(gateway, "command"));
|
||||
@SuppressWarnings("unchecked")
|
||||
Set<String> options = TestUtils.getPropertyValue(gateway, "options", Set.class);
|
||||
assertTrue(options.contains("-P"));
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ public class SftpNamespaceHandler extends AbstractIntegrationNamespaceHandler {
|
||||
public void init() {
|
||||
registerBeanDefinitionParser("inbound-channel-adapter", new SftpInboundChannelAdapterParser());
|
||||
registerBeanDefinitionParser("outbound-channel-adapter", new RemoteFileOutboundChannelAdapterParser());
|
||||
registerBeanDefinitionParser("outbound-gateway", new SftpOutboundGatewayParser());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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.springframework.integration.sftp.config;
|
||||
|
||||
import org.springframework.integration.file.config.AbstractRemoteFileOutboundGatewayParser;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @since 2.1
|
||||
*
|
||||
*/
|
||||
public class SftpOutboundGatewayParser extends AbstractRemoteFileOutboundGatewayParser {
|
||||
|
||||
private static final String BASE_PACKAGE = "org.springframework.integration.sftp";
|
||||
|
||||
public String getGatewayClassName() {
|
||||
return BASE_PACKAGE + ".gateway.SftpOutboundGateway";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSimplePatternFileListFilterClassname() {
|
||||
return BASE_PACKAGE + ".filters.SftpSimplePatternFileListFilter";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getRegexPatternFileListFilterClassname() {
|
||||
return BASE_PACKAGE + ".filters.SftpRegexPatternFileListFilter";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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.springframework.integration.sftp.gateway;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.integration.file.remote.AbstractFileInfo;
|
||||
import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOutboundGateway;
|
||||
import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
import org.springframework.integration.sftp.session.SftpFileInfo;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
|
||||
/**
|
||||
* Outbound Gateway for performing remote file operations via SFTP.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @since 2.1
|
||||
*/
|
||||
public class SftpOutboundGateway extends AbstractRemoteFileOutboundGateway<LsEntry> {
|
||||
|
||||
/**
|
||||
* @param sessionFactory
|
||||
* @param command
|
||||
* @param options
|
||||
* @param expression
|
||||
*/
|
||||
public SftpOutboundGateway(SessionFactory sessionFactory, String command, String expression) {
|
||||
super(sessionFactory, command, expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDirectory(LsEntry file) {
|
||||
return file.getAttrs().isDir();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isLink(LsEntry file) {
|
||||
return file.getAttrs().isLink();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getFilename(LsEntry file) {
|
||||
return file.getFilename();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<AbstractFileInfo<LsEntry>> asFileInfoList(Collection<LsEntry> files) {
|
||||
List<AbstractFileInfo<LsEntry>> canonicalFiles = new ArrayList<AbstractFileInfo<LsEntry>>();
|
||||
for (LsEntry file : files) {
|
||||
canonicalFiles.add(new SftpFileInfo(file));
|
||||
}
|
||||
return canonicalFiles;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getModified(LsEntry file) {
|
||||
return ((long)file.getAttrs().getMTime()) * 1000;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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.springframework.integration.sftp.session;
|
||||
|
||||
import org.springframework.integration.file.remote.AbstractFileInfo;
|
||||
import org.springframework.integration.file.remote.FileInfo;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
import com.jcraft.jsch.SftpATTRS;
|
||||
|
||||
/**
|
||||
* A {@link FileInfo} implementation for SFTP.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @since 2.1
|
||||
*/
|
||||
public class SftpFileInfo extends AbstractFileInfo<LsEntry> {
|
||||
|
||||
private final LsEntry lsEntry;
|
||||
|
||||
private final SftpATTRS attrs;
|
||||
|
||||
|
||||
public SftpFileInfo(LsEntry lsEntry) {
|
||||
Assert.notNull("LsEntry must not be null");
|
||||
this.lsEntry = lsEntry;
|
||||
this.attrs = lsEntry.getAttrs();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @see com.jcraft.jsch.SftpATTRS#isDir()
|
||||
*/
|
||||
public boolean isDirectory() {
|
||||
return this.attrs.isDir();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @see com.jcraft.jsch.SftpATTRS#isLink()
|
||||
*/
|
||||
public boolean isLink() {
|
||||
return this.attrs.isLink();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @see com.jcraft.jsch.SftpATTRS#getSize()
|
||||
*/
|
||||
public long getSize() {
|
||||
return this.attrs.getSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @see com.jcraft.jsch.SftpATTRS#getMTime()
|
||||
*/
|
||||
public long getModified() {
|
||||
return this.attrs.getMTime() * 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @see com.jcraft.jsch.ChannelSftp.LsEntry#getFilename()
|
||||
*/
|
||||
public String getFilename() {
|
||||
return this.lsEntry.getFilename();
|
||||
}
|
||||
|
||||
public String getPermissions() {
|
||||
return this.attrs.getPermissionsString();
|
||||
}
|
||||
|
||||
public LsEntry getFileInfo() {
|
||||
return this.lsEntry;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -269,4 +269,159 @@ endpoint itself is a Polling Consumer for a channel with a queue.
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:element name="outbound-gateway">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Builds an outbound gateway used to issue sftp commands.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="id" type="xsd:string"/>
|
||||
<xsd:attribute name="session-factory" type="xsd:string" use="required">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.integration.sftp.session.SftpSessionFactory"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
Reference to a [org.springframework.integration.sftp.session.SftpSessionFactory] bean.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="cache-sessions" type="xsd:boolean" default="true">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Specify whether the Sessions should be cached. Default is true.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="command" use="required" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
sftp command - ls, get or rm
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="command-options" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
sftp command options; for ls, -1 means just return the file names (otherwise file
|
||||
metadata is returned, -dirs means include directories (not included by default),
|
||||
-links means include links (not included by default); for get, -P means preserve
|
||||
timestamp from remote file.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="expression" use="required" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
SpEL expression representing the path in the command (e.g. ls path to
|
||||
list the files in directory path).
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="auto-startup" type="xsd:string" default="true">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Lifecycle attribute signaling if this component should be started during Application Context startup.
|
||||
Default is 'true'
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="temporary-file-suffix" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Extension used when downloading files. We change it right after we know it's downloaded.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="request-channel" use="required" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.integration.core.MessageChannel"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
Identifies the request channel attached to this gateway.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="reply-channel" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.integration.core.MessageChannel"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
Identifies the reply channel attached to this gateway.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="filter" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.integration.file.filters.FileListFilter"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
Allows you to specify a reference to
|
||||
[org.springframework.integration.file.filters.FileListFilter] bean.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="filename-pattern" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to provide file name pattern to determine the file names retrieved by the ls command
|
||||
and is based on simple pattern matching algorithm (e.g., "*.txt, fo*.txt" etc.)
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="filename-regex" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to provide Regular Expression to determine the file names retrieved by the ls command.
|
||||
(e.g., "f[o]+\.txt" etc.)
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="remote-file-separator" type="xsd:string" default="/">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to provide remote file/directory separator character. DEFAULT: '/'
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="local-directory" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Identifies directory path (e.g., "/local/mytransfers") where file will be transferred TO.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="auto-create-local-directory" type="xsd:boolean">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Tells this adapter if local directory must be auto-created if it doesn''t exist. Default is TRUE.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="order" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Specifies the order for invocation when this endpoint is connected as a
|
||||
subscriber to a channel. This is particularly relevant when that channel
|
||||
is using a "failover" dispatching strategy, or when a failure in the delivery to one subscriber should signal that
|
||||
the message should not be sent to subscribers with a higher 'order' attribute. It has no effect when this
|
||||
endpoint itself is a Polling Consumer for a channel with a queue.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
</xsd:schema>
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:int-sftp="http://www.springframework.org/schema/integration/sftp"
|
||||
xmlns:int="http://www.springframework.org/schema/integration"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration/sftp http://www.springframework.org/schema/integration/sftp/spring-integration-sftp-2.1.xsd">
|
||||
|
||||
<bean id="sf" class="org.mockito.Mockito" factory-method="mock">
|
||||
<constructor-arg value="org.springframework.integration.file.remote.session.SessionFactory"/>
|
||||
</bean>
|
||||
|
||||
<int-sftp:outbound-gateway id="gateway1"
|
||||
local-directory="/tmp"
|
||||
session-factory="sf"
|
||||
request-channel="inbound1"
|
||||
reply-channel="outbound"
|
||||
auto-create-local-directory="false"
|
||||
auto-startup="false"
|
||||
cache-sessions="false"
|
||||
filename-pattern="*"
|
||||
remote-file-separator="X"
|
||||
command="ls"
|
||||
command-options="-1 -f"
|
||||
expression="payload"
|
||||
order="1"
|
||||
/>
|
||||
|
||||
<int-sftp:outbound-gateway id="gateway2"
|
||||
local-directory="/tmp"
|
||||
session-factory="sf"
|
||||
request-channel="inbound2"
|
||||
reply-channel="outbound"
|
||||
auto-create-local-directory="false"
|
||||
auto-startup="false"
|
||||
cache-sessions="true"
|
||||
filename-pattern="*"
|
||||
remote-file-separator="X"
|
||||
command="get"
|
||||
command-options="-P"
|
||||
expression="payload"
|
||||
order="2"
|
||||
/>
|
||||
|
||||
<int:channel id="outbound"/>
|
||||
|
||||
</beans>
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* 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.springframework.integration.sftp.config;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.integration.endpoint.AbstractEndpoint;
|
||||
import org.springframework.integration.file.remote.session.CachingSessionFactory;
|
||||
import org.springframework.integration.sftp.gateway.SftpOutboundGateway;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @since 2.1
|
||||
*
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class SftpOutboundGatewayParserTests {
|
||||
|
||||
@Autowired
|
||||
AbstractEndpoint gateway1;
|
||||
|
||||
@Autowired
|
||||
AbstractEndpoint gateway2;
|
||||
|
||||
@Test
|
||||
public void testGateway1() {
|
||||
SftpOutboundGateway gateway = TestUtils.getPropertyValue(gateway1,
|
||||
"handler", SftpOutboundGateway.class);
|
||||
assertEquals("X", TestUtils.getPropertyValue(gateway, "remoteFileSeparator"));
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "sessionFactory"));
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "outputChannel"));
|
||||
assertEquals(new File("/tmp"), TestUtils.getPropertyValue(gateway, "localDirectory"));
|
||||
assertFalse((Boolean) TestUtils.getPropertyValue(gateway, "autoCreateLocalDirectory"));
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "filter"));
|
||||
assertEquals("ls", TestUtils.getPropertyValue(gateway, "command"));
|
||||
@SuppressWarnings("unchecked")
|
||||
Set<String> options = TestUtils.getPropertyValue(gateway, "options", Set.class);
|
||||
assertTrue(options.contains("-1"));
|
||||
assertTrue(options.contains("-f"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGateway2() {
|
||||
SftpOutboundGateway gateway = TestUtils.getPropertyValue(gateway2,
|
||||
"handler", SftpOutboundGateway.class);
|
||||
assertEquals("X", TestUtils.getPropertyValue(gateway, "remoteFileSeparator"));
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "sessionFactory"));
|
||||
assertTrue(TestUtils.getPropertyValue(gateway, "sessionFactory") instanceof CachingSessionFactory);
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "outputChannel"));
|
||||
assertEquals(new File("/tmp"), TestUtils.getPropertyValue(gateway, "localDirectory"));
|
||||
assertFalse((Boolean) TestUtils.getPropertyValue(gateway, "autoCreateLocalDirectory"));
|
||||
assertNotNull(TestUtils.getPropertyValue(gateway, "filter"));
|
||||
assertEquals("get", TestUtils.getPropertyValue(gateway, "command"));
|
||||
@SuppressWarnings("unchecked")
|
||||
Set<String> options = TestUtils.getPropertyValue(gateway, "options", Set.class);
|
||||
assertTrue(options.contains("-P"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user