Wednesday Jul 13, 2011

Jersey - Automatic XML schema generation for WADL

Gerard Davison contributed to Jersey one (last!) thing which is needed for generating client based on returned wadl - he managed to add XML schemas which describe returned (or accepted) types. See more details in his blog post: Auttomatic XML Schema generation for Jersey WADLs.

Thanks Gerard! ..and looking forward to another contribution(s) :-)

Monday Jul 11, 2011

Jersey - Server and Client side configuration

Originaly this was supposed to be something what are we lacking in our user guide - list of all init params plus at least one sample to each one.. but I wasn't able to finish it yet (and its on my TODO list for at least six months :-| ). But I think just a list with links to javadoc should be useful as well. Please note that this is not complete list and it might be (but I don't guarantee anything) updated in time. Reason why I did this is to sum up all possible properties - it is usable even for me (as a developer) because I can't remember everything :-) And I guess that this list will be sometimes included in Jersey user guide.

Server side init params

field (declaration) value
FeaturesAndProperties FEATURE_DISABLE_XML_SECURITY "com.sun.jersey.config.feature.DisableXmlSecurity"
FeaturesAndProperties FEATURE_FORMATTED "com.sun.jersey.config.feature.Formatted"
FeaturesAndProperties FEATURE_XMLROOTELEMENT_PROCESSING "com.sun.jersey.config.feature.XmlRootElementProcessing"
FeaturesAndProperties FEATURE_PRE_1_4_PROVIDER_PRECEDENCE "com.sun.jersey.config.feature.Pre14ProviderPrecedence"
JSONMarshaller FORMATTED "com.sun.jersey.api.json.JSONMarshaller.formatted"
LoggingFilter FEATURE_LOGGING_DISABLE_ENTITY "com.sun.jersey.config.feature.logging.DisableEntitylogging"
ClassNamesResourceConfig PROPERTY_CLASSNAMES "com.sun.jersey.config.property.classnames"
ClasspathResourceConfig PROPERTY_CLASSPATH "com.sun.jersey.config.property.classpath"
PackagesResourceConfig PROPERTY_PACKAGES "com.sun.jersey.config.property.packages"
ResourceConfig FEATURE_NORMALIZE_URI "com.sun.jersey.config.feature.NormalizeURI"
ResourceConfig FEATURE_CANONICALIZE_URI_PATH "com.sun.jersey.config.feature.CanonicalizeURIPath"
ResourceConfig FEATURE_REDIRECT "com.sun.jersey.config.feature.Redirect"
ResourceConfig FEATURE_MATCH_MATRIX_PARAMS "com.sun.jersey.config.feature.IgnoreMatrixParams"
ResourceConfig FEATURE_IMPLICIT_VIEWABLES "com.sun.jersey.config.feature.ImplicitViewables"
ResourceConfig FEATURE_DISABLE_WADL "com.sun.jersey.config.feature.DisableWADL"
ResourceConfig FEATURE_TRACE "com.sun.jersey.config.feature.Trace"
ResourceConfig FEATURE_TRACE_PER_REQUEST "com.sun.jersey.config.feature.TracePerRequest"
ResourceConfig PROPERTY_MEDIA_TYPE_MAPPINGS "com.sun.jersey.config.property.MediaTypeMappings"
ResourceConfig PROPERTY_LANGUAGE_MAPPINGS "com.sun.jersey.config.property.LanguageMappings"
ResourceConfig PROPERTY_DEFAULT_RESOURCE_COMPONENT _PROVIDER_FACTORY_CLASS "com.sun.jersey.config.property. DefaultResourceComponentProviderFactoryClass"
ResourceConfig PROPERTY_CONTAINER_NOTIFIER "com.sun.jersey.spi.container.ContainerNotifier"
ResourceConfig PROPERTY_CONTAINER_REQUEST_FILTERS "com.sun.jersey.spi.container.ContainerRequestFilters"
ResourceConfig PROPERTY_CONTAINER_RESPONSE_FILTERS "com.sun.jersey.spi.container.ContainerResponseFilters"
ResourceConfig PROPERTY_RESOURCE_FILTER_FACTORIES "com.sun.jersey.spi.container.ResourceFilters"
ResourceConfig PROPERTY_WADL_GENERATOR_CONFIG "com.sun.jersey.config.property.WadlGeneratorConfig"
ServletContainer GLASSFISH_DEFAULT_ERROR_PAGE_RESPONSE "org.glassfish.web.isDefaultErrorPageEnabled"
ServletContainer APPLICATION_CONFIG_CLASS "javax.ws.rs.Application"
ServletContainer RESOURCE_CONFIG_CLASS "com.sun.jersey.config.property.resourceConfigClass"
ServletContainer JSP_TEMPLATES_BASE_PATH "com.sun.jersey.config.property.JSPTemplatesBasePath"
ServletContainer PROPERTY_WEB_PAGE_CONTENT_REGEX "com.sun.jersey.config.property.WebPageContentRegex"
ServletContainer FEATURE_FILTER_FORWARD_ON_404 "com.sun.jersey.config.feature.FilterForwardOn404"
ServletContainer PROPERTY_FILTER_CONTEXT_PATH "com.sun.jersey.config.feature.FilterContextPath"
WebComponent APPLICATION_CONFIG_CLASS "javax.ws.rs.Application"
WebComponent RESOURCE_CONFIG_CLASS "com.sun.jersey.config.property.resourceConfigClass"
WebComponent JSP_TEMPLATES_BASE_PATH "com.sun.jersey.config.property.JSPTemplatesBasePath"

