RSS
 

Archive for the ‘Spring’ Category

MultiResourceItemReader – “No resources to read”

12 Dec

By default Spring Batch MultiResourceItemReader ignores missing resources silently:

INFO  [SimpleStepHandler] Executing step: [someStep]
WARN  [MultiResourceItemReader] No resources to read
INFO  [SimpleStepHandler] Executing step: [someOtherStep]

Sounds familiar?
Well, sometimes this behavior is wanted. But sometimes it is not and we would like to make it fail:

public class MyMultiResourceItemReader<T> extends MultiResourceItemReader<T>
{
    private static final Log logger = LogFactory.getLog( MyMultiResourceItemReader.class );

    private boolean failOnNoResources = true;
    public  boolean failOnNoResources () { return this.failOnNoResources; }
    public  void    setFailOnNoResources ( boolean failOnNoResources ) { this.failOnNoResources = failOnNoResources; }

    @Override
    public void setResources ( Resource[] resources )
    {
        boolean noResources = (( resources == null ) || ( resources.length < 1 ));

        if ( noResources && failOnNoResources())
        {
            throw new RuntimeException( "No resources to read" );
        }

        super.setResources( resources );

        if ( logger.isDebugEnabled())
        {
            logger.debug( String.format( "Resources set: %s", Arrays.toString( resources )));
        }
    }
}

As you see, MyMultiResourceItemReader now fails if you attempt to configure it with missing resources. If you need to go back to original behavior, just swap "failOnNoResources" back to "false", just do so before configuring a "resources" property:

<bean id="myReader" class="MyMultiResourceItemReader">
    <property name="failOnNoResources" value="false"/>
    <property name="resources"         value="${directory}/*.zip"/>
    ...
</bean>

Now, attempting to run Spring Batch job with missing files throws an exception. No more ignoring missing resources silently!

"BATCH-1665" will provide a built-in boolean flag to control MultiResourceItemReader strictness.

 
No Comments

Posted in Code, Spring

 

New Maven plugins released!

14 Nov



Note, an update is available.

After a lot of work I released version "0.1" of the following Maven plugins:

  • "maven-copy-plugin" is an alternative to Maven plugins like assembly, resources, dependency, and truezip. Its purpose is to make working with archives and dependencies very easy.



    It is a Swiss Army knife if you need to copy, pack and unpack files, archives and Maven dependencies. Content replacement, network support, Groovy extension points, attaching archives created as Maven artifacts – it is possible to perform all operations in a single Maven plugin! Oh boy, just give it a try.

  • "maven-hudson-plugin" allows to generate Hudson jobs, keeping any amount of them in a single POM. Jobs can be organized hierarchically with inheritance and can invoke each other, Artifactory deployment is supported as well. Managing tens or hundreds of Hudson jobs becomes possible when they are kept in one place and inherit each other with a sensible defaults.
  • "maven-spring-batch-plugin" allows to invoke Spring Batch jobs as part of Maven build.
  • "maven-mail-plugin" allows to send mails with attachment from Maven.
  • "maven-sshexec-plugin" allows to execute commands on a remote server over ssh.
  • "maven-properties-plugin" allows to create Maven properties dynamically with Groovy snippets.
  • "maven-timestamp-plugin" allows to create a timestamp Maven property.
  • "maven-assert-plugin" allows to verify various build assertions: properties are defined, files exist and files/directories content is identical.
  • "maven-find-plugin" allows to set a Maven property to location of folder dynamically found for each module built. It helps in situations where build needs to access files in other locations than the current module.
 

Neat Delicious trick – keywords combination

26 Aug

The real beauty of Delicious lies in keywords combinations.

I mentioned already that Delicious is my #2 most favorite productivity tool. Within time I developed a set of the most frequently-used keywords in the right column that can be combined with tags in the left column:

"hudson" - Mailing list - "ml"
"spring" - Issue tracker - "jira"
"ant" - API documentation - "api"
"jfrog" - Documentation - "doc"
"groovy" - Maven repository - "repo"
"maven" - Source code browsing - "code"


