Skip to main content

Welcome to Geoff Hayward's Weblog

Commenting on Java, JavaFX, Java EE, Joomla, and IoT.

When an exception is thrown server side it is often desirable to let the user know what went wrong. When working with JAX-RS it is surprisingly simple to send a user-friendly error responds. In this post I will show you how simple it can be.

If you are reading this I assume you have some familiarity with JAX-RS (hopefully Java EE), Bootstrap, JQuery, and with a bit of luck Knockout JS. If you don't, well you soon will.

JAX-RS Mapped Exceptions

With JAX-RS when an endpoint is called that results in an exception, the developer can map the exception to a response. You, the developer, map exceptions by implementing the ExceptionMapper generic. Here is an example:

@Provider
public class ConnectExceptionMapper implements ExceptionMapper<ConnectException> {

    @Override
    public Response toResponse(ConnectException ex) {
        return Response
                .status(Response.Status.GATEWAY_TIMEOUT)
                .entity(new ErrorMessage("Connection Error", ex.getLocalizedMessage))
                .build();
    }
}

A Java EE application container uses the @Provider annotation to link-up the mapping. The response , in this example, sets a '504 Gateway Timeout' status, and takes an entity. The entity is an important element in this example; it will become the user-friendly error message.

The ErrorMessage entity is ordinary but I will include it for completeness.

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement
public class ErrorMessage {
    
    private String title;
    private String body;

    public ErrorMessage() {
    }

    public ErrorMessage(String title, String body) {
        this.title = title;
        this.body = body;
    }

}

The @XmlRootElement is used by JAX-RS via JAXB to format the object. The response format in our case will be assumed as JSON. The format is usually determined by the subject JAX-RS endpoint using the @Produces annotation.

The Web UI

Next you will need to create an HTML5 page. Add the following libraries: Bootstrap, JQuery, and Knockout JS.

The Knockout JS

Knockout JS is very lean; here is the only Knockout JS JavaScript that you need for this example (other than the binding):

var ErrorMessagesViewModel = function () {
    this.list = ko.observableArray([]);
};

This JavaScript is a ViewModel that holds a list. The list is a Knockout JS 'observableArray', so that the error messages can be pushed onto the list.

Next add the Knockout JS view. Knockout JS applies the 'alert-template' to each item in the list.

<div data-bind="template: { name: 'alert-template', foreach: list }">
    <!-- KO dynamic -->
</div> 

In this example I opted for a Knockout JS view template, as I do not like to see a 'flash' load. A 'flash' load would briefly show the view on a page loads then hide it.

Here is the 'alert-template':

<script type="text/html" id="alert-template">
    <div class="alert alert-warning alert-dismissible" role="alert">
        <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
        <strong data-bind="text: title"></strong>
        <br />
        <span data-bind="text: body"></span>
    </div>
</script>

The template is the closable Alert from Bootstrap. The message's title and body are the variables within the template.

Next we will construct the ViewModel and bind it.

var messages = new ErrorMessagesViewModel();
ko.applyBindings(messages); 

As you can see the constructed ViewModel is assigned to a variable named 'messages'. In the next section JQuery will use the 'messages' variable to push server error responses.

The JQuery

JQuery's AJAX utility can catch global events. We are going to catch global error events. Here is the JQuery:

$(document).ajaxError(function (event, response, settings, thrownError) {
     messages.list(response.responseJSON);
});

That's it, as long as you are using JQuery's AJAX utility, such as '$.getJSON' to send requests the JAX-RS mapped exceptions are pushed on to the list and shown to the users.

As an aside the solution in this example is an aspect, in the AOP sense. The 'ErrorMessagesViewModel' is a modularisation of concern.

Video Supplement

This post is an after the fact note on a solution from a project I am working on. So here is a video of this solution in action within the other project.



Read

It took me a while to get to the bottom of why CDI seemed to be dropping @ViewScoped and @SessionScoped beans. @RequestScope was not behaving properly ether.

It took a lot of digging in the application's codebase and a lot of watching the Debugger before narrowing down the course. Anyway here is the culprit; can you spot the bug?