Client side init params

field (declaration) value
ClientConfig PROPERTY_FOLLOW_REDIRECTS "com.sun.jersey.client.property.followRedirects"
ClientConfig PROPERTY_READ_TIMEOUT "com.sun.jersey.client.property.readTimeout"
ClientConfig PROPERTY_CONNECT_TIMEOUT "com.sun.jersey.client.property.connectTimeout"
ClientConfig PROPERTY_CHUNKED_ENCODING_SIZE "com.sun.jersey.client.property.chunkedEncodingSize"
ClientConfig PROPERTY_BUFFER_RESPONSE_ ENTITY_ON_EXCEPTION "com.sun.jersey.client.property. bufferResponseEntityOnException"
ClientConfig PROPERTY_THREADPOOL_SIZE "com.sun.jersey.client.property.threadpoolSize"
HTTPSProperties PROPERTY_HTTPS_PROPERTIES "com.sun.jersey.client.impl.urlconnection.httpsProperties"
URLConnectionClientHandler PROPERTY_HTTP_URL_CONNECTION _SET_METHOD_WORKAROUND "com.sun.jersey.client.property. httpUrlConnectionSetMethodWorkaround"
ApacheHttpClientConfig PROPERTY_INTERACTIVE "com.sun.jersey.impl.client.httpclient.interactive"
ApacheHttpClientConfig PROPERTY_HANDLE_COOKIES "com.sun.jersey.impl.client.httpclient.handleCookies"
ApacheHttpClientConfig PROPERTY_CREDENTIALS_PROVIDER "com.sun.jersey.impl.client.httpclient.credentialsProvider"
ApacheHttpClientConfig PROPERTY_PREEMPTIVE_AUTHENTICATION "com.sun.jersey.impl.client.httpclient.preemptiveAuthentication"
ApacheHttpClientConfig PROPERTY_PROXY_URI "com.sun.jersey.impl.client.httpclient.proxyURI"
ApacheHttpClientConfig PROPERTY_HTTP_STATE "com.sun.jersey.impl.client.httpclient.httpState"
ApacheHttpClient4Config PROPERTY_DISABLE_COOKIES "com.sun.jersey.impl.client.httpclient.handleCookies"
ApacheHttpClient4Config PROPERTY_CREDENTIALS_PROVIDER "com.sun.jersey.impl.client.httpclient.credentialsProvider"
ApacheHttpClient4Config PROPERTY_PREEMPTIVE_ BASIC_AUTHENTICATION "com.sun.jersey.impl.client.httpclient. preemptiveBasicAuthentication"
ApacheHttpClient4Config PROPERTY_CONNECTION_MANAGER "com.sun.jersey.impl.client.httpclient.connectionManager"
ApacheHttpClient4Config PROPERTY_HTTP_PARAMS "com.sun.jersey.impl.client.httpclient.httpParams"
ApacheHttpClient4Config PROPERTY_PROXY_URI "com.sun.jersey.impl.client.httpclient.proxyURI"
ApacheHttpClient4Config PROPERTY_PROXY_USERNAME "com.sun.jersey.impl.client.httpclient.proxyUsername"
ApacheHttpClient4Config PROPERTY_PROXY_PASSWORD "com.sun.jersey.impl.client.httpclient.proxyPassword"

