Using Spring Boot Actuator Endpoints and Jersey Web Services

This one took me a few hours to find an easy solution for, so I thought I’d share here so it may help others.

I have been working to create a Jersey web service that will run in a Spring Boot instance, and wanted to make use of the nifty actuator endpoints that are available in Spring Boot for things such as monitoring the health of the application, listing the beans in use by the application, and shutting down the application, among other things which are detailed on the Spring Boot website

The problem is that Jersey application will take over all URLS at the root, thus masking the Spring Boot URLs such as /health, even though the application itself is not using that mapping.

The easiest solution I found was to add an application path to the Jersey application so that it listened for requests arriving from a different URL root such as /api/MyJerseyService, where /api is the root that Jersey will use.

Configuring this was relatively straightforward and only required an additional annotation in the AppConfig class.  Notice the @ApplicationPath(“/api”) annotation, specifying that Jersey should use /api as the application root.

@Configuration
@ApplicationPath("/api")
public class AppConfig extends ResourceConfig {
    public AppConfig() {
        register( UvDataResource.class );
    }
}

Notice the @ApplicationPath(“/api”) which will tells the application to use /api as the root. Now when the Spring Boot health web service is invoked at the following URL, the expected results are returned.

http://localhost:8080/health

{
    "status": "UP",
    "diskSpace": {
        "status": "UP",
        "free": 118162386944,
        "threshold": 10485760
    }
}

While the call to the Jersey Webservice produces the expected result:

http://localhost:8080/api/uvdata/AMLOPS_EQUIP_MASTER/844024

{
    "id": "844024",
    "equipmentType": "11*3",
    "serialNumber": "844024",
    "checksum": 750288259,
    "badValuesMap": {},
    "multiValueMap": {}
}

twitter: @RobTerpilowski

Advertisements

Debugging Web Service Issues When Migrating from Glassfish Version 2 to 3.

For the most part our migration of web applications (including web services) from Glassfish version 2 to version 3 has been relatively painless. We did however encounter a strange issue with one of our server apps that has both a web service and an RMI server component. The application has been running without problems on Glassfish 2, but when we attempted to deploy to Glassfish 3 we encountered the error message shown in the screenshot below:

“Error occurred during deployment: Exception while preparing the app: Servlet CarrierBillWS implements 2 web service endpoints but must only implement 1”

image

The initial thought was to go ahead and migrate the application from Java EE 5 to Java EE 6, and hope that corrected the issue. This however did not change anything and we were left with the same error. Googling didn’t produce any useful results so I decided to take a look through some of the glassfish code where the exception was being thrown from.

         WebServicesDescriptor webServices = bundle.getWebServices();
          Collection endpoints =
                 webServices.getEndpointsImplementedBy(webComponentImpl);

         if( endpoints.size() > 1 ) {
             String msg = "Servlet " + getWebComponentLink() +
                     " implements " + endpoints.size() + " web service endpoints " +
                     " but must only implement 1";
             throw new IllegalStateException(msg);
         }

It appears that there could possibly a name collision with classes in the application, which wasn’t being checked for in Glassfish 2. The web service class itself is pretty simple:

@WebService()
public class CarrierBillWS {

    protected CarrierBillWSActor actor;
    protected Logger logger;

    public CarrierBillWS() {
        logger = Logger.getLogger(getClass());
        actor = new CarrierBillWSActor(PropertyManager.getInstance().getProperty(PropKey.UV_SESSION));
    }

    @WebMethod(operationName = "reopenBill")
    public void reopenBill( String dispatchId, String locationCode ) {
        logger.info( "Web service Request received to reopen carrier bill: " + dispatchId + " from location: " + locationCode );
        actor.reopenBill(dispatchId, locationCode);

    }
}

For fun, I commented out the @WebService annotation and deployed the application without any issues, with the obvious result being that there was no CarrierBillWS web service listed in the Glassfish admin console, but I was at least able to deploy. I then did a grep for “CarrierBillWS” and to my surprise found another class with the same name in one of the web service app’s dependencies. This dependency is a .jar file that is shared across multiple applications here at Lynden, including the web service. So, why was there a class with the same name in this library? It was because the library also contained code for a client of this CarrierBill web service which other applications depended on for communicating with the service. The CarrierBillWS application itself depends on this .jar file for other functionality that is shared across multiple apps. When the jar was built, it pulled down the WSDL from the web service and auto generated the client side classes, which included a CarrierBillWS client side class. This .jar was then bundled in the web service’s .war file, and Glassfish was finding both classes and then complaining.

The solution to the problem was pretty simple; override some of the default values of the @WebService annotation, and then rebuild the dependency using a new WSDL.

@WebService(serviceName="CarrierBillWebService", name="CarrierBillWebService")
public class CarrierBillWS {

The new “serviceName” and “name” properties on the WebService annotation will cause the client project to generate code with a class name of CarrierBillWebService rather than CarrierBillWS. When the .jar is built, the Web Service and its client related classes in the dependent jar now have different names and Glassfish v3 loads the application without any issues.

Since there wasn’t much info out there on this particular issue, I’m hoping that this may help someone in the future who encounters a similar problem.

Twitter: @RobTerp