2015年7月10日星期五

Fuse_007:Fuse 快速上手之七:rest

环境:JBoss Fuse 6.2.0

1. 学习重点
(1)开发 Restful Web Service,实现CRUD。

2. blueprint.xml 
<?xml version="1.0" encoding="UTF-8"?>
<!--
    JBoss, Home of Professional Open Source
    Copyright 2014, Red Hat, Inc. and/or its affiliates, and individual
    contributors by the @authors tag. See the copyright.txt in the
    distribution for a full listing of individual contributors.

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at
    http://www.apache.org/licenses/LICENSE-2.0
    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
-->
<!--
   This is the OSGi Blueprint XML file defining the CXF JAX-RS beans.  Because the file is in the
   OSGI-INF/blueprint directory inside our JAR, it will be automatically activated as soon as the artifact is installed.

   The root element for any OSGi Blueprint file is 'blueprint' - you also see the namespace definitions for both the Blueprint
   and the CXF JAX-RS namespaces.
-->
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jaxrs="http://cxf.apache.org/blueprint/jaxrs"
    xmlns:cxf="http://cxf.apache.org/blueprint/core"
    xsi:schemaLocation="
      http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
      http://cxf.apache.org/blueprint/jaxrs http://cxf.apache.org/schemas/blueprint/jaxrs.xsd
      http://cxf.apache.org/blueprint/core http://cxf.apache.org/schemas/blueprint/core.xsd">

    <!--
      The <jaxrs:server/> element sets up our JAX-RS services.  It defines:
      - the server's address, '/crm', relative to the default CXF servlet URI
        with the default settings, the server will be running on 'http://localhost:8181/cxf/crm'
      - a list of service beans
        in this example, we refer to another bean defined in this Blueprint XML file with a <ref/> element
    -->
    <jaxrs:server id="customerService" address="/crm">
        <jaxrs:serviceBeans>
            <ref component-id="customerSvc"/>
        </jaxrs:serviceBeans>
        <jaxrs:providers>
           <bean class="com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider"/>
        </jaxrs:providers>
    </jaxrs:server>

    <cxf:bus>
        <cxf:features>
          <cxf:logging />
        </cxf:features>
    </cxf:bus>


    <!--
      We are using the OSGi Blueprint XML syntax to define a bean that we referred to in our JAX-RS server setup.
      This bean carries a set of JAX-RS annotations that allow its methods to be mapped to incoming requests.
    -->
    <bean id="customerSvc" class="io.fabric8.quickstarts.rest.CustomerService"/>

</blueprint>

3. CustomerService.java
/*
 * JBoss, Home of Professional Open Source
 * Copyright 2014, Red Hat, Inc. and/or its affiliates, and individual
 * contributors by the @authors tag. See the copyright.txt in the
 * distribution for a full listing of individual contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.fabric8.quickstarts.rest;

import javax.annotation.Resource;
import javax.ws.rs.core.Context;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;

import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiOperation;
import com.wordnik.swagger.annotations.ApiParam;
import com.wordnik.swagger.annotations.ApiResponses;
import com.wordnik.swagger.annotations.ApiResponse;

import org.apache.cxf.jaxrs.ext.MessageContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.Map;

/**
 * This Java class with be hosted in the URI path defined by the @Path annotation. @Path annotations on the methods
 * of this class always refer to a path relative to the path defined at the class level.
 *
 * For example, with 'http://localhost:8181/cxf' as the default CXF servlet path and '/crm' as the JAX-RS server path,
 * this class will be hosted in 'http://localhost:8181/cxf/crm/customerservice'.  An @Path("/customers") annotation on
 * one of the methods would result in 'http://localhost:8181/cxf/crm/customerservice/customers'.
 */
@Path("/customerservice/")
@Api(value = "/customerservice", description = "Operations about customerservice")
public class CustomerService {

    private static final Logger LOG = LoggerFactory.getLogger(CustomerService.class);