Tuesday Jun 28, 2011

Jersey non blocking client

Although Jersey already have support for making asynchronous requests, it is implemented by standard blocking way - every asynchronous request is handled by one thread and that thread is released only after request is completely processed. That is OK for lots of cases, but imagine how that will work when you need to do lots of parallel requests. Of course you can limit (and its really wise thing to do, you do want control your resources) number of threads used for asynchronous requests, but you'll get another maybe not pleasant consequence - obviously processing time will increase.

There are few projects which are trying to deal with that problem, commonly named as async http clients. I didn't want to "re-implement a wheel" and I decided I'll use AHC - Async Http Client made by Jeanfrancois Arcand. There is also interesting implementation from Apache - HttpAsyncClient, but it is still in "very early stages of development" and others haven't been in similar or better shape as AHC.

How this works? Non-blocking clients allow users to make same asynchronous requests as we can do with standard approach but implementation is different - threads are better utilized, they don't spend most of time in idle state. Simply described - when you make a request (send it over the network), you are waiting for reply from other side. And there comes main advantage of non-blocking approach - it uses these threads for further work, like making other requests or processing responses etc.. Idle time is minimized and your resources (threads) will be far better used.

Who should consider using this? Everyone who is making lots of asynchronous requests. I haven't done proper benchmark yet, but some simple dumb tests are showing huge improvement in cases where lots of concurrent asynchronous requests are made in short period.

Last but not least - this module is still experimental, so if you don't like something or if you have ideas for improvements/any feedback, feel free to comment this blog post, send mail to users@jersey.java.net or contact me personally. All feedback is greatly appreciated!

maven dependency (will be present in java.net maven 2 repo by the end of the day):

link: http://download.java.net/maven/2/com/sun/jersey/experimental/jersey-non-blocking-client

<dependency>
 <groupId>com.sun.jersey.experimental</groupId>
 <artifactId>jersey-non-blocking-client</artifactId>
 <version>1.9-SNAPSHOT</version>
</dependency>

code snippet:

 ClientConfig cc = new DefaultNonBlockingClientConfig();
 cc.getProperties().put(NonBlockingClientConfig.PROPERTY_THREADPOOL_SIZE, 10); // default value, feel free to change
 Client c = NonBlockingClient.create(cc);

 AsyncWebResource awr = c.asyncResource("http://oracle.com");

 Future<ClientResponse> responseFuture = awr.get(ClientResponse.class);

 // or
 awr.get(new TypeListener<ClientResponse>(ClientResponse.class) {
     @Override
     public void onComplete(Future<ClientResponse> f) throws InterruptedException {
          ...
     }
 });