Now with a "d"-keyworded Delicious search I only need to type "d spring api" to get to

Lots of other useful combinations are also available:

"groovy ml" "hudson ml" "spring ml" "maven ml" "jfrog ml"
"groovy jira" "hudson jira" "spring jira" "gmaven jira" "jfrog jira"
"groovy api" "java api" "spring api" "maven api" "apache api"
"java7 doc" "java doc" "spring doc" "css doc" "html doc"
"groovy repo" "hudson repo" "apache repo" "plugins repo" "jfrog repo"
"groovy code" "hudson code" "spring code" "gmaven code" "groovypp code"


So jumping to a mailing list or API documentation for X is just a matter of "d X ml" or "d X api". Fast!

 

Artifactory REST API with Spring 3 RestTemplate

22 Aug

Artifactory’s REST API is something I was using quite a lot recently and would like to share my experience here. Normally, there’s no need to turn to the REST API when working with Artifactory and in most cases Maven, Hudson or TeamCity plugins are the only ones “talking” to it.

But in this specific case Artifactory was used as a general storage for company’s binaries (a capability I praised a lot in the past): Java agent downloads packed Lucene indexes from Artifactory where they are uploaded to by a Hudson job running Maven and Spring Batch.

It is an interesting and very educating project which I’ll surely explore in more details later. Today, I would like to talk about the REST part of it, the way it is used with a SpringTemplate and some Artifactory-specific nuances, marked with

1. Choosing the client.

Initially I was considering Jersey as a client library knowing JFrog teams uses it to provide Artifactory’s REST support. But then I decided to stick to Spring since version 3 was already used throughout the project and one of its new features is exactly what I needed here – a RestTemplate. Why not give it a try?

2. DAO.

Accessing any external resource is normally done through a DAO layer. Accessing Artifactory shouldn’t be an exception to this rule so we have a DAO interface and an ArtifactoryDAO implementation.

public interface DAO
{
    int  getLatestVersion ( String serviceName, String dataType );
    File download ( File dir, String serviceName, String dataType, int version );
    void deleteOldVersions( String serviceName, String dataType, int oldVersions );
}

serviceName and dataType are our analogies to Maven’s groupId and artifactId. The interface allows a client to get a number of the latest version, download any version locally and delete old versions.

3. Spring time!

Java client gets a DAO implementation injected to it, which in turn gets a RestTemplate injected as well, configured with a custom message converter, more on that below.

<bean ... >
    <constructor-arg name = "dao"  ref = "artifactoryDAO"/>
</bean>

<bean name="artifactoryDAO" class="...">

    ...

    <constructor-arg name  = "rest">
        <bean class="org.springframework.web.client.RestTemplate">
            <property name="messageConverters">
                <list>
                    <bean class="JsonMessageConverter"/>
                </list>
            </property>
        </bean>
    </constructor>
</bean>



4. Working with a RestTemplate.

As one would expect, working with a RestTemplate is pretty straightforward. From all convenient methods available I picked up an exchange() one to provide a general-usage wrapper:

<T> T request( String url, HttpMethod method, Class<T> responseType )
{
    HttpHeaders headers = new HttpHeaders();
    headers.set( "Accept",        "application/json" );
    headers.set( "Authorization", auth());

    T response = rest().exchange( url,
                                  method,
                                  new HttpEntity<String>( headers ),
                                  responseType ).getBody();

    assert (( response != null ) &&
            ( responseType.isAssignableFrom( response.getClass())));

    return response;
}


There is a need to specify a correct "Accept" header since RestTemplate sends "Accept: text/plain" by default which is not accepted by Artifactory.

With built-in HttpMessageConverter and its sub-classes there’s no need to worry about conversion of a REST response, JSON in our case, to an object:

Map<String, Object> m =
   request( "http://artifactory/api/storage/repo/serviceName/dataType",
            HttpMethod.GET,
            Map.class );

Last request() argument defines a type of an object to which response will be converted, considering its "Content-Type" and converters available. I was mostly using a Map when reading Artifactory responses, like “folder info”.