    long currentId = 123;
    Map customers = new HashMap();
    Map orders = new HashMap();
    private MessageContext jaxrsContext;

    public CustomerService() {
        init();
    }

    /**
     * This method is mapped to an HTTP GET of 'http://localhost:8181/cxf/crm/customerservice/customers/{id}'.  The value for
     * {id} will be passed to this message as a parameter, using the @PathParam annotation.
     *
     * The method returns a Customer object - for creating the HTTP response, this object is marshaled into XML using JAXB.
     *
     * For example: surfing to 'http://localhost:8181/cxf/crm/customerservice/customers/123' will show you the information of
     * customer 123 in XML format.
     */
    @GET
    @Path("/customers/{id}/")
    @Produces("application/xml")
    @ApiOperation(value = "Find Customer by ID", notes = "More notes about this method", response = Customer.class)
    @ApiResponses(value = {
            @ApiResponse(code = 500, message = "Invalid ID supplied"),
            @ApiResponse(code = 204, message = "Customer not found")
            })
    public Customer getCustomer(@ApiParam(value = "ID of Customer to fetch", required = true) @PathParam("id") String id) {
        LOG.info("Invoking getCustomer, Customer id is: {}", id);
        long idNumber = Long.parseLong(id);
        Customer c = customers.get(idNumber);
        return c;
    }

    /**
     * Using HTTP PUT, we can can upload the XML representation of a customer object.  This operation will be mapped
     * to the method below and the XML representation will get unmarshaled into a real Customer object using JAXB.
     *
     * The method itself just updates the customer object in our local data map and afterwards uses the Reponse class to
     * build the appropriate HTTP response: either OK if the update succeeded (translates to HTTP Status 200/OK) or not
     * modified if the method failed to update a customer object (translates to HTTP Status 304/Not Modified).
     *
     * Note how this method is using the same @Path value as our next method - the HTTP method used will determine which
     * method is being invoked.
     */
    @PUT
    @Path("/customers/")
    @Consumes({ "application/xml", "application/json" })
    @ApiOperation(value = "Update an existing Customer")
    @ApiResponses(value = {
            @ApiResponse(code = 500, message = "Invalid ID supplied"),
            @ApiResponse(code = 204, message = "Customer not found")
            })
    public Response updateCustomer(@ApiParam(value = "Customer object that needs to be updated", required = true) Customer customer) {
        LOG.info("Invoking updateCustomer, Customer name is: {}", customer.getName());
        Customer c = customers.get(customer.getId());
        Response r;
        if (c != null) {
            customers.put(customer.getId(), customer);
            r = Response.ok().build();
        } else {
            r = Response.notModified().build();
        }

        return r;
    }

    /**
     * Using HTTP POST, we can add a new customer to the system by uploading the XML representation for the customer.
     * This operation will be mapped to the method below and the XML representation will get unmarshaled into a real
     * Customer object.
     *
     * After the method has added the customer to the local data map, it will use the Response class to build the HTTP reponse,
     * sending back the inserted customer object together with a HTTP Status 200/OK.  This allows us to send back the
     * new id for the customer object to the client application along with any other data that might have been updated in
     * the process.
     *
     * Note how this method is using the same @Path value as our previous method - the HTTP method used will determine which
     * method is being invoked.
     */
    @POST
    @Path("/customers/")
    @Consumes({ "application/xml", "application/json" })
    @ApiOperation(value = "Add a new Customer")
    @ApiResponses(value = { @ApiResponse(code = 500, message = "Invalid ID supplied"), })
    public Response addCustomer(@ApiParam(value = "Customer object that needs to be updated", required = true) Customer customer) {
        LOG.info("Invoking addCustomer, Customer name is: {}", customer.getName());
        customer.setId(++currentId);

        customers.put(customer.getId(), customer);
        if (jaxrsContext.getHttpHeaders().getMediaType().getSubtype().equals("json")) {
            return Response.ok().type("application/json").entity(customer).build();
        } else {
            return Response.ok().type("application/xml").entity(customer).build();
        }
    }

