JAX-RS Advanced FeaturesPage edited by Sergey BeryozkinChanges (2)
Full ContentJAX-RS : Advanced Features JMS SupportCXF has been designed such that multiple transports can be supported for a given endpoint. If you would like your JAXRS endpoint be capable of serving not only HTTP but also JMS requests then you need to specify a JMS transportId, example : <jaxrs:server serviceName="s:BookService" transportId="http://cxf.apache.org/transports/jms" address="/"> <jaxrs:serviceBeans> <bean class="org.apache.cxf.systest.jaxrs.JMSBookStore"/> </jaxrs:serviceBeans> </jaxrs:server>
If JMS messages are sent to topic destinations then one has to either set a "OnewayMessage" property or ensure that target JAXRS methods are annotated with org.apache.cxf.jaxrs.ext.Oneway. As far as REQUEST_URI is concerned, it is initially matched against a jaxrs:server/@address. So if REQUEST_URI is not set or set to "/" then jaxrs:server/@address has to be set to "/". If REQUEST_URI is set to "/bar/foo" and By referencing a bean such as 'org.apache.cxf.systest.jaxrs.JMSBookStore' from multiple jaxrs endpoints you can ensure that both HTTP and JMS requests are handled by the same service bean. In such cases you may want to use a CXF JAXRS specific ProtocolHeaders context which will let you get either HTTP or JMS headers. FIQL search queriesCXF JAXRS (since 2.3.0) lets users do the advanced search queries based on the Feed Item Query Language(FIQL). FIQL lets users express complex search expressions using an intuitive and URI friendly language. For example, a query such as ?_search=name==CXF;version=ge=2.2 lets users to search for all the Apache projects with the name 'CXF' and the version greater or equals to '2.2'. The initial '=' separates the name of the query '_search' from the FIQL _expression_, while '==' and '=ge=' convey 'equals to' and 'greater or equals to' respectively. More complex composite expressions can also be expressed easily enough. Note that either '_search' or '_s' query has to be used to pass the FIQL _expression_. Consuming FIQL queriesTo work with FIQL queries, a SearchContext needs be injected into an application code and used to retrieve a SearchCondition representing the current FIQL query. This SearchCondition can be used in a number of ways for finding the matching data. For example : @Path("books") public class Books { private Map<Long, Book> books; @Context private SearchContext context; @GET public List<Book> getBook() { SearchCondition<Book> sc = searchContext.getCondition(Book.class); // SearchCondition#isMet method can also be used to build a list of matching beans // iterate over all the values in the books map and return a collection of matching beans List<Book> found = sc.findAll(books.values()); return found; } } SearchCondition can also be used to get to all the search requirements (originally expressed in FIQL) and do some manual // find all conditions with names starting from 'ami' // and levels greater than 10 : // ?_s="name==ami*;level=gt=10" SearchCondition<Book> sc = searchContext.getCondition(Book.class); assertEquals("SELECT * FROM table WHERE name LIKE 'ami%' AND level > '10'", sq.toSQL("table")); The SearchCondition.toSQL() method has become deprecated in CXF 2.3.3 and 2.4.0. Using an org.apache.cxf.jaxrs.ext.search.sql.SQLPrinterVisitor is recommended as it will allow for building more advanced SQL expressions. For example: // ?_s="name==ami*;level=gt=10" SearchCondition<Book> sc = searchContext.getCondition(Book.class); SQLPrinterVisitor<Book> visitor = new SQLPrinterVisitor<Book>("table"); sc.visit(visitor); assertEquals("SELECT * FROM table WHERE name LIKE 'ami%' AND level > '10'", visitor.getResult()); Note that SQLPrinterVisitor can also be initialized with the names of columns and the field aliases map: // ?_s="level=gt=10" SearchCondition<Book> sc = searchContext.getCondition(Book.class); Map\<, String\> fieldMap = new HashMap\<String, String\>(); fieldMap.put("level", "LEVEL_FIELD"); SQLPrinterVisitor<Book> visitor = new SQLPrinterVisitor<Book>(fieldMap, "table", "LEVEL_COLUMN"); sc.visit(visitor); assertEquals("SELECT LEVEL_COLUMN FROM table WHERE LEVEL_COLUMN > '10'", visitor.getResult()); The fields map can help hide the names of the actual table columns/record fields from the Web frontend. Example, the users will know that the 'level' property is available while internally it will be converted to a LEVEL_COLUMN name. Custom visitors producing more optimized SQL or non-SQL expressions can also be introduced. Here is a possible code template to follow when a custom visitor needs to be written: public class CustomVisitor<T> impements SearchConditionVisitor<T> { private StringBuilder sb = new StringBuilder(); public void visit(SearchCondition<T> sc) { if (sb == null) { sb = new StringBuilder(); // start the _expression_ as needed } PrimitiveStatement statement = sc.getStatement(); if (statement != null) { // ex a > b // use statement.getValue() // use statement.getConditionType() // use statement.getProperty(); } else { for (SearchCondition<T> condition : sc.getSearchConditions()) { // pre-process condition.accept(this); // post-process } } } public String getResult() { // convert the internal state into String } } If needed you can access a FIQL query directly and delegate it further to your own custom FIQL handler: @Path("/search") public class SearchEngine { @Context private UriInfo ui; @GET public List<Book> findBooks() { MultivaluedMap<String, String> params = ui.getQueryParameters(); String fiqlQuery = params.getFirst("_s"); // delegate to your own custom handler } Building FIQL queriesCXF 2.4.0 introduces SearchConditionBuilder which makes it simpler to build FIQL queries. SearchConditionBuilder is an abstract class that returns a FIQL builder by default: SearchConditionBuilder b = SearchConditionBuilder.instance(); String fiqlQuery = b.is("id").greaterThan(123).query(); WebClient wc = WebClient.create("http://books.com/search"); wc.query("_s", fiqlQuery); // find all the books with id greater than 123 Collection books = wc.getCollection(Book.class); Here is an example of building more complex queries: // OR condition String ret = b.is("foo").greaterThan(20).or().is("foo").lessThan(10).query(); assertEquals("foo=gt=20,foo=lt=10", ret); // AND condition String ret = b.is("foo").greaterThan(20).and().is("bar").equalTo("plonk").query(); assertEquals("foo=gt=20;bar==plonk", ret); // Complex condition String ret = b.is("foo").equalTo(123.4).or().and( b.is("bar").equalTo("asadf*"), b.is("baz").lessThan(20)).query(); assertEquals("foo==123.4,(bar==asadf*;baz=lt=20.0)", ret); Using dates in queriesBy default, the date values have to have the following format: "yyyy-MM-dd'T'HH:mm:ss.SSSZ", for example: ?_search=time=le=2010-03-11T18:00:00.000+00:00 A simpler date format can be supported. Use "search.date-format" and "search.timezone.support" contextual properies, ex, "search.date-format"="yyyy-MM-dd'T'HH:mm:ss" and "search.timezone.support"="false" will let users avoid specifying milliseconds and timezones: ?_search=time=le=2010-03-11T18:00:00 At the moment, for custom date formats be recognized by SearchConditionBuilder, FIQLSearchConditionBuilder has to be created explicitly: Map<String, String> props = new HashMap<String, String>(); props.put("search.date-format", "yyyy-MM-dd'T'HH:mm:ss"); props.put("search.timezone.support", "false"); Date d = df.parse("2011-03-01 12:34:00"); FiqlSearchConditionBuilder bCustom = new FiqlSearchConditionBuilder(props); String ret = bCustom.is("foo").equalTo(d).query(); assertEquals("foo==2011-03-01T12:34:00", ret); Oneway invocationsResource methods with an org.apache.cxf.jaxrs.ext.Oneway annotation will be invoked oneway with the original request returning 202 HTTP status. HTTP or JMS clients can also add a "OnewayRequest" header if adding Oneway annotations is not an option. Support for ContinuationsPlease see this blog entry describing how JAXRS (and indeed) JAXWS services can rely on the CXF Continuations API. Please see the CXF Continuations page for more information. Server-side cachingEhcache-Web and other similar frameworks can be used to provide an advanced support for For example, the only thing you need to do to interpose Ehcache-Web on top of CXF JAX-RS endpoints is to add the following declarations to the web.xml, assuming the name of the war is 'ehcache-cxf': <context-param> <param-name>webAppRootKey</param-name> <param-value>ehcache-cxf</param-value> </context-param> <filter> <filter-name>SimplePageCachingFilter</filter-name> <filter-class>net.sf.ehcache.constructs.web.filter.SimplePageCachingFilter</filter-class> <init-param> <param-name>varyHeader</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>SimplePageCachingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> Please see the Ehcache-Web page for more information on how to configure it, here is one example: <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../main/config/ehcache.xsd" updateCheck="false" monitoring="autodetect" dynamicConfig="true"> <defaultCache maxElementsInMemory="10" eternal="false" timeToIdleSeconds="5" timeToLiveSeconds="10" overflowToDisk="true" /> <cache name="SimplePageCachingFilter" maxElementsInMemory="100" eternal="false" overflowToDisk="false" timeToIdleSeconds="5" timeToLiveSeconds="10" memoryStoreEvictionPolicy="LFU" /> </ehcache> This configuration has to be saved in ehcache-web.xml file and available as a class-path resource starting from the root. RESTful services without annotationsOne of the latest CXF JAX-RS extensions allows users to provide external models with the information which the runtime typically gets from JAX-RS annotations like @Path, @PathParam, @Consumes, @Produces, etc. User model schema type is described in the jaxrs.xsd. The top-level 'model' element can have 'resource' children elements. A 'resource' element describes a resource class which can be either a root resource class or a sub-resource one and it can have attributes describing 'path', 'produces' and 'consumes' values and it has a 'name' attribute which identifies a fully-qualified resource class. Starting from CXF 2.3.2-SNAPSHOT a "oneway" attribute can also be applied to individual operations. Here is an example : <model xmlns="http://cxf.apache.org/jaxrs"> <resource name="org.apache.cxf.systest.jaxrs.BookStoreNoAnnotations" path="bookstore" produces="application/json" consumes="application/json"> <operation name="getBook" verb="GET" path="/books/{id}" produces="application/xml"> <param name="id" type="PATH"/> </operation> <operation name="getBookChapter" path="/books/{id}/chapter"> <param name="id" type="PATH"/> </operation> <operation name="updateBook" verb="PUT"> <param name="book" type="REQUEST_BODY"/> </operation> </resource> <resource name="org.apache.cxf.systest.jaxrs.ChapterNoAnnotations"> <operation name="getItself" verb="GET"/> <operation name="updateChapter" verb="PUT" consumes="application/xml"> <param name="content" type="REQUEST_BODY"/> </operation> </resource> </model> This model describes two resources, BookStoreNoAnnotations and ChapterNoAnnotations. The BookStoreNoAnnotations resource has three resource operations, 'getBook', 'getBookChapter' and 'updateBook'. Note that the 'getBookChapter' operation element (described in the model) has no 'verb' attribute so runtime will identify it as a subresource locator. Additionally the BookStoreNoAnnotations resource declares that all its resource methods produce 'application/json' mediaTypes, while its 'getBook' method overrides its with its own 'produces' value. BookStoreNoAnnotations resource also has a 'consumes' attribute which requires all of the resource methods (such as 'updateBook') to consume "application/json" formats. The ChapterNoAnnotations 'updateChapter' resource operation requires 'application/xml' formats. You can use a comma-seperated list of media type values if needed, for example, produces("application/xml;charset=utf-8,application/json") or consumes("application/xml;charset=utf-8,application/json"). Please also see this model file for an example. Providing this file will let all implementations of the interface described in this model instance be exposed as RESTful services supported by the JAX-RS runtime. ConfigurationA user model can be referenced in a number of ways. It can be embedded in a jaxrs:server endpoint definition or linked to through a jaxrs:server modelRef attribute as a classpath resource. Please see this bean Spring configuration file, look at jaxrs server beans with 'bookservice6' and 'bookservice7' names. Note that when registering a model from Spring you do not need to declare a jaxrs server serviceBeans section - the runtime will instantiate the beans itself. If you do need to inject certain properties into your service bean from Spring then you do need to declare a service bean too. In this case this bean will be instantiated twice - once by the runtime during the model introspection and once by Spring, however in the end it will be the bean created by Spring that will be used, the one created by the runtime will be removed. Please have a look at this Spring bean. The jaxrs endpoint with id 'bookservice2' will have BookStoreWithNoAnnotations created twice but it will be the Spring created BookStoreWithNoAnnotations bean that will serve as a resource class instance. The jaxrs endpoint with id 'bookservice3' will have BookStoreWithNoAnnotationsImpl class instantiated only by Spring, with the model describing BookStoreWithNoAnnotationsInterface only that this class implements. You can also register a model programmatically, for example : JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean(); sf.setAddress("http://localhost:9080/"); String modelRef = "classpath:/org/apache/cxf/systest/jaxrs/resources/resources2.xml"; sf.setModelRef(modelRef); // or if you have interface classes described in the model already loaded, ex : OSGI // sf.setModelRefWithServiceClass(modelRef, BookStoreNoAnnotationsInterface.class); // register an actual bean only if the model describes interfaces sf.setServiceBeans(new BookStoreNoAnnotationsImpl()); Please also see this system test for the example of how model beans like UserResource can be created and registered programmatically. Similarly, you can register a user model on the client side, either from jaxrs:client or programmatically, example : JAXRSClientFactoryBean cf = new JAXRSClientFactoryBean(); cf.setAddress("http://localhost:9080/"); String modelRef = "classpath:/org/apache/cxf/systest/jaxrs/resources/resources2.xml"; sf.setModelRef(modelRef); BookStoreNoAnnotations proxy = cf.create(BookStoreNoAnnotations.class); At the moment it is only possible to register a user model with CXFNonSpringJAXRSServlet using the latest 2.2.3-SNAPSHOT like the way it is done in this web.xml. See CXFServlet3 and CXFServlet4 servlet declarations. Note that CXFServlet4 registers a model containing interfaces so it also registers a BookStoreNoAnnotationsImpl service class. The workaround is to create a custom servlet : public class JAXRSUserModelServlet extends CXFNonSpringJaxrsServlet { @Override public void loadBus(ServletConfig servletConfig) throws ServletException { super.loadBus(servletConfig); JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean(); String address = servletConfig.getInitParameter(SERVICE_ADDRESS_PARAM); //jaxrs.address if (address == null) { address = "/"; } sf.setAddress(address); // modelRef needs to start from 'classpath:', ex 'classpath:/WEB-INF/models/model1.xml String modelRef = servletConfig.getInitParameter("user.model"); sf.setModelRef(modelRef); sf.create(); }
Change Notification Preferences
View Online
|
View Changes
|
Add Comment
|