There is a need to extend MappingJacksonHttpMessageConverter in order to recognize "application/vnd.org.jfrog.artifactory.storage.folderinfo+json", returned by Artifactory as JSON:

import org.springframework.http.MediaType;
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;

public class JsonMessageConverter extends MappingJacksonHttpMessageConverter
{
    @Override
    public boolean canRead ( Class<?> clazz, MediaType mediaType )
    {
        return
            ( super.canRead( clazz, mediaType ) ||
              (( "application".equals( mediaType.getType())) &&
              ( mediaType.getSubtype().startsWith( "vnd.org.jfrog.artifactory" ))));
    }
}



5. Reading InputStream and downloading files.

Message converters work really nice if response is not large and fits into memory. But how about downloading files? I can not assume their content will fit into memory and would like to access a raw InputStream.

It is also possible but in a different way, using an execute() method with two callbacks:

import org.apache.commons.io.IOUtils;

...

private static final RequestCallback ACCEPT_CALLBACK =
    new RequestCallback()
    {
        @Override
        public void doWithRequest ( ClientHttpRequest request ) throws IOException
        {
            request.getHeaders().set( "Accept", "application/json" );
        }
    };

private static class FileResponseExtractor implements ResponseExtractor<Object>
{
    private final File file;
    private       File file () { return this.file; }

    private FileResponseExtractor ( File file )
    {
        this.file = file;
    }

    @Override
    public Object extractData ( ClientHttpResponse response ) throws IOException
    {
        InputStream  is = response.getBody();
        OutputStream os = new BufferedOutputStream( new FileOutputStream( file()));

        IOUtils.copyLarge( is, os );
        IOUtils.closeQuietly( is );
        IOUtils.closeQuietly( os );

        return null;
    }
}

...

rest().execute( downloadUrl,
                HttpMethod.GET,
                ACCEPT_CALLBACK,
                new FileResponseExtractor( new File( downloadDir, fileName )));


Note that to download an artifact with a REST "/api/download/" method requires Artifactory Power Pack license but you can also download an artifact with a regular URL like
"http://artifactory/repo/serviceName/dataType/version/dataType-version.tar".


6. Downloading large files.

I blogged recently about ServletResponse.setContentLength() problem: it doesn’t work with files larger than 2Gb and sends a negative "Content-Length" header due to int overflow.

This problem has also appeared in Artifactory bit it is fixed already, we just need to wait for version "2.2.6" to be released.

Spring 3 uses Apache HttpClient downloading an empty file if negative "Content-Length" header is sent. So I could not download files larger than 2Gb with Spring but I could with wget!

import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;

...

String command  = String.format(
    "wget -S -nv -O \"%s\" -T 120 \"%s\" --cache=off %s",
    destinationFile, sourceFileUrl, credentials );

int    exitCode = new DefaultExecutor().execute( CommandLine.parse( command ));

As it appears, "wget" works just fine with a negative "Content-Length" which, btw, is not that unusual and can be sent in those cases when response length is not known in advance.

7. Deleting artifacts.

Artifactory’s API doesn’t really mention it but artifacts or folders can be deleted as well, provided client has enough permissions to do so, of course:

request( "http://artifactory/repo/serviceName/dataType",
         HttpMethod.DELETE,
         Map.class );



8. Security.

HTTP calls can be authenticated by overriding Spring’s CommonsClientHttpRequestFactory and configuring RestTemplate to use it:

public class ArtifactoryCommonsClientHttpRequestFactory
    extends  CommonsClientHttpRequestFactory
{
    private final String user;
    private final String password;

    public ArtifactoryCommonsClientHttpRequestFactory ( String user,
                                                        String password )
    {
        this.user     = user;
        this.password = password;
    }

    @Override
    public HttpClient getHttpClient ()
    {
        HttpClient client = super.getHttpClient();

        if ( this.user != null )
        {
            client.getState().setCredentials(
                AuthScope.ANY,
                new UsernamePasswordCredentials( this.user, this.password ));
        }

        return client;
    }
}
<bean class="org.springframework.web.client.RestTemplate">
    <property name  = "requestFactory">
        <bean class = "ArtifactoryCommonsClientHttpRequestFactory">
            <constructor-arg name="user"     value="..."/>
            <constructor-arg name="password" value="..."/>
        </bean>
    </property>

    ...