    /**
     * This method is mapped to an HTTP DELETE of 'http://localhost:8181/cxf/crm/customerservice/customers/{id}'.  The value for
     * {id} will be passed to this message as a parameter, using the @PathParam annotation.
     *
     * The method uses the Response class to create the HTTP response: either HTTP Status 200/OK if the customer object was
     * successfully removed from the local data map or a HTTP Status 304/Not Modified if it failed to remove the object.
     */
    @DELETE
    @Path("/customers/{id}/")
    @ApiOperation(value = "Delete Customer")
    @ApiResponses(value = {
            @ApiResponse(code = 500, message = "Invalid ID supplied"),
            @ApiResponse(code = 204, message = "Customer not found")
            })
    public Response deleteCustomer(@ApiParam(value = "ID of Customer to delete", required = true) @PathParam("id") String id) {
        LOG.info("Invoking deleteCustomer, Customer id is: {}", id);
        long idNumber = Long.parseLong(id);
        Customer c = customers.get(idNumber);

        Response r;
        if (c != null) {
            r = Response.ok().build();
            customers.remove(idNumber);
        } else {
            r = Response.notModified().build();
        }

        return r;
    }

    /**
     * This method is mapped to an HTTP GET of 'http://localhost:8181/cxf/crm/customerservice/orders/{id}'.  The value for
     * {id} will be passed to this message as a parameter, using the @PathParam annotation.
     *
     * The method returns an Order object - the class for that object includes a few more JAX-RS annotations, allowing it to
     * display one of these two outputs, depending on the actual URI path being used:
     * - display the order information itself in XML format
     * - display details about a product in the order in XML format in a path relative to the URI defined here
     */
    @Path("/orders/{orderId}/")
    public Order getOrder(@PathParam("orderId") String orderId) {
        LOG.info("Invoking getOrder, Order id is: {}", orderId);
        long idNumber = Long.parseLong(orderId);
        Order c = orders.get(idNumber);
        return c;
    }

    /**
     * The init method is used by the constructor to insert a Customer and Order object into the local data map
     * for testing purposes.
     */
    final void init() {
        Customer c = new Customer();
        c.setName("John");
        c.setId(123);
        customers.put(c.getId(), c);

        Order o = new Order();
        o.setDescription("order 223");
        o.setId(223);
        orders.put(o.getId(), o);
    }

    @Context
    public void setMessageContext(MessageContext messageContext) {
        this.jaxrsContext = messageContext;
    }

}


4. CrmTest.java
 /*
 * JBoss, Home of Professional Open Source
 * Copyright 2014, Red Hat, Inc. and/or its affiliates, and individual
 * contributors by the @authors tag. See the copyright.txt in the
 * distribution for a full listing of individual contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.fabric8.quickstarts.rest;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.FileRequestEntity;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

import static org.junit.Assert.*;

/**
 * The client class has a main method that accesses a few of the resources defined in our JAX-RS example using
 * the Apache Commons HttpClient classes.
 */
public final class CrmTest {

    public static final String CUSTOMER_TEST_URL = "http://localhost:8181/cxf/crm/customerservice/customers/123";
    public static final String PRODUCT_ORDER_TEST_URL = "http://localhost:8181/cxf/crm/customerservice/orders/223/products/323";
    public static final String CUSTOMER_SERVICE_URL = "http://localhost:8181/cxf/crm/customerservice/customers";
    private static final Logger LOG = LoggerFactory.getLogger(CrmTest.class);
    private URL url;
    private InputStream in;

    /*
     * Just a simple helper method to read bytes from an InputStream and return the String representation.
     */
    private static String getStringFromInputStream(InputStream in) throws Exception {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        int c = 0;
        while ((c = in.read()) != -1) {
            bos.write(c);
        }
        in.close();
        bos.close();
        return bos.toString();
    }

