Showing posts with label adf. Show all posts
Showing posts with label adf. Show all posts

Thursday, April 15, 2010

ADF/JSF Invoking a method on a backing bean before the page is shown

In a couple of cases in our application we really needed to run a little section of code before the page was displayed. We tried a bunch of ways of doing this until we got a tip from Ducan Mills that we could consider abusing "Bookmark" feature in adfc-config.xml. This is normally just for accepting query parameters; but it turns out that the method is executed before the page is rendered so it can be useful it you need to do some minor configuration in advance.

Note that this only applies to unbounded task flows, bounded task flows can't be made book markable. Also the parameters are optional, the code will fire even if none are defined as they are in this example. (We are using the code to force some initial selections in this case.)

I am not convinced it is a good idea in many cases; it was really helpful in solve our particular problem. I would be interested to hear if there is a more JSF way of doing this.

Wednesday, January 27, 2010

A little bit of REST with Oracle ADF / SDO

So I have been working on my BuildAnApp project, that I have mentioned previously, and decided to try and take the day out to build a simple RESTful interface to my ADF application module. Just a simple resource so I can create and update items from a single table.

The problem is always how to convert you objects into XML, so I decided to expose the Application Module with a Service interface. This results in an set of methods that can update the model in terms of SDO object which are easy to convert into XML.

I then created another project in JDeveloper along with a class called Root configured as a JAX-RS resource, you can find more about working with Jersey / JAX-RS web services in the help for 11R1PS1. I won't go into details here.

One of the limitations of Jersey is that it isn't yet properly integrated in to the container so nice JEE annotations such as @Resource don't work properly. I blogged about this previously but you can just create a class that looks like this as a short cut:

package org.concept.model.rest;

import com.sun.jersey.api.core.ResourceConfig;
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.core.spi.component.ComponentScope;
import com.sun.jersey.spi.container.WebApplication;
import com.sun.jersey.spi.container.servlet.ServletContainer;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.InjectableProvider;

import java.lang.reflect.Type;

import javax.annotation.Resource;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import javax.servlet.ServletConfig;


public class InjectingAdapter extends ServletContainer {
    @Override
    protected void configure(ServletConfig servletConfig, ResourceConfig rc,
                             WebApplication wa) {
        super.configure(servletConfig, rc, wa);


        rc.getSingletons().add(new InjectableProvider<Resource, Type>() {

                public ComponentScope getScope() {
                    return ComponentScope.PerRequest;
                }

                public Injectable<Object> getInjectable(ComponentContext ic,
                                                        Resource r, Type c) {

                    final String name = r.name();


                    return new Injectable<Object>() {

                        public Object getValue() {
                            
                            Object value = null;

                            try {
                                Context ctx = new InitialContext();
        

                                // Look up a data source
                                try {
                                    value = ctx.lookup(name);
                                } catch (NamingException ex) {

                                    value =
                                            ctx.lookup("java:comp/env/" + name);
                                }

                            } catch (Exception ex) {
                                ex.printStackTrace();
                            }

                            return value;
                        }
                    };
                }
            });
    }
}

You have to modify the web.xml that JDeveloper generates to use this new class rather than the default Jersey servlet. I have also added in a ejb-ref that will pick up the service interface from the application module.

<web-app>

  ...

  <servlet>
    <servlet-name>jersey</servlet-name>
    <servlet-class>org.concept.model.rest.InjectingAdapter</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>jersey</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>
  <ejb-ref>
    <ejb-ref-name>ConceptServiceInterface</ejb-ref-name>
    <ejb-ref-type>Session</ejb-ref-type>
    <remote>org.concept.model.am.common.serviceinterface.ConceptModuleService</remote>
    <ejb-link>org.concept.model.am.common.ConceptModuleServiceBean</ejb-link>
  </ejb-ref>
</web-app>