javadoc (temporary location, won't be updated): http://anise.cz/~paja/jersey-non-blocking-client/

Monday May 23, 2011

Enable/Disable WADL generation in runtime - Jersey 1.7

We've received request about giving more control related to WADL generation, but not that much (no authentication or this kind of features) in runtime. This can be useful for example when you have more than one application deployed and you want to disable WADL generation for all of them - as some security measure. How is it done? You can inject WadlApplicationContext and use its methods - isWadlGenerationEnabled() and setWadlGenerationEnabled(boolean).

Use of this is self explanatory, but note that you won't be able to enable WADL for application which disables WADL in web.xml (see ResourceConfig.FEATURE_DISABLE_WADL). Where you can inject this? Almost everywhere, for example to your WebApplicationListener or even into resource class:

    @Path("root")
    public static class RootResource {
        @Get
        public String get() {
            return "GET";
        }

        @Path("switch")
        @POST
        public void switchMethod(@Context WadlApplicationContext wadlApplicationContext) {
            wadlApplicationContext.setWadlGenerationEnabled(!wadlApplicationContext.isWadlGenerationEnabled());
        }
    }

As title suggest, this functionality was added in Jersey 1.7.

Friday May 20, 2011

Limiting threads used for asynchronous requests - Jersey Client

Jersey Client already has support for making asynchronous requests, but it uses default ExecutorService, which spawns new thread for each request, which is not very convenient in most of cases. You can set your own ExecutorService (see Client.setExecutorService()) and limit thread count but it is not that obvious thing and lots of users are not aware of this possibility.

So I've added another property to ClientConfig class which represents threadpool size of created ExecutorService. See code snippet below:

        ClientConfig cc = new DefaultClientConfig();
        cc.getProperties().put(ClientConfig.PROPERTY_THREADPOOL_SIZE, 10);
        Client c = Client.create(cc);
        AsyncWebResource r = c.asyncResource("http://somehost.tld/resource").build());

And possibility to set your own ExecutorService still remains, so consider this just as some minor improvement/try for visibility gain for this option.

Thursday Apr 21, 2011

Jersey Client - Presentation slides - CZJUG

Slides

Jersey Client - Apache HTTP Client 4.x integration

Since this week, Jersey users can benefit from Apache HTTP Client 4.x integration. "jersey-apache-http-client4" module has been finalized and provides similar functionality as "jersey-apache-http-client", but make sure you read javadoc - most of settings have changes because of major change in Apache HTTP Client API.

How to start with Jersey and Apache HTTP Client 4.x?

Add following dependency:

<dependency>
    <groupId>com.sun.jersey.contribs</groupId>
    <artifactId>jersey-apache-http-client4</artifactId>
    <version>1.7-SNAPSHOT</version> <!-- or 1.7-ea04 and newer, when available -->
</dependency>


And you can instantiate new client for example like described in Javadoc: package com.sun.jersey.client.apache4.

Wednesday Mar 16, 2011

Jersey - Grizzly 2.x integration

Since today, Jersey users can benefit from Grizzly 2.x integration. "jersey-grizzly2" module has been finalized and provides same functionality as "jersey-grizzly" and new test framework module was added - "jersey-test-framework-grizzly2", which also became default test container for nearly all our test and samples.

How to start with Jersey and Grizzly 2.x?

If you want to be able to run your application from command line by executing mvn compile exec:java, see Main.java from Helloworld sample.

If you want to use test framework integration, change your dependency to (or you might want just to add and keep older ones):

<dependency>
    <groupId>com.sun.jersey.jersey-test-framework</groupId>
    <artifactId>jersey-test-framework-grizzly2</artifactId>
    <version>1.6-SNAPSHOT</version> <!-- or 1.6-ea06 and newer, when available -->
    <scope>test</scope>
</dependency>



Wednesday Feb 23, 2011

Code coverage using Cobertura

We were requested to investigate and implement code coverage measurement and report generation to our project - Jersey. Nice opportunity to get into new thing and have a motivation to finish (since this was a "must do" type of request..).

Alright, let's get into it. At the begging, we had to decide which tool we are going to use - Cobertura [1] won, one of main arguments are nicer reports, active development and price (it's free).