</bean>


Artifactory encrypted password can be specified as a "password".

But this approach didn’t work well in all scenarios, particularly in DELETE calls which went out unauthorized so I switched to setting an "Authorization" HTTP header, as shown in “Working with a RestTemplate” section above.

9. Related links.


10. Summary.

Overall, it took some time to implement ArtifactoryDAO and sort out all issues involved: Spring converters, request and response headers, authentication, downloading large files, etc .. But it now works extremely well, response time of each REST invocation is normally below 10 ms and Artifactory Hudson plugin eats 2.5Gb files for breakfast!




 

Ant FTP task: adding progress indicator and timeout

18 Aug

Ant FTP task provides no option to display how much the download has progressed so far, similarly to wget or curl verbose options. Setting connection timeout isn’t possible either.

But I had to implement it one day and here is what I have found out:

The basic idea is therefore to redefine an FTP task with improved version of FTPClient making use of CopyStreamListener when calling Util.copyStream(). Let’s do it!

1. New FTP task:

The biggest problem in the whole story is the following line of code: ftp = new FTPClient(). I wish there were a simple IoC-like way to “inject” an FTPClient instance here but Ant does not provide too many ways to customize its default configuration, something like RestTemplate.setRequestFactory(). Alas!

I really wish more people would embrace Spring/IoC way of thinking: everything is a Strategy and therefore can be “injected”, extended, modified, and customized.

So my first attempt was to extend Ant’s org.apache.tools.ant.taskdefs.optional.net.FTP class, override execute() method, copy its original code and replace the assignment. As much as I have no taste for copying someone else’s code in order to modify a line or two, sometimes this is the only way to go. But it didn’t work! execute() method accesses lots of private members, not accessible from a sub-class.

The next option was to copy an entire FTP class and then replace the assignment. If anyone can suggest a better way to do the same I’m all ears. So I ended up with:

public class FTP extends Task
{
    ...
    public void execute() throws BuildException {
        ...
        org.apache.commons.net.ftp.FTPClient ftp = null;
        ...
        ftp = ( verbose ? new FTPClient( getProject()) :
                          new org.apache.commons.net.ftp.FTPClient());
        ftp.setDataTimeout( 5 * 60 * 1000 ); // 5 minutes
        ...
    }
}

Line 08 sets a customized version of FTPClient or a default one, line 10 sets up FTPClient timeout.

2. New FTPClient:

public class FTPClient extends org.apache.commons.net.ftp.FTPClient
{
    private final Project project; // org.apache.tools.ant.Project

    ...

    @Override
    public boolean retrieveFile(String remote, OutputStream local)
    throws IOException
    {
        ...
        Util.copyStream( input,
                         local,
                         getBufferSize(),
                         CopyStreamEvent.UNKNOWN_STREAM_SIZE,
                         new CopyStreamListener( this.project ),
                         false );
        ...
    }
}

Again, I had to copy the original code and “patch” Util.copyStream() call to use CopyStreamListener implementation. Also, I had to copy and slightly modify an FTPConfigurator class to make it accept my new FTPClient.

3. CopyStreamListener:

public class CopyStreamListener implements org.apache.commons.net.io.CopyStreamListener
{
    private final Project project;
    private       long    mbTransferred = 0;

    ...

    @Override
    public void bytesTransferred ( long totalBytes, int bytes, long streamSize )
    {
        long mb = ( totalBytes / ( 1024 * 1024 ));
        if ( mb > mbTransferred )
        {
            mbTransferred = mb;
            this.project.log( "[" + new Date() + "]: [" + mbTransferred + "] Mb transferred" );
        }
    }
}