The next thing you have to worry about is that Jersey doesn't know how to deal with SDO objects out of the box. So we need to provide a Writer and Reader, these can be placed on the class path and Jersey will pick them up. Note that the implementation is far from production quality; but it is enough to get started.

package org.concept.model.rest;


import commonj.sdo.DataObject;
import commonj.sdo.helper.XMLHelper;

import java.io.IOException;
import java.io.OutputStream;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;


@Produces("application/sdo+xml")
@Provider
public class SDOMessageBodyWriter implements MessageBodyWriter<DataObject> {
    public SDOMessageBodyWriter() {
        super();
    }

    public boolean isWriteable(Class c, Type type, Annotation[] annotation,
                               MediaType mediaType) {
        return true;
    }


    public long getSize(DataObject dataObject, Class<?> class1, Type type,
                        Annotation[] annotations, MediaType mediaType) {
        return -1L;
    }

    public void writeTo(DataObject dataObject, Class<?> class1, Type type,
                        Annotation[] annotations, MediaType mediaType,
                        MultivaluedMap<String, Object> multivaluedMap,
                        OutputStream outputStream)  {

        // From http://wiki.eclipse.org/EclipseLink/Examples/SDO/StaticAPI
        
        // and http://www.eclipse.org/eclipselink/api/1.1/index.html


        try {
            commonj.sdo.Type sdoType = dataObject.getType();
            XMLHelper.INSTANCE.save(dataObject, sdoType.getURI(), sdoType.getName(), outputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

And the reader implementation:

package org.concept.model.rest;


import commonj.sdo.DataObject;
import commonj.sdo.helper.XMLDocument;
import commonj.sdo.helper.XMLHelper;

import java.io.InputStream;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

import javax.ws.rs.Consumes;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Provider;


@Consumes("application/sdo+xml")
@Provider
public class SDOMessageBodyReader implements MessageBodyReader<DataObject> {
    public boolean isReadable(Class<?> class1, Type type,
                              Annotation[] annotations, MediaType mediaType) {
        return true;
    }

    public DataObject readFrom(Class<DataObject> class1, Type type,
                               Annotation[] annotations, MediaType mediaType,
                               MultivaluedMap<String, String> multivaluedMap,
                               InputStream inputStream) {
        
        try
        {
            XMLDocument xmldocument = XMLHelper.INSTANCE.load(inputStream);        
            DataObject dos = xmldocument.getRootObject();
            return dos;
        }
        catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }
}

Now for the root resource, and we provide methods to get a list of concepts in the form of a URIList, get the data for a particular Concept, and update the values for a concepts. Delete and create are left as an exercise for the reader.

package org.concept.model.rest;


import commonj.sdo.helper.DataFactory;

import java.math.BigDecimal;

import java.net.URI;

import java.util.List;

import javax.annotation.Resource;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;

import oracle.jbo.common.service.types.FindControl;
import oracle.jbo.common.service.types.FindCriteria;

import org.concept.model.am.common.serviceinterface.ConceptModuleService;
import org.concept.model.vo.common.ConceptViewSDO;


@Path("/")
public class Root {

  @Resource(name = "ConceptServiceInterface")
  private ConceptModuleService service;
 

  @GET
  @Path("/concepts/")
  @Produces("application/xml")
  public Response getConcepts(
      @Context UriInfo info,
      @QueryParam("start") Integer start,
      @QueryParam("size") Integer size) {
    
    // If we don't have the start and end then redirect with one otherwise
    // the client could get a lot of data back
    //
    if (size==null) {
      return Response.seeOther(
        info.getRequestUriBuilder().queryParam("start", start!=null?start : 0)
                                   .queryParam("size", 10).build()).build();
    }
    
    //

    FindCriteria criteriaImpl =
      (FindCriteria)DataFactory.INSTANCE.create(FindCriteria.class);
    criteriaImpl.setFetchStart(start);
    criteriaImpl.setFetchSize(size);
    
    
    FindControl controlImpl =
      (FindControl)DataFactory.INSTANCE.create(FindControl.class);
    List list =
      service.findConceptView(criteriaImpl, controlImpl);

    // Build the uri list to return to the client
    //
    
    URI uriList[] = new URI[list.size()];
    int i = uriList.length;
    for (int j = 0; j < i; j++) {
      uriList[j] = info.getBaseUriBuilder().path("concept").path(
        list.get(j).getConceptId().toString()).build();
    }

    return Response.ok(new URIList(uriList)).build();
  }

  

  @GET
  @Path("/concepts/{concept}")
  @Produces("application/sdo+xml")
  public ConceptViewSDO getConcept(@PathParam("concept")
    BigDecimal conceptId) {
    ConceptViewSDO conceptView = service.getConceptView(conceptId);
    return conceptView;
  }


  @PUT
  @Path("/concepts/{concept}")
  @Consumes("application/sdo+xml")
  public void updateConcept(
    @PathParam("concept")
        BigDecimal conceptId,
    ConceptViewSDO concept) {

    // Check we are updating the correct concept
    //
    if (!conceptId.toBigInteger().equals(concept.getConceptId())) {
      throw new WebApplicationException(
              Response.Status.CONFLICT);
    }
    
    service.updateConceptView(concept);
  }


}

Now there is a lot more I could do for this resource, for example next and previous links to make it easier to iterate over the list. In the query method we should modify the FindCriteria to only return the ID attributes for performance reasons. We should also be providing action links for operations that we wish to perform on the concept entity. Finally since SDO allows you to easily generate a change summary it would be nice to be able to support the new HTTP PATCH method.

As I work on the project I will update this page as I add more features. There is some interesting hypermedia stuff coming in future versions of Jersey and I hope to be able to update this example to take this into account.

Monday, November 2, 2009

Debugging groovy in ADF

So I have completed my introduction to ADF course so will hopefully be able to start work on the side projects we have been given time for. It was interesting just how easy it was to do a lot of common things in ADF.

One this that did concern a lot of use was how to debug certain parts of ADF. Steve has a post on this topic but that doesn't say anything about the numerous cases where groovy is used as an expression language.

Since Groovy runs on the same VM as your application you can easily call out to external classes, so you simply need to create a handy debug class that returns the value it is passed in:

package project2;

public class Debug {
    public static <T> T debug(T object, Object... objects) {
        return object;
    }
}

You can then put this in your scripts, for example in this derived attribute on a view object:

You can then inspect both the value you have calculated along with any parameters you have passed in using the debugger:

It seems that by default the groovy scripts are not compiled to bytecode which is a shame as you can't step out into the groovy code; but this little hack can help in certain situations. Thank to my co worker Alan Davis for pointing out that there way a simpler method than generating a class with the client "breakpoint" bytecode in it :-).

Thursday, October 8, 2009

Project Dogfood, why I am going to spend 20% of my time writing ADF apps

Most of the people working on JDeveloper spend a lot of time writing in Java to support developers who build ADF applications. Unfortunately this doesn't give us developer a lot of daily experience building the sort of application that our customer build day to day using our tools. Particular those of use like myself who work on more general JEE technologies such as web services.

To try and fix this, and hopefully improve the development experience for all of our users, our gov'ner Ted has decreed in the next cycle that every part of of the JDeveloper development team will take time out to build real ADF apps.

To do this 20% of our working week has been set aside in the next development cycle. (The one after next from the customer's point of view) We all have to work in teams to build new and hopefully interesting applications. The parameters of the applications are not defined yet; but we should see some interesting projects coming out of the wood.

This is good news for me of course as it allow me broaden my skills, CometD here I come, and to take some time to escape from web services on day a week. It will be interesting to see how this works with the pressures of releasing production software; but I hope we do get the time as this will be a very interesting experiment. I will let you know how it goes.