There is a nice set of scripts, which can instrument classes/jars, collect & merge generated files and finally create report. This can be used in environment, where you have total control of your classpath and can easily run tests against it. Well, Jersey uses maven, so this is little more difficult, but cobertura does have maven support, so lets try it..

Maven support works perfectly when you have tests in each module and nowhere else, otherwise it is probable you'll run into similar issues as I did.. To be more descriptive: let's say, I have module A (which has some tests) and module B, which depends on A (and also has some tests). By default, code coverage results from B will contain only classes from B, which is not correct, because we want to have complete results. I hit this in Jersey, for example substitute A with "jersey-core" and B with "jersey-tests" and you are there. Solution is not that simple, because you have to somehow force B to use instrumented version of A.. which is not very straightforward using maven..

Solution:

You might find or think of few solutions, I like the one described here [2].

Main pom (profile which enables building&local deploying cobertura classified artifacts: 

        <profile>
            <id>cobertura</id>
            <activation>
                <property>
                    <name>cobertura</name>
                </property>
            </activation>
            <dependencies>
                <dependency>
                    <groupId>net.sourceforge.cobertura</groupId>
                    <artifactId>cobertura</artifactId>
                    <optional>true</optional>
                    <version>1.9.4.1</version>
                </dependency>
            </dependencies>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.codehaus.mojo</groupId>
                        <artifactId>cobertura-maven-plugin</artifactId>
                        <executions>
                            <execution>
                                <id>cobertura-instrument</id>
                                <phase>pre-integration-test</phase>
                                <goals>
                                    <goal>instrument</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>

                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-jar-plugin</artifactId>
                        <executions>
                            <execution>
                                <id>cobertura-jar</id>
                                <phase>post-integration-test</phase>
                                <goals>
                                    <goal>jar</goal>
                                </goals>
                                <configuration>
                                    <classifier>cobertura</classifier>
                                    <classesDirectory>${basedir}/target/generated-classes/cobertura</classesDirectory>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>

                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-install-plugin</artifactId>
                        <executions>
                            <execution>
                                <id>cobertura-install</id>
                                <phase>install</phase>
                                <goals>
                                    <goal>install</goal>
                                </goals>
                                <configuration>
                                    <classifier>cobertura</classifier>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>

and additionally, you need to make sure that instrumented artifacts are used during cobertura:cobertura run. What we have here is two profiles, one activated by -Dcobertura and other one by default:

        <profile>
            <id>cobertura</id>
            <activation>
                <property>
                    <name>cobertura</name>
                </property>
            </activation>
            <dependencies>
                <dependency>
                    <groupId>com.sun.jersey</groupId>
                    <artifactId>jersey-core</artifactId>
                    <version>${project.version}</version>
                    <classifier>cobertura</classifier>
                </dependency>
            </dependencies>
        </profile>
        <profile>
            <id>default</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <dependencies>
                <dependency>
                    <groupId>com.sun.jersey</groupId>
                    <artifactId>jersey-core</artifactId>
                    <version>${project.version}</version>
                </dependency>
            </dependencies>
        </profile>

(this is taken from jersey-server module). I encountered some issues with report generation and actually jersey project structure is not simple, so I couldn't use automatic html report generation in pom file.. So we need to collect all generated cobertura.ser files separately, merge it, prepare all sources and generate report. This can be done by following commands:

# prepare sources
mkdir ../sources
find . | grep src/main/java$ | while read X ; do cp -r "$X"/\* ../sources/ ; done

# compile and run tests
mvn clean install -Dmaven.test.skip=true -Dcobertura
mvn cobertura:cobertura -Dcobertura -DforkMode=never

# collect cobertura.ser files, merge and generate report
find . | grep cobertura.ser$ | while read X  ; do cp "$X" ../cobertura$I.ser ; I=$(($I+1)) ;  done;
cobertura-merge.sh --datafile ./cobertura_final.ser ../cobertura\*
cobertura-report.sh --datafile ./cobertura_filtered.ser --destination ../report --format html ../sources/

Hope it helps somebody who wants to do code coverage for different project. Btw, coverage report for jersey: http://anise.cz/~paja/jersey/report/

[1] http://cobertura.sourceforge.net/
[2] http://foobar.lacoctelera.net/post/2008/09/15/uso-del-plugin-cobertura-dentro-un-proyecto-multi-modulo-en

Friday Feb 11, 2011

Replacing client used in Jersey Test Framework

There was an interesting question on mailing list - Is it possible to replace default Jersey client in JerseyTest? (JerseyTest is a class from Jersey Test Framework, basically superclass of all "Jersey enabled" tests).

Answer is.. not really. It just wasn't made to support this option. Until now :)

There was method getClient(), but it couldn't be easily overriden (it was private static) and it actually does more that create Client, so it isn't really method you want to replace. Long story short.. I created overridable ClientFactory which is (surprisingly) responsible for creating new Clients..

Let's say we want to just replace default Jersey Client implementation by Apache:

public class MainTest extends JerseyTest {
    ...
    @Override
    protected ClientFactory getClientFactory() {
        return new ClientFactory() {
            @Override
            public Client create(ClientConfig clientConfig) {
                return ApacheHttpClient.create(clientConfig);
            }
        };
    }
}

This overrides default client implementation BUT still allows container override this and use its client (this is used for example in InMemoryTestContainer). If you want to have total control and replace even this concept, you can do it by overriding getClient() method:

    @Override
    protected Client getClient(TestContainer tc, AppDescriptor ad) {
        return ApacheHttpClient.create(ad.getClientConfig());
    }

but I don't recommend this approach, former one should be sufficient in most cases.

This functionality is present in 1.6-SNAPSHOT and 1.6-ea03 (and newer Jersey versions).

Friday Feb 04, 2011

Jersey Client - Making requests with HttpURLConnection hack

HttpURLConnection provided in JDK has serious limitation - you can't do requests with arbitrary chosen method - actually it limit methods to some subsed defined in javadoc, which is NOT extendable in any way.. which might cause some problems. For example you can't do PATCH request because it wasn't in HTTP spec in the time of writing that class. And if you would want to use it for making WebDav client, you are totally lost, it just can't be done.

 Well, until now ;)