<h:link outcome="#{navbar.logout()}" >Logout</h:link>

That is from an extract of a composite component that is used on every page of the application. Hence, CDI seemed to be dropping scope.

@Named(value = "navbar")
@ApplicationScoped
public class Navbar implements Serializable {
	
    @Inject
    private HttpServletRequest request;

    public String logout() {
        request.getSession(false).invalidate();
        return "/index?faces-redirect=true";
    }

}

For completeness, the extracts CDI backing bean.

I hope you enjoyed this issue more than I did.



Read

You can convert an ArrayList to a Java EE JsonArray using the Java Stream API in the following way.

// set up example
ArrayList<Pet> pets = new ArrayList<>();
pets.add(new Pet("Goldie", "Fish"));
pets.add(new Pet("Daisy", "Cow"));
pets.add(new Pet("Snowball", "Cat"));

// the work
pets.stream()
	.map((a) -> { 
		return Json.createObjectBuilder()
			.add("id", a.getName())
			.add("type", a.getGroup())
			.build();
	})
	.collect(
		Json::createArrayBuilder,
		JsonArrayBuilder::add,
		JsonArrayBuilder::add
	)
	.build();

The .map operation of the stream API takes a Function<T,R>. The function converts each item to a JsonObject. Then the .collect operation creates the JsonArray using each of the JsonObjects.

I hope you find this useful.



Read

I would like to share a simple Windows Batch script that I made. The Batch script changes the WildFly's logging level quickly and easily. It is as easy as running change-logging-level.cmd --file-debug --console-error

change-logging-level.cmd

:: Name:     change-logging-level.cmd
:: Purpose:  Set's the logging level to very low or the setting given as an argument
:: Author:   geoffhayward.eu
:: Revision: August 2016 - initial version
@ECHO OFF

SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION

SET NOPAUSE=true
SET ME=%~n0
SET SCRIPT=%TEMP%\%ME%-%DATE:/=-%.txt
SET CON_LEVEL=ERROR
SET FILE_LEVEL=ERROR