    /**
     * HTTP GET http://localhost:8181/cxf/crm/customerservice/customers/123
     * returns the XML document representing customer 123
     *
     * On the server side, it matches the CustomerService's getCustomer() method
     *
     * @throws Exception
     */
    @Test
    public void getCustomerTest() throws Exception {
        LOG.info("Sent HTTP GET request to query customer info");
        url = new URL(CUSTOMER_TEST_URL);
        InputStream in = null;
        try {
            in = url.openStream();
        } catch (IOException e) {
            LOG.error("Error connecting to {}", CUSTOMER_TEST_URL);
            LOG.error("You should build the 'rest' quick start and deploy it to a local Fabric8 before running this test");
            LOG.error("Please read the README.md file in 'rest' quick start root");
            fail("Connection error");
        }
        String res = getStringFromInputStream(in);
        LOG.info(res);
        assertTrue(res.contains("123"));
    }

    /**
     * HTTP GET http://localhost:8181/cxf/crm/customerservice/orders/223/products/323
     * returns the XML document representing product 323 in order 223
     *
     * On the server side, it matches the Order's getProduct() method
     *
     * @throws Exception
     */
    @Test
    public void getProductOrderTest() throws Exception {

        LOG.info("Sent HTTP GET request to query sub resource product info");
        url = new URL(PRODUCT_ORDER_TEST_URL);
        try {
            in = url.openStream();
        } catch (IOException e) {
            LOG.error("Error connecting to {}", PRODUCT_ORDER_TEST_URL);
            LOG.error("You should build the 'rest' quick start and deploy it to a local Fabric8 before running this test");
            LOG.error("Please read the README.md file in 'rest' quick start root");
            fail("Connection error");
        }

        String res = getStringFromInputStream(in);
        LOG.info(res);
        assertTrue(res.contains("product 323"));
    }

    /**
     * HTTP POST http://localhost:8181/cxf/crm/customerservice/customers is used to upload the contents of
     * the add_customer.json file to add a new customer to the system.
     *
     * On the server side, it matches the CustomerService's addCustomer() method
     *
     * @throws Exception
     */
    @Test
    public void postCustomerTestJson() throws IOException {
        LOG.info("Sent HTTP POST request to add customer");
        String inputFile = this.getClass().getResource("/add_customer.json").getFile();
        File input = new File(inputFile);
        PostMethod post = new PostMethod(CUSTOMER_SERVICE_URL);
        post.addRequestHeader("Accept", "application/json");
        RequestEntity entity = new FileRequestEntity(input, "application/json; charset=ISO-8859-1");
        post.setRequestEntity(entity);
        HttpClient httpclient = new HttpClient();
        String res = "";

        try {
            int result = httpclient.executeMethod(post);
            LOG.info("Response status code: " + result);
            LOG.info("Response body: ");
            res = post.getResponseBodyAsString();
            LOG.info(res);
        } catch (IOException e) {
            LOG.error("Error connecting to {}", CUSTOMER_SERVICE_URL);
            LOG.error("You should build the 'rest' quick start and deploy it to a local Fabric8 before running this test");
            LOG.error("Please read the README.md file in 'rest' quick start root");
            fail("Connection error");
        } finally {
            // Release current connection to the connection pool once you are
            // done
            post.releaseConnection();
        }
        assertTrue(res.contains("Jack"));

    }

    /**
     * HTTP POST http://localhost:8181/cxf/crm/customerservice/customers is used to upload the contents of
     * the add_customer.xml file to add a new customer to the system.
     *
     * On the server side, it matches the CustomerService's addCustomer() method
     *
     * @throws Exception
     */
    @Test
    public void postCustomerTest() throws IOException {
        LOG.info("Sent HTTP POST request to add customer");
        String inputFile = this.getClass().getResource("/add_customer.xml").getFile();
        File input = new File(inputFile);
        PostMethod post = new PostMethod(CUSTOMER_SERVICE_URL);
        post.addRequestHeader("Accept", "application/xml");
        RequestEntity entity = new FileRequestEntity(input, "application/xml; charset=ISO-8859-1");
        post.setRequestEntity(entity);
        HttpClient httpclient = new HttpClient();
        String res = "";

        try {
            int result = httpclient.executeMethod(post);
            LOG.info("Response status code: " + result);
            LOG.info("Response body: ");
            res = post.getResponseBodyAsString();
            LOG.info(res);
        } catch (IOException e) {
            LOG.error("Error connecting to {}", CUSTOMER_SERVICE_URL);
            LOG.error("You should build the 'rest' quick start and deploy it to a local Fabric8 before running this test");
            LOG.error("Please read the README.md file in 'rest' quick start root");
            fail("Connection error");
        } finally {
            // Release current connection to the connection pool once you are
            // done
            post.releaseConnection();
        }
        assertTrue(res.contains("Jack"));

    }