One Jersey user - Markus Karg came with a workaround. Method name can be "injected" to one field in HttpURLConnection via injection and it will be used to create request. We don't want to have this enabled by default, so if you want to use it, you need to set property URLConnectionClientHandler.PROPERTY_HTTP_URL_CONNECTION_SET_METHOD_WORKAROUND to true.

        DefaultClientConfig config = new DefaultClientConfig();
        config.getProperties().put(URLConnectionClientHandler.PROPERTY_HTTP_URL_CONNECTION_SET_METHOD_WORKAROUND, true);
        Client c = Client.create(config);

        WebResource r = c.resource(getUri().path("test/entity").build());

        ClientResponse cr = r.method("GOTOSLEEP", ClientResponse.class);

will produce:

Feb 4, 2011 11:25:44 AM com.sun.jersey.api.client.filter.LoggingFilter log
INFO: 1 \* Client out-bound request
1 > GOTOSLEEP http://localhost:9997/test/test

and you can cleary see that method "GOTOSLEEP" was used and HttpURLConnection don't complain about anything. This workaround has some limitation - you can't put an entity to the request. And additionally, it \*probably\* wont work on some containers and maybe in future versions of JDK (containers sometimes have their own implementation of HttpURLConnection).

This functionality is available in current Jersey trunk (1.6-SNAPSHOT) and in promoted build 1.6-ea02 (which will be released later today).

Wednesday Jan 26, 2011

Jersey - Apache HTTP Client 4.1 (experimental) integration available

What is supported? All basic functionality (simple requests with or without entity) and Jersey related things like filters (logging, auth, ...). Additionally, some of Apache HTTP Client features are supported by default - like cookies processing.

