REST easy with the JavaBeans Activation Framework

Find out what makes the JavaBeans Activation Framework a perfect vehicle for RESTful data transmission, then put JAF to work in a sample Web application that tracks requests and responses through a Java Servlet-based system[@more@]

Representational State Transfer, also known as REST, is a style of software architecture defined by a collection of architectural principles. REST is applied specifically to such distributed systems as the Web, and stipulates mechanisms for defining and accessing resources. The term, originally described in a dissertation by Roy Fielding, often is used to describe any framework that transmits data over a protocol, such as HTTP, without additional semantic layers or session management.

The goal of REST is to simplify the implementation of actors in a client/server system by maintaining a strict separation of concerns. REST also seeks to simplify communication semantics, thereby improving performance and increasing the scalability of server components. REST relies on stateless interaction between requests, in which various media types are exchanged using standard message verbs.

REST can be implemented for the HTTP protocol using the Java programming language and JAF. In this article I introduce the basics of REST and JAF, then walk you through a REST implementation using JAF.

Note that Sun provides binaries of a JAF reference implementation that can be used to develop JAF components and clients on any Java 2 Standard Edition platform Version 1.4 or later.

REST for beginners

In a REST implementation, resources, such as items in a shopping cart, are addressed via uniform resource identifiers (URI). A given URI is used to access the representational state of a resource, and also to modify that resource. For example, let's say a widget manufacturer uses REST to make its widgets available for consumption. At any given time, the manufacturer can present Web URLs containing descriptive information about the widgets. Widget consumers then need to know only a widget's URL to read the information. If authorized, the consumer also could modify the information as needed. The following illustrates a typical REST URI:

http://www.example.com/widgets/foobar/E4456SE321DAACF567

Notice how the URI defines the location of the "foobar" widget without stipulating an action to take. This is the goal of a URI in REST -- to represent a resource with a URI and to leave action semantics up to the underlying protocol.

One of the defining principles of REST is to exploit already existing technologies and Web protocols, in particular, HTTP. This makes REST simpler to use than other Web-based message processes, such as Simple Object Access Protocol (SOAP). A REST implementation requires no additional overhead to provide or consume information. According to Fielding, a REST-based conversation is stateless between requests, thereby supporting such subscription-based technologies as RSS, in which Web-site content is described and delivered to interested clients.

Entities of a REST implementation

REST is an abstraction of fundamental architectural entities within a distributed system. REST defines these entities as follows:

  • Data elements: Resources, resource identifiers (URIs and URLs), and resource representations, such as HTML documents, images and XML documents.
  • Components: Origin servers, gateways, proxies and user agents.
  • Connectors: Clients, servers, caches, and so forth.

Figure 1 illustrates some of the entities of REST and how they might participate within a simple enterprise system.

Entities of REST
Figure 1. REST in a simple enterprise system

In a system like the one above, connectors would be embodied as ports to enable communication among the components over each given protocol. Also note that any component can have multiple client and server connectors to handle communication between itself and other client or server components.

REST and HTTP

According to Fielding's dissertation, representational state for resources in an HTTP-based REST system should be accessed using the standard HTTP methods. What follows is a simple breakdown of these methods:

  • GET is used to transfer the current representational state of a resource from a server to a client.
  • PUT is used to transfer the modified representational state of a resource from the client to the server.
  • POST is used to transfer the new representational state of a resource from the client to the server.
  • DELETE is used to transfer information needed to change a resource to a deleted representational state.

The four standard HTTP methods are supported by Java language and server technologies, such as servlets and JavaServer Pages (JSP). You can also use JAF to extend these technologies to support a REST-based system.

What is JAF?

JAF is a standard extension to the Java platform. It presents a framework of Java-based APIs and components in which information about data presented by Java objects or beans can be recognized easily and accessed.

JAF defines APIs that are used to register and discover data by content or Multi-purpose Internet Mail Extensions (MIME) type. Standard methods defined by JAF components then can be used to instantiate data handlers, on which commands and operations are performed to access and modify the data.

Simply put, JAF does the following:

  • Lets arbitrary data for a Java object or bean be registered and discovered according to content or MIME type using consistent APIs.
  • Presents standard methods for discovering the operations and commands that are available for data encapsulated by a Java object or bean.
  • Presents data handlers supporting commands and methods to access and modify date encapsulated by a Java object or bean.

With JAF, developers use standard APIs to determine the content type of data for a given resource encapsulated by a Java object or bean. For example, an image resource in JAF might be identified by a content or MIME type of image/png. A DataHandler component representing the image then could be used to access and modify the content of the image using the same standard streaming operations that would be used to access and modify content from a component representing an XML document, that is, a component with a content type of application/atom+xml.

Components of the JAF architecture

Figure 2 shows the primary components defined by the JAF architecture.

Components defined by the JAF architecture
Figure 2. Primary components defined by the JAF architecture

As shown in Figure 2, the DataHandler class provides a standard interface between JAF clients and resources.

The DataSource component encapsulates data for a resource by providing stream access to the data and by providing the content or MIME type describing the data. JAF provides two DataSource implementations: FileDataSource, representing file-system data, and URLDataSource, representing data located by a given URL.

DataContentHandler components can be implemented to provide cut-and-paste capabilities to a given DataHandler via the java.awt.datatransfer.Transferable interface.

The CommandMap object represents a registry of command/verbs available for specific MIME types supported by a resource. The CommandMap registry lets a DataHandler component retrieve JavaBeans, which implement the CommandObject interface, to operate on a representation of a resource supporting a particular MIME type.

Once a DataHandler for a given resource is instantiated, a DataSource object is created and associated with the DataHandler. The DataSource object provides content-type information to the DataHandler component. A CommandMap object can be associated with the DataHandler component if the developer so chooses. The CommandMap object is used to associate a collection of verb/JavaBean pairs with a DataHandler. The verb/JavaBean pairs are used by a DataHandler to present components to clients for handling customized commands for a given resource.

Resources and data elements in REST

Most distributed systems offer an architect only three options for exposing the data of a resource:

  1. Render the data where it is located and send a fixed-format image to the recipient.
  2. Encapsulate the data with a rendering engine and send both to the recipient.
  3. Send the raw data to the recipient along with metadata that describes the data type, so the recipient can choose his or her own rendering engine.

According to Fielding, REST provides a hybrid of these three data-rendering options by "focusing on a shared understanding of data types with metadata, but limiting the scope of what is revealed to a standardized interface."

Data elements in REST are summarized as:

  • Resources
  • Resource identifiers
  • Representations
  • Representation metadata
  • Resource metadata
  • Control data

REST components act on a resource by using a URI-based representation to acquire a snapshot of the current state of the resource and transmitting that snapshot between components. A representation in REST is defined as a sequence of bytes, plus representation metadata describing those bytes. The format of a resource representation is defined by its MIME or media type. It is this key aspect of REST that makes JAF and the MIME-defined aspects of JAF such great facilitators of a REST data-element implementation.

Now that you've learned a bit about REST and JAF, we'll look at a sample Web application that tracks REST-based requests and responses through a simple Java servlet-based system.

Implementing REST with JAF

In the next sections I will develop a REST implementation using JAF. I'll start by modeling a simple system of components around the primary entities defined by REST, after which I'll construct a Java application using servlets and JAF. For now, consider the class diagram in Figure 3, which offers a high-level view of the Java classes and interfaces that make up the JAF-based REST implementation for the sample application.

Java classes and interfaces comprising the JAF-based REST implementation.
Figure 3. Classes and interfaces of a JAF-based REST implementation

As the class diagram shows, a FrontController servlet interacts with most of the primary REST entities, which are represented as Java interfaces. The following sections discuss each of the primary REST entities and the Java components that represent them.

Java components for a REST implementation

REST components are entities that are defined by the role they play in a given enterprise system. Typical components in a REST system are as follows:

User agents
A user agent initiates requests to some form of server component. The user agent then handles the response from the server component. A user agent can be a Web browser, a mobile device or another type of Web-enabled client.
Origin servers
An origin server embodies the namespace for a given collection of resources. An origin server is the source of representations of the state of the resources it embodies. An origin server receives requests from user agents, performs the necessary actions on the targeted resources, and responds to the user agent with the appropriate representational state for the resource and the actions performed by the origin server. An origin server typically exposes resources using a hierarchical interface.
Intermediary components
Intermediary components forward requests and responses to and from user agents and/or server components. An intermediary component might perform some type of transformation on a request or response as needed by a given system or application, such as data conversion, authentication and authorization, or performance optimization. Proxies and gateways are typical intermediary components.

In Java, you might represent a component using a marker interface, like so:

public interface Component
{
}

A server component in Java can expose the properties and methods by which resource contexts are managed, as illustrated in Listing 1.

Listing 1. ServerComponent exposes properties and methods
public abstract class ServerComponent
  implements Component
{
  private java.util.HashMap contexts =
    new java.util.HashMap();
  
  abstract public Context addContext(String contextRootPath, String contextPath);
  
  public java.util.Iterator getContextPaths()
  {
    return contexts.keySet().iterator();
  }
  
  public Context getContext(String contextPath)
  {
    return contexts.get(contextPath);
  }
  
  protected java.util.HashMap getContexts()
  {
    return contexts;
  }
}


An HTTP-specific server component can expose HTTP-specific contexts, as shown in Listing 2.

Listing 2. ServerComponent exposes HTTP-specific contexts
public Context addContext(String contextRootPath, String contextPath)
  {
    Context context = getContexts().get(contextPath);
    if (context == null)
    {
      context = new HTTPContext(contextRootPath, contextPath);
      getContexts().put(contextPath, context);
    }
    
    return context;
  }

An HTTP-specific context implements the Context interface, where each HTTP method is handled and resource representations are manipulated and accessed. Listing 3 shows a REST implementation representing the HTTP GET method. Note that the details of business logic for a particular vertical domain are encapsulated within individual business services.

Listing 3. A REST implementation representing HTTP GET
public Representation handleGet(Request request)
    throws ContextRequestException
  {
    String uri = ((HTTPRequest)request).getRequest().getRequestURI();
    if (uri.startsWith(contextPath))
    {
      uri = reqURI.substring(contextPath.length());
    }

    BusinessService businessService =
      ServiceLocator.locateService(contextRootPath, contextPath, uri);

    Representation representation = null;
    
    try
    {
      representation = businessService.read(request);
    }
    catch (ServiceExecutionException e)
    {
      e.printStackTrace();
      throw new ContextRequestException(e);
    }
    
    return representation;
  }

REST components use connectors to interact with each other.

Connectors in REST

A connector is a software entity exposing a generic interface that enables communication among REST components. A connector typically represents one endpoint or port of a network communication protocol. A connector can be a client connector or a server connector. User agents use client connectors to instigate requests to an origin server. Origin servers use server connectors to receive requests from user agents and to respond with representational state for targeted resources.

A Java implementation of a connector can be represented by the following interface:

public interface Connector
{
  public ServerComponent addServer(int port);
}

A factory class can be used to manage protocol-specific connectors in Java. Each connector can be pooled if needed. Listing 4 is an example of a Connector factory class.

Listing 4. A Connector factory class
public static Connector getConnector(Protocol protocol)
    throws ConnectorException
  {
    String key = protocol.getScheme();
    Connector instance = instances.get(key);
    
    if (instance == null)
    {
      if (key.equalsIgnoreCase(Protocol.HTTP.getScheme()))
      {
        instance = new HTTPConnector();
        instances.put(key, instance);
      }
      else
      {
        throw new ConnectorException("Invalid protocol: " + protocol.getScheme());
      }
    }
    
    return instance;
  }

An HTTP-specific server Connector implements the Connector interface and exposes HTTP-specific server components for each given port, as illustrated here:

public ServerComponent addServer(int port)
  {
    return new HTTPServerComponent(port);
  }


Connectors facilitate communication among client components and server components. Server components represent resources that can be implemented as discussed in the next section.

Resource representation

For each client request, a URI is passed that identifies the targeted resource for the request. Once a server component receives a request, it reads the URI, performs any necessary actions to carry out the request for a resource, and returns the resulting representational state of the resource.

The flow for instantiating necessary components and accessing each to handle a typical request from a client to server and back again could be as follows:

  • Create a connector and add to it an HTTP server component.
  • Add the context for a given resource.
  • Dispatch incoming client requests received by a server component to the appropriate business service.
  • Encapsulate the model returned from the business service as a resource representation.
  • Respond to the client with the state of the representation.

Figure 4 illustrates this flow of control.

A RESTful client-server request-response cycle.
Figure 4. A RESTful client-server request-response cycle

JAF's javax.activation.DataHandler class exposes a component that can embody representations of resources. Using the DataHandler class lets you present multiple types of resources of differing content types with a standard interface.

Listing 5 shows an abstract class that extends the DataHandler class to encapsulate a standard base class for objects representing a wide array of resources.

Listing 5. Representation encapsulates resource objects
public abstract class Representation extends javax.activation.DataHandler
{
  public Representation(DataSource dataSource)
  {
    super(dataSource);
  }
}

JAF's DataSource class

JAF's javax.activation.FileDataSource class implements the javax.activation.DataSource interface to expose a simple object representing a file resource.

Listing 6 shows a specific implementation of the Representation interface that encapsulates file resources.

Listing 6. Using Representation to encapsulate file resources
public class FileRepresentation extends Representation
{
  public FileRepresentation(File file)
  {
    super(new FileDataSource(file));
  }
  
  public FileDataSource getDataSource()
  {
    try
    {
      return this.getDataSource();
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
    
    return null;
  }
  
  public File getFile()
  {
    try
    {
      return this.getDataSource().getFile();
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
    
    return null;
  }
}

The FileDataSource class provides content-typing using a FileTypeMap object. The FileTypeMap class relies on a default, MIME-based map to determine content types. Customization of the default content types can be facilitated using the setFileTypeMap method.

JAF content types for REST resources

Implementations of the FileTypeMap class implement the abstract getContentType methods to return the content type of a resource using whatever mechanism is appropriate for the resource. This might entail reading the file extension of a file resource or opening the file and reading its contents to determine the content type. The FileDataSource class uses the default FileTypeMap to determine the content type of file resources. The default FileTypeMap is an instance of the MimetypesFileTypeMap class.

The MimetypesFileTypeMap class extends FileTypeMap and determines the content type of files by mapping their file extensions. The default mechanism employed by the MimetypesFileTypeMap to find the extension mapping is as follows:

  • Use any entries programmatically added to the MimetypesFileTypeMap instance.
  • Look for a file named "mime.types" in the user's home directory.
  • Look for a file named "mime.types" in the /lib directory.
  • Look for a file named "mime.types" in the META-INF directory of the resource classpath.
  • Look for a file named "mime.types.default" in the META-INF directory of the resource classpath.
  • The file or resource named META-INF/mimetypes.default. This is usually provided by the activation.jar file installed with the JAF reference implementation.

Listing 7 is an example of the MIME-types mapping file.

Listing 7. A MIME-types mapping file
MIME Type             File Extension
========================================
text/html              html htm HTML HTM
text/plain             txt text TXT TEXT
image/gif              gif GIF
image/ief              ief
image/jpeg             jpeg jpg jpe JPG
image/tiff             tiff tif
image/png              png PNG
image/x-xwindowdump    xwd
application/postscript ai eps ps
application/rtf        rtf
application/x-tex      tex
application/x-texinfo  texinfo texi
application/x-troff    t tr roff
audio/basic            au
audio/midi             midi mid
audio/x-aifc           aifc
audio/x-aiff           aif aiff
audio/x-mpeg           mpeg mpg
audio/x-wav            wav
video/mpeg             mpeg mpg mpe
video/quicktime        qt mov
video/x-msvideo        avi

REST request and response handling

Once components, connectors, and resource representation classes and interfaces are implemented, a FrontController servlet is provided to receive incoming client requests and respond to clients with the representation state of the resources that it manages.

Listing 8 shows a sample implementation of the standard doGet method of the HttpServlet class. In this example, doGet handles a REST-based HTTP GET method.

Listing 8. doGet handles a REST-based HTTP GET
protected void doGet(HttpServletRequest request,
                       HttpServletResponse response)
    throws ServletException,
           IOException
  {
    HTTPConnector connector =
      (HTTPConnector)Connectors.getConnector(Protocol.HTTP);
    HTTPServerComponent serverComponent =
      (HTTPServerComponent)connector.addServer(request.getServerPort());

    String contextRootPath = this.getServletContext().getRealPath("/");
    String contextPath = request.getContextPath() + "/" + this.getServletName();
    serverComponent.addContext(contextRootPath, contextPath);

    Representation representation = null;
    
    try
    {
      representation =
        serverComponent.getContext(request,
                                   this).handleGet(new HTTPRequest(request));
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new ServletException("Error: " + e);
    }
    
    new HTTPResponse(response).write(representation.getContentType(),
                                     representation.getInputStream());
  }

Services to handle REST-based business logic

A BusinessService class can be employed to encapsulate resource representations for specific-domain business logic. The BusinessService classes should embody the implementations for each resource representation using an implementation of the javax.activation.DataHandler class and the javax.activation.DataSource interface that is appropriate for the content of the model generated from the service's business logic.

An example of a simple business service that encapsulates file resources is shown in Listing 9.

Listing 9. A business service encapsulating file resources
public class SimpleBusinessService
  implements BusinessService
{
  // todo: read RESOURCES_DIR from config or system property
  //
  private static final String RESOURCES_DIR = "resources";
  
  private String contextRootPath = "";
  private String contextPath = "";
  
  public SimpleBusinessService(String contextRootPath,
                               String contextPath)
  {
    this.contextRootPath = contextRootPath;
    this.contextPath = contextPath;
  }
  
  protected String filePathFromRequest(Request httpReq)
  {
    return contextRootPath + RESOURCES_DIR
           + ((HTTPRequest)httpReq).getRequest().getPathInfo();
  }

  protected void writeToFile(Request request, File file, boolean append)
      throws IOException,
             FileNotFoundException
  {
    System.out.println("writeToFile called");
    
    InputStream inStream = ((HTTPRequest)request).getInputStream();
    byte[] dataBuf = new byte[4096];
                                                                  
    FileOutputStream outStream = new FileOutputStream(file, append);
    int bytesRead = 0;
    while ((bytesRead = inStream.read(dataBuf)) > 0)
    {
      System.out.println("Writing [" + bytesRead + " bytes]");
      outStream.write(dataBuf, 0, bytesRead);
    }
        
    outStream.flush();
    outStream.close();
  }

  public Representation create(Request request)
    throws ServiceExecutionException
  {
    System.out.println("SimpleBusinessService.create()");
    
    String filePath = filePathFromRequest(request);
    File file = new File(filePath);

    try
    {
      boolean append = false;
      writeToFile(request, file, append);
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new ServiceExecutionException(e);
    }
    
    FileRepresentation representation = new FileRepresentation(file);
    
    return representation;
  }

  public Representation read(Request request)
    throws ServiceExecutionException
  {
    System.out.println("SimpleBusinessService.read()");
    
    String filePath = filePathFromRequest(request);
    File file = new File(filePath);
    if (file.exists() == false)
    {
      throw new ServiceExecutionException("File [" + filePath + "] does not exist.");
    }
    
    FileRepresentation representation = new FileRepresentation(file);
    
    return representation;
 }
  
  public Representation update(Request request)
    throws ServiceExecutionException
  {
    System.out.println("SimpleBusinessService.update()");
    
    String filePath = filePathFromRequest(request);
    File file = new File(filePath);
    FileRepresentation representation = new FileRepresentation(file);
    
    try
    {
      boolean append = (file.exists() ? true : false);
      writeToFile(request, file, append);
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new ServiceExecutionException(e);
    }
    
    return representation;
  }
  
  public void delete(Request request)
    throws ServiceExecutionException
  {
    System.out.println("SimpleBusinessService.delete()");
    
    String filePath = filePathFromRequest(request);
    System.out.println("SimpleBusinessService.delete() - resolving file ["
                       + filePath
                       + "]");    
    File file = new File(filePath);
    System.out.println("SimpleBusinessService.delete() - file resolved");
    if (file.exists())
    {
      System.out.println("SimpleBusinessService.delete() - deleting file");

      if (file.delete() == false)
      {
        System.out.println("SimpleBusinessService.delete() - file deletion failed");
        throw new ServiceExecutionException("Error deleting file ["
                                            + filePath + "]");
      }
      System.out.println("SimpleBusinessService.delete() - file deletion succeeded");
    }
    else
    {
      System.out.println("SimpleBusinessService.delete() - file ["
                         + filePath
                         + "] does not exist");    
    }
  }
}

As previously mentioned, business services should represent the data models generated by their specific logic using implementations of the JAF classes and interfaces that are appropriate for the content of the models. This might entail database resources, dynamically generated resources, file system resources and others.

In conclusion

REST is a style of software architecture and a collection of architecture principles for distributed systems that stipulates mechanisms for defining and accessing resources. REST can be used to describe a framework that transmits data over a protocol, such as HTTP, without additional semantic layers or session management. REST defines a separation of concerns and stateless conversations that simplify the actors and communication semantics in a distributed system. Within REST, media types are exchanged using standard message verbs.

JAF is a standard extension to the Java platform that presents a framework of Java-based APIs and components in which resources represented by Java objects or beans are recognized and accessed. JAF defines APIs through which data is registered and discovered by content or MIME type. These mechanisms make JAF and the MIME-defined aspects of JAF great facilitators of a REST data-element implementation.

In this article I have introduced you to REST and JAF. Using a sample Web application that tracks requests and responses through a Java servlet-based system, I have shown how JAF facilitates the exchange of data in REST-based systems. See the Resources section to learn more about REST and JAF.

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/71047/viewspace-996761/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/71047/viewspace-996761/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值