    /**
     * HTTP PUT http://localhost:8181/cxf/crm/customerservice/customers is used to upload the contents of
     * the update_customer.xml file to update the customer information for customer 123.
     *
     * On the server side, it matches the CustomerService's updateCustomer() method
     *
     * @throws Exception
     */
    @Test
    public void putCustomerTest() throws IOException {

        LOG.info("Sent HTTP PUT request to update customer info");

        String inputFile = this.getClass().getResource("/update_customer.xml").getFile();
        File input = new File(inputFile);
        PutMethod put = new PutMethod(CUSTOMER_SERVICE_URL);
        RequestEntity entity = new FileRequestEntity(input, "application/xml; charset=ISO-8859-1");
        put.setRequestEntity(entity);
        HttpClient httpclient = new HttpClient();
        int result = 0;
        try {
            result = httpclient.executeMethod(put);
            LOG.info("Response status code: " + result);
            LOG.info("Response body: ");
            LOG.info(put.getResponseBodyAsString());
        } catch (IOException e) {
            LOG.error("Error connecting to {}", CUSTOMER_SERVICE_URL);
            LOG.error("You should build the 'rest' quick start and deploy it to a local Fabric8 before running this test");
            LOG.error("Please read the README.md file in 'rest' quick start root");
            fail("Connection error");
        } finally {
            // Release current connection to the connection pool once you are
            // done
            put.releaseConnection();
        }

        assertEquals(result, 200);
    }

}


5. 编译、部署、卸载
(1)cd /Users/maping/Redhat/fuse/jboss-fuse-6.2.0.redhat-133/quickstarts/cxf/rest
(2)mvn clean install
(3)./fuse
(4)osgi:install -s mvn:org.jboss.quickstarts.fuse/cxf-rest/6.2.0.redhat-133
(5)osgi:list
(6)http://localhost:8181/cxf/ 
(7)http://localhost:8181/cxf/crm/customerservice/customers/123
(8)http://localhost:8181/cxf/crm/customerservice/orders/223
(9)http://localhost:8181/cxf/crm/customerservice/orders/223/products/323
(10)mvn -Ptest

(11)Create a customer
curl -X POST -T src/test/resources/add_customer.xml -H "Content-Type: application/xml" http://localhost:8181/cxf/crm/customerservice/customers
(12)Retrieve the customer instance with id 123
curl http://localhost:8181/cxf/crm/customerservice/customers/123
(13)Update the customer instance with id 123
curl -X PUT -T src/test/resources/update_customer.xml -H "Content-Type: application/xml" http://localhost:8181/cxf/crm/customerservice/customers
(14)Delete the customer instance with id 123
curl -X DELETE http://localhost:8181/cxf/crm/customerservice/customers/123
(15)osgi:uninstall <id>

6. 修改 /cxf servlet 别名
默认情况下,CXF Servlet 的默认使用 '/cxf' 别名。如果要修改,可以
(1)增加 org.apache.cxf.osgi.cfg 文件到 /etc 目录下,并且增加 org.apache.cxf.servlet.context属性,比如:org.apache.cxf.servlet.context=/custom
(2)使用 shell 命令行,比如
config:edit org.apache.cxf.osgi
config:propset org.apache.cxf.servlet.context /custom
config:update


没有评论: