Archive for the ‘Java’ category

Jersey 1.13 Released

July 13th, 2012

Last week we released Jersey 1.13 after the long 4+ months! Since the team is now fully focusing on Jersey 2.0 and JAX-RS 2.0 development, this is mostly a bug-fixing release. Anyway, we did manage to address a number of interesting issues. As for any other Jersey 1.x release, the list of changes can be found in changes.txt file in the root of our repository.

The main changes were around JSON handling – Michal, one of our new team-mates, got rid of our custom JSON stream reader and replaced it with a Jackson-based one to make our JSON-JAXB binding more robust. He also made some fixes and improvements to Jersey integration with MOXy JAXB implementation. We received a few external code contributions – from Edouard Chevalier (fixing the interaction of GZIP encoding filter with ETags) and from Atlassian – thank you guys! Finally, I ported the proxy client to Jersey 1.x. It is available in the Jersey 1.x svn repository under experimental/proxy-client (you won’t find it mentioned in changes.txt given it is an experimental module).

Writing Web Applications That Can Run With Jersey 1.x As Well As Jersey 2.0

June 26th, 2012

Now, that early builds of Jersey 2.0 are integrated into GlassFish application server main trunk (which Jakub is still planning to blog about) more people have to deal with the reality that Jersey 2.0 is not compatible with Jersey 1.x. All the internal Jersey API’s underwent a significant refactoring in Jersey 2.0. And people started asking if there is any way of designing the web application (WAR-based) so that it can run on GlassFish 3.x (which bundles Jersey 1.x) while also being able to run with GlassFish main trunk (that now bundles Jersey 2.0 milestone builds). So, I decided to spend my last week’s “weekend coding” on creating a simple jar that one can bundle in their war to enable this. As a result, I added a new maven module under jersey/ext (in Jersey 2.0 workspace) called servlet-portability. You can build it and add it to your application as a dependency. Now, here is what it does and what it doesn’t do:

  • It provides PortableServletContainer class you can use in web.xml instead of the Jersey version-specific ServletContainer class to register a Jersey servlet.
  • It enables your application to bundle Jersey 2.x-specific code along with Jersey 1.x-specific code and helps to ensure the right code gets loaded depending on which version of the Jersey runtime is on the runtime classpath.
  • It does not provide a compatibility layer – in other words, it does not enable you to use API’s from Jersey 1.x with Jersey 2.x runtime – i.e. in your web app you have to write the Jersey version-specific code twice – one class using Jersey 1.x API’s and another one using Jersey 2.x API’s.

Example Application

Here is how you can use it. I will build a simple application with the following requirements:

  1. Use package-scanning for discovering all the resources and providers that are using pure JAX-RS API (i.e. are not using any version-specific Jersey API’s)
  2. Expose one resource that will require different implementation depending on Jersey version

Getting Jersey 2.0 Source Code and Building It

To try this out let’s first get Jersey 2.0 source code. You can do it in one of the following ways:

  • Download the sources zip file, or
  • Clone Jersey git repository on java.net by executing the following in the terminal:
    git clone ssh://<your_java_net_userid>@git.java.net/jersey~code jersey

    , or

  • Clone Jersey git repository on github.com by executing the following in the terminal:
    git clone git://github.com/jersey/jersey.git

Now, to build the code execute the following in the source tree root:

mvn clean install -Dmaven.test.skip=true

Creating a New Web Application Project

We will use Jersey 2.0 web application maven archetype to quickly create a skeleton of a new Jersey web application. To do that, switch to the directory where you want your project to reside and execute the following command in that directory:

mvn archetype:generate -DarchetypeArtifactId=jersey-quickstart-webapp -DarchetypeGroupId=org.glassfish.jersey.archetypes -DinteractiveMode=false -DgroupId=com.example -DartifactId=simple-service -Dpackage=com.example -DarchetypeVersion=2.0-SNAPSHOT

You should see a new simple-service subdirectory got created that contains the project files. Now, open this project in your favorite IDE. You should see it is a simple project containing a single resource called MyResource (in com.example package) and registering Jersey servlet using web.xml descriptor.

Updating the pom.xml

Let’s update the pom.xml of the project to add a dependency on the portability module, change the scope of Jersey 2.0 servlet dependency to “provided” (so that the Jersey jars are not bundled in our war) and add Jersey 1.x servlet dependency. The resulting pom.xml should look as follows:


    4.0.0
    com.example
    simple-service
    <packaging>war</packaging>
    1.0-SNAPSHOT
    simple-service
    
        simple-service
        <plugins>
            <plugin>
                org.apache.maven.plugins
                maven-compiler-plugin
                true
                
                    1.6
                    1.6
                
            </plugin>
        </plugins>
    
    
        
            org.glassfish.jersey.ext
            jersey-servlet-portability
            ${jersey.version}
        
        
            org.glassfish.jersey.containers
            jersey-container-servlet-core
            ${jersey.version}
            provided
        
        
            com.sun.jersey
            jersey-servlet
            1.12
            provided
        
    
    <properties>
        2.0-SNAPSHOT
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

Adding Jersey Version-Specific Resource

Create two new sub-packages – com.example.jersey1 and com.example.jersey2. We will use these for version-specific classes. Let’s create a new resource in com.example.jersey2 package and name it InfoResource. Just for illustration, it will inject ResourceConfig class from Jersey 2 API, retrieve the value of “example.text” property and return it back to the client. Here is how the resource class looks like:

package com.example.jersey2;

import javax.ws.rs.GET;
import javax.ws.rs.core.Context;
import org.glassfish.jersey.server.ResourceConfig;

public class InfoResource {
    private @Context ResourceConfig rc;
    
    @GET
    public String get() {
        return (String) rc.getProperty("example.text");
    }
}

As you can see on line 5 it uses Jersey version-specific class. Let’s clone this into com.example.jersey1 package, changing just the import statement to import ResourceConfig from Jersey 1.x package. Here is how the InfoResource in com.example.jersey1 should look like:

package com.example.jersey1;

import javax.ws.rs.GET;
import javax.ws.rs.core.Context;
import com.sun.jersey.api.core.ResourceConfig;

public class InfoResource {
    private @Context ResourceConfig rc;
    
    @GET
    public String get() {
        return (String) rc.getProperty("example.text");
    }
}

One important thing to note here is I haven’t annotated InfoResource using the @Path annotation. This is to make sure it does not get picked up by the package scanning (otherwise it would result in Jersey 2-specific resource being loaded when only Jersey 1.x runtime is present or vice-versa). We will use the “explicit root resources” feature from Jersey 1.x and resource builder API from Jersey 2.0 to register this unannotated resource into the Jersey runtime. We need to implement a custom ResourceConfig class for that.

Implementing ResourceConfig

Add ExampleResourceConfig class to com.example.jersey2 package and configure it to scan “com.example” package for resources and providers and map InfoResource to “info” path using the resource builder API. Here is the source code:

package com.example.jersey2;

import java.util.ArrayList;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.model.Resource;
import org.glassfish.jersey.server.model.ResourceModelIssue;

public class ExampleResourceConfig extends ResourceConfig {
    public ExampleResourceConfig() {
        // look for resources in com.example package and its subpackages
        this.packages("com.example");
        
        // create a resource builder from the InfoResource class
        // (sending in dummy issues list - we don't expect introspection issues)
        Resource.Builder rb = Resource.builder(
            InfoResource.class,
            new ArrayList<ResourceModelIssue>()
        );
        // bind it to "info" path
        rb.path("info");
        // add it to the resource config
        this.addResources(rb.build());
    }
}

Add the Jersey 1.x version of the same class into the com.example.jersey1 package. It should look as follows:

package com.example.jersey1;

import com.sun.jersey.api.core.PackagesResourceConfig;

// extend PackagesResourceConfig to get the package scanning functionality
public class ExampleResourceConfig extends PackagesResourceConfig {
    public ExampleResourceConfig() {
        // pass name of the package to be scanned
        super("com.example");
        
        // add InfoResource as an explicit resource bound to "info" path
        this.getExplicitRootResources().put("info", InfoResource.class);
    }
}

Adding Link to InfoResource to the index.jsp

To be able to conveniently access the InfoResource, let’s add a link to the index.jsp page. Here is how the updated index.jsp looks like:

<html>
<body>
    <h2>Jersey RESTful Web Application!</h2>
    <p>Jersey resource
    <p>InfoResource
    <p>Visit Project Jersey website
    for more information on Jersey!
</body>
</html>

Updating web.xml – Putting It All Together