What needs to be done:

  • Apache style auth support
  • Proxy configuration settings
  • Caching and other features...

How can you give it a try? Simply.. put another dependency to your maven project:

        <dependency>
            <groupId>com.sun.jersey.experimental</groupId>
            <artifactId>jersey-apache-client4</artifactId>
            <version>1.6-SNAPSHOT</version>
        </dependency>

And create your client like:

        Client c = new com.sun.jersey.client.apache.ApacheHttpClient4();

        WebResource wr = c.resource("http://localhost:9998/helloworld-webapp");

        String s = wr.path("helloworld").get(String.class);

        System.out.println(s);

and use it like you are used to..

Comments/remarks/suggestions are welcomed!

Wednesday Jan 12, 2011

Returning XML representation of List from Jersey

One user asked about wrapping element that Jersey uses when serves List<T> recently and I recalled I didn't write a blog post about one change I made in this area so.. here we are..

 Jersey supports returning JSR222 (JAXB) annotated classes and even List of them. Let's say we have following class:

@XmlRootElement
public class A {
        public String s;
        public int i;
}

and some corresponding method which returns List<A>

    @GET
    @Path("list")
    public List<A> getList() {
        ArrayList<A> list = new ArrayList<A>();

        return list;

    }

 when we deploy this and do a GET on /list, we'll see something like

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<as><a><s>first</s><i>1</i></a><a><s>second</s><i>2</i></a></as>

which is fine and valid for most cases, but you might need something else. For example, you want to rename <a> to something meaningful, let's say "contract". XmlRootElement annotation has "name" property which is designed for these cases, so lets add it to our class A annotation (@XmlRootElement(name="contract") public class A { ... } ) and make that request again..

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<as><contract><s>first</s><i>1</i></contract><contract><s>second</s><i>2</i></contract></as>

Looks better, <a> has changed to <contract> and we might be satisfied.. oh wait, what about <as>? It haven't changed! Firstly, <as> was just plural for <a> and it stayed same when XmlRootElements name property was specified.. oops. This was a bug in Jersey and we couldn't fix it without breaking backwards compatibility, so fix for this is enabled only when FEATURE_XMLROOTELEMENT_PROCESSING is enabled (set to true). Lets do that and see how output changes..

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<contracts><contract><s>first</s><i>1</i></contract><contract><s>second</s><i>2</i></contract></contracts>

Ha, we are almost there. Or.. depends what you want, I guess most of users is satisfied and doesn't need to read further. But there is always someone who wants something "extra".. like specify wrapping element. That is needed for some cases (like B2B communication) and Jersey needs to be able to handle this usecase as well. Unfortunately its not as clean as other solutions. We need to implement our own MessageBodyWriter and for those not familiar with JAX-RS API - it basically allows you to specify how the output will be presented for which mediatype.

Your MessageBodyWriter needs to have @javax.ws.rs.ext.Provider  annotation (or be added as provider to ResourceConfig) and implement MessageBodyWriter interface.. I create such class which is sufficient for basic cases, you might want to improve it by creating support for other charsets than UTF-8 or gathering Marshaller from other source that creating new instance.. but at least its a working startpoint..

@Provider
public class ListAProvider implements MessageBodyWriter<List> {

  private String myWrapElemName = "wrapper";
  private Marshaller m;

  public ListAProvider() throws Exception{
    JAXBContext context = JAXBContext.newInstance(A.class);
    m = context.createMarshaller();
    m.setProperty(Marshaller.JAXB_FRAGMENT, true);
  }

  @Override
  public long getSize(List as, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
    return -1;
  }

  @Override
  public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {

    if(mediaType.getSubtype().endsWith("xml") &&
          List.class.isAssignableFrom(type) &&
          genericType instanceof ParameterizedType) {
      if ((((ParameterizedType)genericType).getActualTypeArguments()[0]).equals(A.class)) {
        return true;
      }
    }

    return false;
  }

  @Override
  public void writeTo(List list, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException {

    Charset c = Charset.forName("UTF-8");
    String cName = c.name();

    entityStream.write(String.format("<?xml version=\\"1.0\\" encoding=\\"%s\\" standalone=\\"yes\\"?>", cName).getBytes(cName));

    entityStream.write(String.format("<%s>", myWrapElemName).getBytes(cName));

    for (Object o : list)
      try {
        m.marshal(o, entityStream);
      } catch(JAXBException exp) {}

    entityStream.write(String.format("</%s>", myWrapElemName).getBytes(cName));

  }
}

when you add this to your Jersey enabled application and make same request as we did earlier, you should get:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<wrapper><contract><s>first</s><i>1</i></contract><contract><s>second</s><i>2</i></contract></wrapper>

and we are done for this article, hope somebody will find it useful.

Tuesday May 05, 2009

Jersey Client - Using ContainerListener

I would like to introduce a possibility to monitor connections made within jersey client api. Monitoring in this case means have knowledge about amount of transferred bytes.

This functionality is provided (as many others) as a client filter, concretely ConnectionListenerFilter. To create ConnectionListenerFilterinstance, you have to implement OnStartConnectionListener. It might look as follows:

class ListenerFactory implements OnStartConnectionListener {
     public ContainerListener onStart(ClientRequest cr) {
         return new Listener();
    }
}

Looks simple (and it is). This factory creates ContainerListener instance based on ClientRequest. Important note: ClientRequest instance here may be used only for getting values. Any attempt to change anything will produce UnsupportedOperationException.

Ok. Now we need ContainerListener implementation. This class is defined as:

public abstract class ContainerListener {

    public void onSent(long delta, long bytes) {};

    public void onReceiveStart(long totalBytes) {};

    public void onReceived(long delta, long bytes) {};

    public void onFinish() {};
}

Parameters should be easy to understand, delta is always difference to previous call, bytes is sum of sent/received bytes so far and totalBytes is value from http header (if present, otherwise is set to -1).

Every request has a simple "lifecycle":

1) Send request
2) Receive response

Method onSent is called during step one only when request has some entity. When request is sent and response has no entity, onFinish() is called and that's it. More interesting scenario is when response contains some entity - onReceiveStart is called after sending a request and onReceived during data receiving. Method onFinish is called after whole entity is read.

You will probably have some nice GUI progress bar but I'm going to do only plain text log for now to keep it simple.

class Listener extends ContainerListener {
     @Override
     public void onSent(long delta, long bytes) {
         System.out.println("onSent: delta: " + delta + " bytes: " + bytes);
     }

     @Override
     public void onReceiveStart(long totalBytes) {
         System.out.println("onReceiveStart: " + totalBytes);
     }

     @Override
     public void onReceived(long delta, long bytes) {
         System.out.println("onReceived: delta: " + delta + " bytes: " + bytes);
     }

     @Override
     public void onFinish() {
         System.out.println("onFinish");
     }
}

And we're almost done! Last thing to do is register our ListenerFactory as a client filter:

    Client c = Client.create();

    c.addFilter(new ConnectionListenerFilter(new ListenerFactory()));

and actually do a request:

    WebResource r = c.resource("http://download.java.net/maven/2/com/sun/jersey/samples/jersey-samples/1.1.0-ea/jersey-samples-1.1.0-ea-project.zip");
    r.get(String.class);

Output should look like this:

onReceiveStart: 615534
onReceived: delta: 3677 bytes: 3677
onReceived: delta: 2608 bytes: 6285
...
onReceived: delta: 2608 bytes: 613949
onReceived: delta: 1585 bytes: 615534
onFinish

You can experiment with some requests containing entity (so you will see that onSent method is called during sending).

For more information about project Jersey visit http://jersey.dev.java.net

About

Pavel Bucek

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today