4. Plugging in new definition of FTP task with ClfAntBuilder:

public class ClfAntBuilder extends AntBuilder
{
    public ClfAntBuilder ()
    {
        super();
        getProject().addTaskDefinition( "ftp", FTP.class );
    }
}

5. Running it:

new ClfAntBuilder().ftp( action         : 'get',
                         server         : host,
                         userid         : username,
                         ... )
{
    fileset( dir       : '...',
             includes  : '...',
             excludes  : '...' )
}

6. The final result:

Trying to override old definition of task ftp
      [ftp] getting files
      [ftp] transferring SomeFile.xml.zip to C:\Temp\SomeFile.xml.zip
[Tue Aug 17 20:43:07 IDT 2010]: [1] Mb transferred
[Tue Aug 17 20:43:07 IDT 2010]: [2] Mb transferred
[Tue Aug 17 20:43:07 IDT 2010]: [3] Mb transferred
[Tue Aug 17 20:43:07 IDT 2010]: [4] Mb transferred
[Tue Aug 17 20:43:07 IDT 2010]: [5] Mb transferred
[Tue Aug 17 20:43:07 IDT 2010]: [6] Mb transferred
[Tue Aug 17 20:43:07 IDT 2010]: [7] Mb transferred
...

Nice!

 
 

Say “No” to ServletResponse.setContentLength()

05 Aug



javax.servlet.ServletResponse.setContentLength(int) is a new kind of Y2k bug. It accepts int assuming no files larger than 2Gb will ever be sent as an HTTP response.

If you pass a long larger than 231-1 which is exactly 2Gb, it overflows and negative header is sent in response.

Free Download Manager fails to download a file and Apache Commons HttpClient v3.1 downloads an empty one (need to check version 4)

Too bad Java SE did the same mistake and java.net.URLConnection.getContentLength() returns int as well, it means the API will never be changed.

Right now the solution is to use HttpServletResponse.setHeader(String,String):

long length = file.length();
..
response.setHeader( "Content-Length", String.valueOf( length ))

Other APIs have taken the right route though:

 
1 Comment

Posted in Spring, Web

 

Spring: Ant-like files pattern matching

27 Jul



Spring Batch job definition:

<bean>
    <property name="resources" value="file:/${path}/some.*.pattern.zip"/>
    ...
</bean>

"${path}/some.*.pattern.zip" is right, I can guarantee that. Double-checked!
So why Spring Batch complaints "No resources to read" and does nothing?

Argh .. probably, has something to do with files pattern matching.

All right, I was curious about Ant-like files pattern matching in non-Ant environments for a long time already.
How one does that?

For "maven-copy-plugin" I’ve managed with "file-management" module:

<dependency>
    <groupid>org.apache.maven.shared</groupid>
    <artifactid>file-management</artifactid>
    <version>1.2.1</version>
    <scope>compile</scope>
</dependency>
import org.apache.maven.shared.model.fileset.FileSet;
import org.apache.maven.shared.model.fileset.util.FileSetManager;

FileSet fs = new FileSet();

fs.setDirectory( .. );
fs.setIncludes( .. );
fs.setExcludes( .. );

String[] files = new FileSetManager().getIncludedFiles( fs )

but that’s Maven. How is it done in Spring?
Well, debugging is always fun!

What a journey. But we have the answer, it’s "org.springframework.util.AntPathMatcher". Doesn’t surprise me, "org.springframework.util" package was always full of nice utilities. One more to a toolbox!

P.S.
"No resources to read" ?
"file:/" was not required:

<bean>
    <property name="resources" value="${path}/some.*.pattern.zip"/>
    ...
</bean>

Previous version was converted to something like "file:c:\file:/", but this one worked just fine.
I guess this version would also work, according to PathMatchingResourcePatternResolver Javadoc:

<bean>
    <property name="resources" value="file:${path}/some.*.pattern.zip"/>
    ...
</bean>

One more example where a single "/" is of crucial importance. I love this job!

 
No Comments

Posted in Ant, Maven, Spring