:parse_args
IF NOT "%~1"=="" (
		IF "%~1"=="--all" (
		SET CON_LEVEL=ALL
		SET FILE_LEVEL=ALL
	)
	IF "%~1"=="--console-all" (
		SET CON_LEVEL=ALL
	)
	IF "%~1"=="--file-all" (
		SET FILE_LEVEL=ALL
	)
	IF "%~1"=="--config" (
		SET CON_LEVEL=CONFIG
		SET FILE_LEVEL=CONFIG
	)
	IF "%~1"=="--console-config" (
		SET CON_LEVEL=CONFIG
	)
	IF "%~1"=="--file-config" (
		SET FILE_LEVEL=CONFIG
	)
	IF "%~1"=="--debug" (
		SET CON_LEVEL=DEBUG
		SET FILE_LEVEL=DEBUG
	)
	IF "%~1"=="--console-debug" (
		SET CON_LEVEL=DEBUG
	)
	IF "%~1"=="--file-debug" (
		SET FILE_LEVEL=DEBUG
	)
	IF "%~1"=="--error" (
		SET CON_LEVEL=ERROR
		SET FILE_LEVEL=ERROR
	)
	IF "%~1"=="--console-error" (
		SET CON_LEVEL=ERROR
	)
	IF "%~1"=="--file-error" (
		SET FILE_LEVEL=ERROR
	)
	IF "%~1"=="--fatal" (
		SET CON_LEVEL=FATAL
		SET FILE_LEVEL=FATAL
	)
	IF "%~1"=="--console-fatal" (
		SET CON_LEVEL=FATAL
	)
	IF "%~1"=="--file-fatal" (
		SET FILE_LEVEL=FATAL
	)
	IF "%~1"=="--fine" (
		SET CON_LEVEL=FINE
		SET FILE_LEVEL=FINE
	)
	IF "%~1"=="--console-fine" (
		SET CON_LEVEL=FINE
	)
	IF "%~1"=="--file-fine" (
		SET FILE_LEVEL=FINE
	)
	IF "%~1"=="--finer" (
		SET CON_LEVEL=FINER
		SET FILE_LEVEL=FINER
	)
	IF "%~1"=="--console-finer" (
		SET CON_LEVEL=FINER
	)
	IF "%~1"=="--file-finer" (
		SET FILE_LEVEL=FINER
	)
	IF "%~1"=="--finest" (
		SET CON_LEVEL=FINEST
		SET FILE_LEVEL=FINEST
	)
	IF "%~1"=="--console-finest" (
		SET CON_LEVEL=FINEST
	)
	IF "%~1"=="--file-finest" (
		SET FILE_LEVEL=FINEST
	)
	IF "%~1"=="--info" (
		SET CON_LEVEL=INFO
		SET FILE_LEVEL=INFO
	)
	IF "%~1"=="--console-info" (
		SET CON_LEVEL=INFO
	)
	IF "%~1"=="--file-info" (
		SET FILE_LEVEL=INFO
	)
	IF "%~1"=="--off" (
		SET CON_LEVEL=OFF
		SET FILE_LEVEL=OFF
	)
	IF "%~1"=="--console-off" (
		SET CON_LEVEL=OFF
	)
	IF "%~1"=="--file-off" (
		SET FILE_LEVEL=OFF
	)
	IF "%~1"=="--trace" (
		SET CON_LEVEL=TRACE
		SET FILE_LEVEL=TRACE
	)
	IF "%~1"=="--console-trace" (
		SET CON_LEVEL=TRACE
	)
	IF "%~1"=="--file-trace" (
		SET FILE_LEVEL=TRACE
	)
	IF "%~1"=="--warn" (
		SET CON_LEVEL=WARN
		SET FILE_LEVEL=WARN
	)
	IF "%~1"=="--console-warn" (
		SET CON_LEVEL=WARN
	)
	IF "%~1"=="--file-warn" (
		SET FILE_LEVEL=WARN
	)
	IF "%~1"=="--warning" (
		SET CON_LEVEL=WARNING
		SET FILE_LEVEL=WARNING
	)
	IF "%~1"=="--console-warning" (
		SET CON_LEVEL=WARNING
	)
	IF "%~1"=="--file-warning" (
		SET FILE_LEVEL=WARNING
	)
	SHIFT
	GOTO :parse_args
)


ECHO batch > %SCRIPT%
ECHO /subsystem=logging/console-handler=CONSOLE:change-log-level(level=%CON_LEVEL%)>> %SCRIPT%
ECHO /subsystem=logging/periodic-rotating-file-handler=FILE:change-log-level(level=%CON_LEVEL%)>> %SCRIPT%
ECHO run-batch >> %SCRIPT%

 
CALL %JBOSS_HOME%\bin\jboss-cli.bat -c --file="%TEMP%\%ME%-%DATE:/=-%.txt"

ENDLOCAL

This version of the Batch script is designed for Windows based dev environments, with WildFly as the target dev Java EE application container. The Batch script creates a simple JBoss CLI script and then sends that script into JBoss CLI. Note %JBOSS_HOME% needs to be set as an environment variable.

Calling this 'change-logging-level.cmd' script without any arguments will set WildFly's logging level down to 'ERROR' for both the console and file logging. Supplying arguments will override the default, for example change-logging-level.cmd --fine will immediately (except long running transactions), set all logging to 'fine'.



Read

I am very happy to be attending Devoxx Poland 2016; I enjoyed Devoxx Poland last year very much. As well as Krakow.

This year I am looking forward to talks on: Java 9 Flow API; Angular 2; Microservices; lambdas; and many other topics. But, worryingly there does not seem to be a single talk with "Java EE" in the title. Like last year Devoxx Poland has attracted many great speakers.

Also, the food last year was delicious, so I hoping for the same this year.

Here is Devoxx 2015 video



Read

Mailing List

Responsive Media

With the ResponsiveMedia plugin for Joomla it is easy to add 3rd party content from YouTube, Vimeo, and Instagram right in to any Joomla! article.

ResponsiveMedia