/** * Copyright (C) 2010 Talend Inc. - www.talend.com */ package client; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.GregorianCalendar; import java.util.List; import java.util.Map; import java.util.Properties; import org.apache.cxf.interceptor.Interceptor; import org.apache.cxf.interceptor.transform.TransformInInterceptor; import org.apache.cxf.interceptor.transform.TransformOutInterceptor; import org.apache.cxf.jaxrs.client.ClientConfiguration; import org.apache.cxf.jaxrs.client.JAXRSClientFactory; import org.apache.cxf.jaxrs.client.WebClient; import org.apache.cxf.jaxrs.ext.xml.XMLSource; import org.apache.cxf.jaxrs.provider.JAXBElementProvider; /** * Demonstrates how the forward and backward compatibility between * the old and new SOAP and REST clients is achieved with the help * of the CXF STAX Transform Feature */ public class RESTClient { private static final String PORT_PROPERTY = "http.port"; private static final int DEFAULT_PORT_VALUE = 8080; private static final String HTTP_PORT; static { Properties props = new Properties(); try { props.load(RESTClient.class.getResourceAsStream("/client.properties")); } catch (Exception ex) { throw new RuntimeException("client.properties resource is not available"); } HTTP_PORT = props.getProperty(PORT_PROPERTY); } int port; public RESTClient() { port = getPort(); } /** * Old REST client uses old REST service */ public void useOldRESTService() throws Exception { List<Object> providers = createJAXRSProviders(); com.example.customerservice.CustomerService customerService = JAXRSClientFactory .createFromModel("http://localhost:" + port + "/examples/direct/rest", com.example.customerservice.CustomerService.class, "classpath:/model/CustomerService-jaxrs.xml", providers, null); System.out.println("Using old RESTful CustomerService with old client"); customer.v1.Customer customer = createOldCustomer("Smith Old REST"); customerService.updateCustomer(customer); customer = customerService.getCustomerByName("Smith Old REST"); printOldCustomerDetails(customer); } /** * New REST client uses new REST service */ public void useNewRESTService(String address) throws Exception { List<Object> providers = createJAXRSProviders(); org.customer.service.CustomerService customerService = JAXRSClientFactory .createFromModel(address, org.customer.service.CustomerService.class, "classpath:/model/CustomerService-jaxrs.xml", providers, null); System.out.println("Using new RESTful CustomerService with new client"); customer.v2.Customer customer = createNewCustomer("Smith New REST"); customerService.updateCustomer(customer); customer = customerService.getCustomerByName("Smith New REST"); printNewCustomerDetails(customer); } /** * New REST client uses old REST service */ public void useOldRESTServiceWithNewClient() throws Exception { List<Object> providers = createJAXRSProviders(); org.customer.service.CustomerService customerService = JAXRSClientFactory .createFromModel("http://localhost:" + port + "/examples/direct/rest", org.customer.service.CustomerService.class, "classpath:/model/CustomerService-jaxrs.xml", providers, null); // The outgoing new Customer data needs to be transformed for // the old service to understand it and the response from the old service // needs to be transformed for this new client to understand it. ClientConfiguration config = WebClient.getConfig(customerService); addTransformInterceptors(config.getInInterceptors(), config.getOutInterceptors(), true); System.out.println("Using old RESTful CustomerService with new client"); customer.v2.Customer customer = createNewCustomer("Smith New to Old REST"); customerService.updateCustomer(customer); customer = customerService.getCustomerByName("Smith New to Old REST"); printNewCustomerDetails(customer); } /** * New REST client uses old REST service */ public void useOldRESTServiceWithNewClientAndXPath() throws Exception { List<Object> providers = createJAXRSProviders(); String address = "http://localhost:" + port + "/examples/direct/rest/customerservice"; WebClient client = WebClient.create(address, providers); // The outgoing new Customer data needs to be transformed for // the old service to understand it and the response from the old service // needs to be transformed for this new client to understand it. ClientConfiguration config = WebClient.getConfig(client); addTransformInterceptors(config.getInInterceptors(), config.getOutInterceptors(), true); System.out.println("Consuming old RESTful CustomerService with new client and XPath"); customer.v2.Customer customer = createNewCustomer("Smith New to Old REST, XPath"); client.path("customer"); client.put(customer); XMLSource source = client.query("name", "Smith New to Old REST, XPath").get(XMLSource.class); customer.v2.Customer xmlCustomer = source.getNode("/ns:customer", Collections.singletonMap("ns", "http://customer/v2"), customer.v2.Customer.class); printNewCustomerDetails(xmlCustomer); } /** * Old REST client uses new REST service */ public void useNewRESTServiceWithOldClient() throws Exception { List<Object> providers = createJAXRSProviders(); com.example.customerservice.CustomerService customerService = JAXRSClientFactory .createFromModel("http://localhost:" + port + "/examples/direct/new-rest", com.example.customerservice.CustomerService.class, "classpath:/model/CustomerService-jaxrs.xml", providers, null); // The outgoing old Customer data needs to be transformed for // the new service to understand it and the response from the new service // needs to be transformed for this old client to understand it. ClientConfiguration config = WebClient.getConfig(customerService); addTransformInterceptors(config.getInInterceptors(), config.getOutInterceptors(), false); System.out.println("Using new RESTful CustomerService with old Client"); customer.v1.Customer customer = createOldCustomer("Smith Old to New REST"); customerService.updateCustomer(customer); customer = customerService.getCustomerByName("Smith Old to New REST"); printOldCustomerDetails(customer); } /** * Old REST client uses new REST service with the * redirection to the new endpoint and transformation * on the server side */ public void useNewRESTServiceWithOldClientAndRedirection() throws Exception { List<Object> providers = createJAXRSProviders(); com.example.customerservice.CustomerService customerService = JAXRSClientFactory .createFromModel("http://localhost:" + port + "/examples/old/rest-endpoint", com.example.customerservice.CustomerService.class, "classpath:/model/CustomerService-jaxrs.xml", providers, null); System.out.println("Using new RESTful CustomerService with old client and the redirection"); customer.v1.Customer customer = createOldCustomer("Smith Old to New REST With Redirection"); customerService.updateCustomer(customer); customer = customerService.getCustomerByName("Smith Old to New REST With Redirection"); printOldCustomerDetails(customer); } /** * Prepares transformation interceptors for a client. * * @param clientConfig the client configuration * @param newClient indicates if it is a new/updated client */ private void addTransformInterceptors(List<Interceptor<?>> inInterceptors, List<Interceptor<?>> outInterceptors, boolean newClient) { // The old service expects the Customer data be qualified with // the 'http://customer/v1' namespace. // The new service expects the Customer data be qualified with // the 'http://customer/v2' namespace. // If it is an old client talking to the new service then: // - the out transformation interceptor is configured for // 'http://customer/v1' qualified data be transformed into // 'http://customer/v2' qualified data. // - the in transformation interceptor is configured for // 'http://customer/v2' qualified response data be transformed into // 'http://customer/v1' qualified data. // If it is a new client talking to the old service then: // - the out transformation interceptor is configured for // 'http://customer/v2' qualified data be transformed into // 'http://customer/v1' qualified data. // - the in transformation interceptor is configured for // 'http://customer/v1' qualified response data be transformed into // 'http://customer/v2' qualified data. // - new Customer type also introduces a briefDescription property // which needs to be dropped for the old service validation to succeed // this configuration can be provided externally Map<String, String> newToOldTransformMap = Collections.singletonMap("{http://customer/v2}*", "{http://customer/v1}*"); Map<String, String> oldToNewTransformMap = Collections.singletonMap("{http://customer/v1}*", "{http://customer/v2}*"); TransformOutInterceptor outTransform = new TransformOutInterceptor(); outTransform.setOutTransformElements(newClient ? newToOldTransformMap : oldToNewTransformMap); if (newClient) { outTransform.setOutDropElements( Collections.singletonList("{http://customer/v2}briefDescription")); } TransformInInterceptor inTransform = new TransformInInterceptor(); inTransform.setInTransformElements(newClient ? oldToNewTransformMap : newToOldTransformMap); inInterceptors.add(inTransform); outInterceptors.add(outTransform); } /** * Creates a custom JAX-RS JAXBElementProvider which can handle * generated JAXB clasess with no XmlRootElement annotation * @return providers */ private List<Object> createJAXRSProviders() { JAXBElementProvider provider = new JAXBElementProvider(); provider.setUnmarshallAsJaxbElement(true); provider.setMarshallAsJaxbElement(true); List<Object> providers = new ArrayList<Object>(); providers.add(provider); return providers; } /** * namespace: {http://customer}v1 */ private customer.v1.Customer createOldCustomer(String name) { customer.v1.Customer cust = new customer.v1.Customer(); cust.setName(name); cust.getAddress().add("Pine Street 200"); Date bDate = new GregorianCalendar(2009, 01, 01).getTime(); cust.setBirthDate(bDate); cust.setNumOrders(1); cust.setRevenue(10000); cust.setShares(new BigDecimal(1.5)); cust.setType(customer.v1.CustomerType.BUSINESS); return cust; } /** * namespace: {http://customer}v2 * new simple element: briefDescription */ private customer.v2.Customer createNewCustomer(String name) { customer.v2.Customer cust = new customer.v2.Customer(); cust.setName(name); cust.getAddress().add("Pine Street 200"); Date bDate = new GregorianCalendar(2009, 01, 01).getTime(); cust.setBirthDate(bDate); cust.setNumOrders(1); cust.setRevenue(10000); cust.setShares(new BigDecimal(1.5)); cust.setType(customer.v2.CustomerType.BUSINESS); cust.setBriefDescription("Business Customer"); return cust; } private void printOldCustomerDetails(customer.v1.Customer customer) { System.out.print("Name : " + customer.getName()); System.out.print(", orders : " + customer.getNumOrders()); System.out.print(", shares : " + customer.getShares()); System.out.println(); } private void printNewCustomerDetails(customer.v2.Customer customer) { System.out.print("Name : " + customer.getName()); System.out.print(", orders : " + customer.getNumOrders()); System.out.print(", shares : " + customer.getShares()); System.out.println(); } private static int getPort() { try { return Integer.valueOf(HTTP_PORT); } catch (NumberFormatException ex) { // ignore } return DEFAULT_PORT_VALUE; } public static void main(String args[]) throws Exception { RESTClient client = new RESTClient(); // Scenario 1: // - Old clients have become aware that the new endpoints have been deployed. // They continue talking to the old endpoints which have not been stopped yet // otherwise they are configured such that they can talk to new endpoints // - New clients talk to the new endpoints but in some cases where new endpoints // have not been introduced yet, they are configured such that they can talk to // the old endpoints System.out.println("Scenario 1: Old and new clients are configured" + " to invoke on the new and old endpoints respectively"); // JAX-RS: Scenario 1 System.out.println("JAX-RS:"); System.out.println(); // Old Client uses Old Service client.useOldRESTService(); System.out.println(); // New Client uses Old Service client.useOldRESTServiceWithNewClient(); System.out.println(); // New Client uses Old Service, XPath is used to consume the response client.useOldRESTServiceWithNewClientAndXPath(); System.out.println(); // New Client uses New Service client.useNewRESTService("http://localhost:" + RESTClient.getPort() + "/examples/direct/new-rest"); System.out.println(); // Old Client uses New Service client.useNewRESTServiceWithOldClient(); System.out.println(); // Scenario 2: // Old clients are unaware of the fact that the old endpoint has been removed // and the new endpoint has been introduced. // Old Client thinks Old Service is still being used but // on the server the request will be redirected to the New Service endpoint // Similarly, the new clients can be redirected to the old endpoints on the server // if no new endpoints have been introduced yet in a given destination System.out.println("Scenario 2: Old clients invoke on the new endpoints" + " without being aware of it"); // JAX-RS: Scenario 2 System.out.println("JAX-RS:"); System.out.println(); // Old Client uses New Service without being aware of it client.useNewRESTServiceWithOldClientAndRedirection(); System.out.println(); // New Client uses New Service, the same new endpoint handles // new and redirected old client requests client.useNewRESTService("http://localhost:" + RESTClient.getPort() + "/examples/new/rest-endpoint"); System.exit(0); } }