And finally we will use the PortableServletContainer to put this all together. Open web.xml and replace the use of ServletContainer with the use of PortableServletContainer. It will have two sets of init-parameters – one Jersey 1.x specific – prefixed with jersey1#, the other one Jersey 2.0 specific prefixed with jersey2#. The servlet will figure out automatically what version of Jersey runtime is present and use the right init-params subset. Btw, obviously you don’t have to add jersey1/2# prefix to the parameter name if that parameter should be present and have the same value regardless of Jersey version being used. Here is how the updated web.xml should look like:



    
        Jersey Web Application
        org.glassfish.jersey.servlet.portability.PortableServletContainer
        
            jersey1#javax.ws.rs.Application
            com.example.jersey1.ExampleResourceConfig
        
        
            jersey1#example.text
            Using Jersey 1.x
        
        
            jersey2#javax.ws.rs.Application
            com.example.jersey2.ExampleResourceConfig
        
        
            jersey2#example.text
            Using Jersey 2.0
        
        1
    
    
        Jersey Web Application
        /webapi/*
    

Trying It Out

Now you can build the application and deploy the war to GlassFish 3.1.2 which bundles Jersey 1. Assuming the GlassFish is running at locahost:8080, you should be able to access the application at http://localhost:8080/simple-service/. You can see that clicking on Jersey resource works (invokes MyResource.getIt() method returning “Got it!”) and clicking on InfoResource shows “Using Jersey 1.x”.

If you deploy the same thing on the latest GlassFish 4.0 promoted build that bundles Jersey 2, you should see that after clicking on InfoResource, “Using Jersey 2.0” is displayed.

Resources

You can download a zip file with this example application at: http://java.net/projects/jersey/downloads/download/portability-example.zip

Another example can be seen in in Jersey 2.0 integration tests workspace: https://github.com/jersey/jersey/tree/master/tests/integration/portability-jersey-1

Proxy Client on Top of JAX-RS 2.0 Client API

May 4th, 2012

UPDATE (7/3/2012): Proxy-client module is now part of the regular workspace (instead of incubator) and get’s pushed to maven along with other Jersey modules, so no need to build it manually anymore.

Several JAX-RS 1.x implementations are providing proxy client API as one of the ways to access web services. The basic idea is you can attach the standard JAX-RS annotations to an interface, and then implement that interface by a resource class on the server side while reusing the same interface on the client side by dynamically generating an implementation of that using java.lang.reflect.Proxy calling the right low-level client API methods.

Jersey 1.x client API (and now JAX-RS 2.0 client API) leverages the fact REST (and HTTP) defines the standard set of operations, so unlike for JAX-WS type of web services, which are verb-centric, for RESTful web services the interface is uniform and thus there is no need for generating client proxies. The standard client API is static (has fixed set of operations – get, put, post, delete, …). Proxy client API may be seen as evil, as it creates tighter coupling between services and clients. However, some people do find it useful and it provides better opportunities for reusing JAX-RS concepts between services and clients, including name-bound interceptors, for example.

Few weeks back I quickly “hacked” a proxy client factory for Jersey as part of Jersey 2.0 and made some small incremental improvements since then. It is based purely on the current draft of the standard JAX-RS 2.0 client API, so should be usable with any JAX-RS 2.0 implementation, not just Jersey. Currently it lives in the “incubator” subfolder of Jersey 2.0 workspace, so if you want to give it a try, you have to check it out and build it yourself. The project includes a test that illustrates the usage, and also a simple example.

Building the Proxy-Client Project

To build it, first clone the Jersey repository, build Jersey, then switch to the proxy-client folder and build the proxy-client project. The following shell commands do that (assuming you have git and mvn installed):

git clone ssh://[your-java.net-user-id]@git.java.net/jersey~code jersey
cd jersey
mvn install
cd incubator/proxy-client
mvn install

Using/Playing with the Proxy-Client

The project has just one class – WebResourceFactory. Here is how it can be used:

Let’s say you want to access a web resource available at a URI that could be represented by the following URI template:

http://acme.com/rest/books/{book-id}

Let’s assume this resource can produce and consume application/json and application/xml and supports GET, PUT and DELETE operations. There is also a parent resource – http://acme.com/rest/books/ which accepts GET (returns a list of books) and POST (creates a new book). Here is one example of how you can model it using the interfaces with JAX-RS annotations and then access these resources using those interfaces and WebResourceFactory class:

Interface representing the “books” resource:

@Path("books")
public interface BooksResource {
    @GET
    @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    List<Book> getBooks();

    @POST
    @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    Response createBook(Book book);

    @Path("{bookid}")
    BookResource getBookResource(@PathParam("bookid") String bookId);
}

Interface representing “books/{bookid}” subresource:

public interface BookResource {
    @GET
    @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    Book get();

    @PUT
    @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    Book update(Book book);

    @DELETE
    void delete();
}

Accessing it from your application:

// create a new JAX-RS 2.0 target pointing to the root of the web api
Target t = ClientFactory.newClient().target("http://acme.com/rest/");

// create a new client proxy for the BooksResource
BooksResource booksRsc = WebResourceFactory(BooksResource.class, t);

// get list of books
List<Book> books = booksRsc.getBooks();

// get book resource by ID
BookResource bookRsc = booksRsc.getBookResource(bookId);
// get book object
Book myBook = bookRsc.get();
// delete book
bookRsc.delete();

I hope you got the idea. As you can see, the proxy factory can handle path parameters and can automatically create proxies for sub-resources. It can also handle other kinds of parameters such as header parameters, query parameters, form parameters and cookie parameters.

Give it a try and let us know what you think.

Library for Reading and Writing Encrypted Zip Files in Java

March 31st, 2012

More than two years ago I posted this blog entry, where I showed a little utility for reading password-protected zip files in Java. Since then it seems it helped a lot of people and many asked if I could provide something for the other direction – i.e. writing encrypted files. Finally I’ve decided to add that as well as improve the original class for reading and recently I published a small open source project which provides both the ZipDecryptInputStream (for reading) as well as ZipEncryptOutputStream (for writing). The project is named ziputils and is available here: https://bitbucket.org/matulic/ziputils/overview.

The usage is very simple. ZipDecryptInputStream can be used in the same way as outlined in my old blog entry. ZipEncryptOutputStream which I added can be used in a very similar way:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

// usage: java Main zip-file-name password filename1 [filename2 [filename3 [...]]]
public class Main {
    public static void main(String[] args) throws IOException {
        // create a stream that will encrypt the resulting zip file
        // using the password provided in the second command line argument
        ZipEncryptOutputStream zeos = new ZipEncryptOutputStream(new FileOutputStream(args[0]), args[1]);
        // create the standard zip output stream, initialize it with our encrypting stream
        ZipOutputStream zos = new ZipOutputStream(zeos);

        // write the zip file
        for (int i = 2; i < args.length; i++) {
            ZipEntry ze = new ZipEntry(args[i]);
            zos.putNextEntry(ze);
            InputStream is = new FileInputStream(args[i]);
            int b;
            while ((b = is.read()) != -1) {
                zos.write(b);
            }
            zos.closeEntry();
        }
        zos.close();
    }
}

You can find more about how to use the classes in the project javadoc. To download the ziputils jar, go to the project downloads page. Enjoy!

ooomailer 1.0 – More Flexible Out-of-office Replies

October 2nd, 2011

In my day job I have to deal with a huge amount of e-mails each day – most of them are not urgent, but some are. When I go for vacation or on a business trip, I need to let people know I am not available and won’t respond until I get back (and provide an alternate contact for urgent matters). These days pretty much every mail server has a “vacation message” feature, that allows you to do exactly that – supports sending automated responses to all e-mails coming to your inbox, letting others know you won’t be able to read the message before a given date. However the implementation of that feature varies from server to server.

I have several e-mail accounts on several mail servers and I found myself in a situation when I needed more flexibility in the way I set up my vacation message. Being involved in several open source projects and subscribed to many open mailing lists, I needed to make sure I don’t spam those lists or users of those lists (who may not know me) with my vacation auto-replies. Even people I do want to notify don’t need to get my auto-reply to every message that reaches me – notification once in several days is good enough. And I needed to customize the vacation message for internal recipients (e.g. providing links to internal resources) and have it slightly more detailed than the auto-replies that go to external people.

So, a couple of months back, the weekend before my summer vacation I sat down and implemented a simple utility that provides me with this flexibility. Since then I used it several times, fixed a few issues, and Alexis (a colleague of mine) contributed some more improvements. At this point I am pretty confident the tool works smoothly, so decided to release version 1.0. Give it a try if you are in a similar situation – the tool and the source code are available at https://bitbucket.org/matulic/ooomailer/wiki/Home.

Jersey 1.9.1 Released

September 16th, 2011

Over the past two weeks, I’ve been working with Pavel on finalizing and staging the bits for the hands-on-lab on OAuth, we are going to do at this year’s JavaOne. As part of that, I had to make a few more clean-ups in the Jersey OAuth client library, so we decided to make a branch for 1.9.1 and make those clean-ups along with some other small fixes there. Now, 2 weeks after 1.9, we released it. This is the release we’ll be using for JavaOne and although the release cycle was so short, it does have two nice additions worth highlighting.

  • Un-/marshalling collection types
    Until 1.9.1, JAXB un-/marshalling in Jersey worked only for Collection and List interfaces. I.e. if your resource method returned (or took as a parameter) Collection<Foo> or List<Foo> (where Foo was a JAXB bean), de-/serialization from/to XML/JSON would work, but if it returned LinkedList<Foo> or Set<Foo> or any other Collection subtype, it would not work. This is fixed in 1.9.1 and you can now return and retrieve any well-known interfaces that extend Collection (such as Set, Queue, etc.) and their implementations which have default public constructor.
  • PostReplaceFilter improvements
    PostReplaceFilter can be used to support clients which can’t send the full range of HTTP methods. It enables converting POST requests to other methods such as PUT or DELETE. If a POST request comes with a different method specified in X-HTTP-Method-Override header, the filter will replace POST in the request with that specified method. This has been in Jersey for a while, but only supported method overriding using the X-HTTP-Method-Override header. In 1.9.1 you can now use “_method” query parameter as well, and when overriding POST to GET the filter will convert all the form parameters to query parameters. Whether both header and query parameter are looked at by the filter (or only the header or only the query parameter) is configurable. Thanks to gk5885Fredy Nagy and Florian Hars for sharing their views and patches.
You can see the full list of changes in our changelog. For more info on Jersey see http://jersey.java.net.

Reading Password-Protected ZIP Files in Java

October 31st, 2009

On a recent “fun” project, I needed my application to be able to access password-protected zip files of a particular format. It was one of these features I thought will take me no time to implement. Anyway, to my surprise, neither JDK supports password-protected ZIP files, nor I was able to find a suitable Java open source library I could use for that purpose. So, I ended up writing the utility class on my own. I wrote an implementation of java.io.InputStream that filters the ZIP file data and turns a password-protected ZIP into an unprotected one on the fly – so the stream can be nicely chained with java.util.zip.ZipInputStream. Although the class is specifically targeted at the particular type of ZIP files I had to deal with (see the limitations below), maybe other people have to deal with the same type of files, or this class can provide a good start for others to turn it into a utility that would work with any type of ZIP (maybe I will do it myself some day – for now I don’t have time).
To implement this class I used the ZIP File Format Specification as the source of information. I also used the 7-zip project (C++) as a reference during the debugging to verify my understanding of the ZIP spec. and the CRC algorithm.
So, here is the class:

import java.io.IOException;
import java.io.InputStream;

public class ZipDecryptInputStream extends InputStream {
    private static final int[] CRC_TABLE = new int[256];
    // compute the table
    // (could also have it pre-computed - see http://snippets.dzone.com/tag/crc32)
    static {
        for (int i = 0; i < 256; i++) {
            int r = i;
            for (int j = 0; j < 8; j++) {
                if ((r & 1) == 1) {
                    r = (r >>> 1) ^ 0xedb88320;
                } else {
                    r >>>= 1;
                }
            }
            CRC_TABLE[i] = r;
        }
    }

    private static final int DECRYPT_HEADER_SIZE = 12;
    private static final int[] LFH_SIGNATURE = {0x50, 0x4b, 0x03, 0x04};

    private final InputStream delegate;
    private final String password;
    private final int keys[] = new int[3];

    private State state = State.SIGNATURE;
    private int skipBytes;
    private int compressedSize;
    private int value;
    private int valuePos;
    private int valueInc;

    public ZipDecryptInputStream(InputStream stream, String password) {
        this.delegate = stream;
        this.password = password;
    }

    @Override
    public int read() throws IOException {
        int result = delegate.read();
        if (skipBytes == 0) {
            switch (state) {
                case SIGNATURE:
                    if (result != LFH_SIGNATURE[valuePos]) {
                        state = State.TAIL;
                    } else {
                        valuePos++;
                        if (valuePos >= LFH_SIGNATURE.length) {
                            skipBytes = 2;
                            state = State.FLAGS;
                        }
                    }
                    break;
                case FLAGS:
                    if ((result & 1) == 0) {
                        throw new IllegalStateException("ZIP not password protected.");
                    }
                    if ((result & 64) == 64) {
                        throw new IllegalStateException("Strong encryption used.");
                    }
                    if ((result & 8) == 8) {
                        throw new IllegalStateException("Unsupported ZIP format.");
                    }
                    result -= 1;
                    compressedSize = 0;
                    valuePos = 0;
                    valueInc = DECRYPT_HEADER_SIZE;
                    state = State.COMPRESSED_SIZE;
                    skipBytes = 11;
                    break;
                case COMPRESSED_SIZE:
                    compressedSize += result << (8 * valuePos);
                    result -= valueInc;
                    if (result < 0) {
                        valueInc = 1;
                        result += 256;
                    } else {
                        valueInc = 0;
                    }
                    valuePos++;
                    if (valuePos > 3) {
                        valuePos = 0;
                        value = 0;
                        state = State.FN_LENGTH;
                        skipBytes = 4;
                    }
                    break;
                case FN_LENGTH:
                case EF_LENGTH:
                    value += result << 8 * valuePos;
                    if (valuePos == 1) {
                        valuePos = 0;
                        if (state == State.FN_LENGTH) {
                            state = State.EF_LENGTH;
                        } else {
                            state = State.HEADER;
                            skipBytes = value;
                        }
                    } else {
                        valuePos = 1;
                    }
                    break;
                case HEADER:
                    initKeys(password);
                    for (int i = 0; i < DECRYPT_HEADER_SIZE; i++) {
                        updateKeys((byte) (result ^ decryptByte()));
                        result = delegate.read();
                    }
                    compressedSize -= DECRYPT_HEADER_SIZE;
                    state = State.DATA;
                    // intentionally no break
                case DATA:
                    result = (result ^ decryptByte()) & 0xff;
                    updateKeys((byte) result);
                    compressedSize--;
                    if (compressedSize == 0) {
                        valuePos = 0;
                        state = State.SIGNATURE;
                    }
                    break;
                case TAIL:
                    // do nothing
            }
        } else {
            skipBytes--;
        }
        return result;
    }

    @Override
    public void close() throws IOException {
        delegate.close();
        super.close();
    }

    private void initKeys(String password) {
        keys[0] = 305419896;
        keys[1] = 591751049;
        keys[2] = 878082192;
        for (int i = 0; i < password.length(); i++) {
            updateKeys((byte) (password.charAt(i) & 0xff));
        }
    }

    private void updateKeys(byte charAt) {
        keys[0] = crc32(keys[0], charAt);
        keys[1] += keys[0] & 0xff;
        keys[1] = keys[1] * 134775813 + 1;
        keys[2] = crc32(keys[2], (byte) (keys[1] >> 24));
    }

    private byte decryptByte() {
        int temp = keys[2] | 2;
        return (byte) ((temp * (temp ^ 1)) >>> 8);
    }

    private int crc32(int oldCrc, byte charAt) {
        return ((oldCrc >>> 8) ^ CRC_TABLE[(oldCrc ^ charAt) & 0xff]);
    }

    private static enum State {
        SIGNATURE, FLAGS, COMPRESSED_SIZE, FN_LENGTH, EF_LENGTH, HEADER, DATA, TAIL
    }
}

These are the limitations:

  • Only the “Traditional PKWARE Encryption” is supported (spec. section VII)
  • Files that have the “compressed length” information at the end of the data section (rather than at the beginning) are not supported (see “general purpose bit flag”, bit 3 in section V, subsection J in the spec.)

And this is how you can use it in your code:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

// usage: java Main [filename] [password]
public class Main {
    public static void main(String[] args) throws IOException {
        // password-protected zip file I need to read
        FileInputStream fis = new FileInputStream(args[0]);
        // wrap it in the decrypt stream
        ZipDecryptInputStream zdis = new ZipDecryptInputStream(fis, args[1]);
        // wrap the decrypt stream by the ZIP input stream
        ZipInputStream zis = new ZipInputStream(zdis);

        // read all the zip entries and save them as files
        ZipEntry ze;
        while ((ze = zis.getNextEntry()) != null) {
            FileOutputStream fos = new FileOutputStream(ze.getName());
            int b;
            while ((b = zis.read()) != -1) {
                fos.write(b);
            }
            fos.close();
            zis.closeEntry();
        }
        zis.close();
    }
}