Foute foutjes

27 February 2017

Soms zijn de foutmeldingen die een computer je geeft te leuk om weg te klikken. Ik druk dan tegenwoordig steevast op PrintScreen. Hieronder een paar voorbeeldjes:

Wat gebeurt er als je Instant Messenger vastloopt?

Arjan is not responding

De content assistent in de war

Onderstaand voorbeeld is een beetje een vreemde mix van scriptlets in de code. Maar de multiple annotations die hier gevonden worden zijn ook wel vreemd…

Jahoor

Eclipse 4.5 SR2 released!

26 February 2016

Another year, another SR release; the Eclipse organization released maintenance release two of Eclipse 4.5 (aka, MARS.2, aka Eclipse 4.5.2) today.

The release has been done with little fanfare. There’s no notion of this event anywhere on the eclipse.org homepage. On the top of the page at the moment of writing is the Eclipse Foundation + Google Summer of Code and the fact that the EclipseCon 2016 starts March 7. The actual IDE that is Eclipse is not mentioned. Main releases typically are, but SR releases aren’t, or at least not right away. Last year we saw the event being put on the homepage after about 5 days.

As it appears, there were 95 bugs fixed for this release in the core of Eclipse.

Among others a somewhat older but high profile OS X bug is fixed involving a nasty NPE and an obscure one where Eclipse would actually delete code.

Furthermore the usual assortment of totally weird bugs where fixed, the ones even the most experienced developers have hardly any idea about what they could mean. Stuff like ClassCastException in Theme$1.propertyChange.

This time around the good people from the WTP project did not feel like posting about the SR2 event on their homepage. Fiddling with the handy bugzilla URLs revealed a list of 22 bugs that are likely to be fixed in WTP 3.7.2, the version that should be the one that’s bundled with Eclipse 4.5.2.

Among the highlights of bugs that WTP 3.7.2 fixed is a fix for the fact the pom.xml did not match a Manifest.fm. Most other fixes focused on solving null pointer exceptions. No less than 9 different ones of these were solved, including the dreaded NullPointerException in ParameterGuesser$VariableCollector.collect. Interesting to note is that all of these were reported via the automated error reporting that was introduced with Eclipse 4.5. Next to null pointer exceptions, a couple of array index out of bounds exceptions where fixed as well.

Following the trend, community reporting is even lower than last year. This year there’s really no reporting at all. But it has only been released today, so maybe a few outlets will pick it up in the following days.

It remains a fact, year after year, that SR releases apparently aren’t that exciting. But the lack of announcements about them and the silent releases of what should really be one of the most important products that the Eclipse organization delivers remains a curious thing.

Luckily the milestones for the next Eclipse, code named Neon (4.6), do get some more attention.

Counting the rows returned from a JPA query

24 June 2015

Despite being almost ten years old, the JPA specification to this day has rather poor support for basic paging/sorting/filtering. Paging/sorting/filtering is used in a lot of (CRUD) applications where the result from a query is shown in a table, and where the user can scroll through the results one page at a time, and where this result can be sorted by clicking on any of the table column headers.

In order to support this a number of things are generally needed:

  1. The total number of rows (or entities) in the full result must be known
  2. There should be support for an offset in the full result and a limit for the amount of rows that will be obtained
  3. The column (attribute) on which to sort must be dynamically added to the query
  4. Search expressions must be dynamically added to the query

As it appears, only offset/limit is directly supported in JPA. A sorting column can only be added dynamically when using the overly verbose and hard to work with Criteria API. Search expressions are somewhat possible to add via the Criteria API as well, but it’s an awkward and rather poor mechanism.

Surprisingly, universally counting the number of rows is not possible at all in JPA. In this article we’ll look at a very hairy workaround for this using Hibernate specific code.

The core problem is that JPA does not allow subqueries in a SELECT. This makes it impossible to solve this issue in a straightforward manner like:

SELECT 
    COUNT(_user)
FROM (
    SELECT 
        DISTINCT _user
    FROM 
        USER _user
    JOIN 
        _user.roles _role
    WHERE 
        _role.id IN (1)
)

For some classes of queries the query can be rewritten to yield the same effect by putting the subquery in the WHERE clause, which is supported by JPA:

SELECT 
    COUNT(_user)
FROM
    USER _user
WHERE
    _user IN (
        SELECT 
            DISTINCT _user
        FROM 
            USER _user
        JOIN 
            _user.roles _role
        WHERE 
            _role.id IN (1)
    )

Unfortunately this trick does not work when aggregation is used, with or without constructor expressions. Consider for example the following JPQL query:

SELECT
     NEW com.example.AggregatedStatistic(
          SUM(_statistic.views),
          SUM(_statistic.clicks),
          _statistic.date
     )
FROM
     Statistic _statistic
WHERE
     _statistic.date >= :startDate AND
     _statistic.date <= :endDate
GROUP BY
     _statistic.date

Strange as it may seem, this query is uncountable in JPA, while in SQL this is usually not a problem. So what we could do is generate the corresponding SQL query, surround it by an outer count(*) query and then execute that.

But here we hit another wall. While by definition every JPA implementation must be able to generate SQL from a JPA query, there’s no actual standard API to get just this query text.

Now one particular aspect of JPA is that it’s almost never a pure implementation (such as e.g. JSF), but a standardization API layered on top of another API. This other API is typically richer. In the case of Hibernate there indeed appears to be a public API available to do the transformation that we need, including handling query parameters (if any).

To demonstrate this, let’s first create the Query object in Java. Here we assume that the JPQL query shown above is available as a query named “Statistic.perDate”:

TypedQuery<Statistic> typedQuery = 
    entityManager.createNamedQuery("Statistic.perDate", Statistic.class)
                 .setParameter("startDate", twoMonthsBack())
                 .setParameter("endDate", now());

From this typed query we can obtain the Hibernate Query, and from that get the query string. This query string always represents the JPQL (technically, HQL) independent of whether the query was created from JPQL or from a Criteria:

String hqlQueryText= 
    typedQuery.unwrap(org.Hibernate.Query.class).getQueryString()

In order to parse this JPQL (HQL) query text we need to make use of the ASTQueryTranslatorFactory. Using this and the JPA EntityManagerFactory one can get hold of the SQL query text and a collection of parameters:

QueryTranslatorFactory translatorFactory = new ASTQueryTranslatorFactory();
 
QueryTranslator translator = translatorFactory.createQueryTranslator(
    hqlQueryText, hqlQueryText,
    EMPTY_MAP,
    (SessionFactoryImplementor) entityManagerFactory.unwrap(SessionFactory.class), 
    null
);
 
translator.compile(EMPTY_MAP, false);

After executing the above code the mentioned SQL query and parameters are available from the translator object. We’ll first construct the counting query itself:

javax.persistence.Query nativeQuery = entityManager.createNativeQuery(
    "select count(*) from (" +
        translator.getSQLString() +
    ") x"			
);

And then set the parameters back:

ParameterTranslations parameterTranslations = translator.getParameterTranslations();
 
for (Parameter<?> parameter : typedQuery.getParameters()) {
    String name = parameter.getName();
    for (int position : parameterTranslations.getNamedParameterSqlLocations(name)) {
        nativeQuery.setParameter(
            position + 1, 
            typedQuery.getParameterValue(name)
        );
    }
}

Note that the +1 on the position is needed because of a mismatch between 0-based and 1-based indexing of both APIs.

With all this in place we can now finally execute the query and obtain the count:

Long cnt = ((Number) nativeQuery.getSingleResult()).longValue();

The casting here looks a big nasty. In the case of PostgreSQL a BigInteger was returned. I’m not entirely sure if this would be the case for all databases, hence the cast to Number first and then getting the long value from that.

Conclusion

Using the Hibernate specific API it’s more or less possible to universally count the results of a query. It’s not entirely perfect still, as values set on a JPQL query can often be richer than those set on a native query. For example, you can often set an entity itself as a parameter and the JPA provider will then automatically use the ID of that.

Furthermore using provider specific APIs when using JPA, especially for such an essential functionality, is just not so nice.

Finally, some providers such as EclipseLink do support subqueries in the select clause. For those providers no vendor specific APIs have to be used (and therefor there are no compile time concerns), but the code is of course still not portable.

If/when there will ever be a new JPA version again it would really be nice if the current problems with paging/sorting/filtering could be addressed.

Arjan Tijms

Hitachi Cosminexus v10 silently certified for Java EE 7

28 April 2015

Every time after a Java EE spec is released it’s somewhat of a battle of who is the first to certify for that new specification.

GlassFish is always the first (by definition, as required by the JCP rules for a RI implementation), with tech previews/community editions of JEUS and JBoss following suit. These are however not (directly) supported for production by their own vendors.

During the Java EE 6 cycle, IBM was the first to come out with a supported and certified server, namely WebSphere 8.0. For the Java EE 7 cycle, the battle seemed to be between IBM and Oracle. Both of them are expected to release a Java EE 7 server soon. People are eagerly awaiting this, as Java EE 7 brings many improvements.

Surprisingly it’s the relatively unknown HITACHI Cosminexus Application Server that was completely silently (in Western outlets, that is) added to Oracle’s certification page. HITACHI themselves do mention this fact on their homepage, but otherwise there hasn’t been much news about this.

It appears that HITACHI is focusing exclusively on the Japanese market, but still this may be an interesting server to check out.

Arjan Tijms

A basic implementation of basic access authentication using JASPIC

20 April 2015

Basic access authentication is a crude mechanism to authenticate that’s part of the HTTP standard. It allows both an agent to send username/password credentials and a server to request the agent to authenticate itself. This happens in a simple but standardized way.

The mechanism can be easily implemented using Java EE’s JASPIC and a sprinkle of utility code from the experimental OmniSecurity project (which is currently being discussed as one of the possible options to simplify security in Java EE 8).

A basic implementation looks as follows:

public class BasicAuthModule extends HttpServerAuthModule {
 
    @Override
    public AuthStatus validateHttpRequest(HttpServletRequest request, HttpServletResponse response, HttpMsgContext httpMsgContext) throws AuthException {
 
        String[] credentials = getCredentials(request);
        if (!isEmpty(credentials)) {
 
            UsernamePasswordIdentityStore identityStore = getReferenceOrNull(UsernamePasswordIdentityStore.class);
            if (identityStore != null) {
                if (identityStore.authenticate(credentials[0], credentials[1])) {
                    return httpMsgContext.notifyContainerAboutLogin(
                        identityStore.getUserName(), 
                        identityStore.getApplicationRoles()
                    );
                }                
            }            
        }
 
        if (httpMsgContext.isProtected()) {
            response.setHeader("WWW-Authenticate", "Basic realm=\"test realm:\"");
            return httpMsgContext.responseUnAuthorized();
        }
 
        return httpMsgContext.doNothing();
    }
 
    private String[] getCredentials(HttpServletRequest request) {
 
        String authorizationHeader = request.getHeader("Authorization");
        if (!isEmpty(authorizationHeader) && authorizationHeader.startsWith("Basic ") ) {
            return new String(parseBase64Binary(authorizationHeader.substring(6))).split(":");
        }
 
        return null;
    }
}

Full code in the OmniSecurity repo

Using injection, the example can be simplified a little and will then look as follows:

public class BasicAuthModule extends HttpServerAuthModule {
 
    @Inject
    private UsernamePasswordIdentityStore identityStore;
 
    @Override
    public AuthStatus validateHttpRequest(HttpServletRequest request, HttpServletResponse response, HttpMsgContext httpMsgContext) throws AuthException {
 
        String[] credentials = getCredentials(request);
        if (!isEmpty(credentials) && identityStore.authenticate(credentials[0], credentials[1])) {
            return httpMsgContext.notifyContainerAboutLogin(
                identityStore.getUserName(),
                identityStore.getApplicationRoles()
            );
        }
 
        if (httpMsgContext.isProtected()) {
            response.setHeader("WWW-Authenticate", "Basic realm=\"test realm:\"");
            return httpMsgContext.responseUnAuthorized();
        }
 
        return httpMsgContext.doNothing();
    }
 
    private String[] getCredentials(HttpServletRequest request) {
 
        String authorizationHeader = request.getHeader("Authorization");
        if (!isEmpty(authorizationHeader) && authorizationHeader.startsWith("Basic ") ) {
            return new String(parseBase64Binary(authorizationHeader.substring(6))).split(":");
        }
 
        return null;
    }
}

Note that the JASPIC auth module as shown here is responsible for implementing the client/server interaction details. Validating the credentials (username/password here) and obtaining the username and roles is delegated to an identity store (which can e.g. be database or LDAP based).

Arjan Tijms

First official JSF 2.3 contribution from zeef.com

27 January 2015

A while back we joined the JSF 2.3 EG as zeef.com. While we had contributed as individuals before (mostly via code suggestions and snippets in JIRA issues) we are proud that today our first more direct contribution was committed to Mojarra for the ongoing JSF 2.3 effort.

Co-spec lead Manfred Riem tweeted about this earlier today:

The commit in question can be seen in our GitHub mirror. To summarize the change; before it was only possible to inject the application map as follows:

@Inject
@ApplicationMap
Map applicationMap;

As can be seen, the map is missing its generic parameters. This is of course far from ideal. With the latest patch, this map can now be injected as it should be:

@Inject
@ApplicationMap
Map<String, Object> applicationMap;

Injection into a raw map is still supported, but for most cases the generic variant should be preferred.

It’s a fairly small change, but hopefully many more of such changes will follow soon ๐Ÿ˜‰

Arjan Tijms

CDI based @Asynchronous alternative

19 January 2015

Arguably one of the most convenient things in EJB after declarative transactions is the @Asynchronous annotation. Applying this annotation to a method will cause it to be executed asynchronously when called (the caller does not have to wait for the method to finish executing).

The downside of this annotation is that it’s only applicable to EJB beans. While EJB beans these days are lightweight and nothing to avoid in general, the fact is that in Java EE 6 and especially Java EE 7 other managed beans, specifically CDI ones, play an increasingly important role. These beans unfortunately can not directly take advantage of the platform provided @Asynchronous.

Building such support ourselves in Java EE 7 however is not that difficult. Thanks to the Java 8, and the Interceptors and Concurrency specs it’s actually quite simple, but with a small caveat (see below):

We’ll start with defining the annotation itself:

@InterceptorBinding
@Target({METHOD})
@Retention(RUNTIME)
@Inherited
public @interface Asynchronous {}

Next we need a helper class that effectively unwraps the dummy Future instance (of type AsyncResult, as provided by the EJB spec) that an asynchronous method returns. Such a wrapper class is needed in Java, since you otherwise can’t call a method that returns say String and assign it to Future<String>. This is not specific to this CDI implementation, but is exactly how EJB’s @Asynchronous works.

public class FutureDelegator implements Future<Object> {
 
    private final Future<?> future;
 
    public FutureDelegator(Future<?> future) {
        this.future = future;
    }
 
    @Override
    public Object get() throws InterruptedException, ExecutionException {
        AsyncResult<?> asyncResult = (AsyncResult<?>) future.get();
        if (asyncResult == null) {
            return null;
        }
 
        return asyncResult.get(); 
    }
 
    @Override
    public Object get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
        AsyncResult<?> asyncResult = (AsyncResult<?>) future.get(timeout, unit);
        if (asyncResult == null) {
            return null;
        }
 
        return asyncResult.get(); 
    }
 
    @Override
    public boolean cancel(boolean mayInterruptIfRunning) {
        return future.cancel(mayInterruptIfRunning);
    }
 
    @Override
    public boolean isCancelled() {
        return future.isCancelled();
    }
    @Override
    public boolean isDone() {
        return future.isDone();
    }
}

With those 2 classes in place the actual interceptor can be coded as follows:

@Interceptor
@Asynchronous
@Priority(PLATFORM_BEFORE)
public class AsynchronousInterceptor implements Serializable {
 
    private static final long serialVersionUID = 1L;
 
    @Resource
    private ManagedExecutorService managedExecutorService;
 
    @AroundInvoke
    public Object submitAsync(InvocationContext ctx) throws Exception {
        return new FutureDelegator(managedExecutorService.submit( ()-> { return ctx.proceed(); } ));
    }
}

There are a few things to take into account here. The first is the priority of the interceptor. I put it on PLATFORM_BEFORE, which is the absolute lowest level, meaning the interceptor will likely hit before any other interceptor. If this interceptor would ship with a library it’s more correct to use the lowest range reserved for libraries: LIBRARY_BEFORE.

For the actual parallel execution, the call to ctx.proceed() is scheduled on a thread pool using the Java EE Concurrency provided executor service. While this service was only recently introduced in Java EE 7, it in fact originated from a very old spec draft that was dragged into modern times. Unfortunately that spec felt it needed to use the somewhat archaic @Resource annotation for injection instead of the more modern @Inject. So that’s why we use that former one here and not the latter.

A caveat is that the interceptor as given does not work on the current released versions of Weld, but in fact does work on the not yet released SNAPSHOT version. The issue is explained by Jozef on the CDI-dev mailing list.

As a temporary workaround a thread local guard can be used on Weld as follows:

@Interceptor
@Asynchronous
@Priority(PLATFORM_BEFORE)
public class AsynchronousInterceptor implements Serializable {
 
    private static final long serialVersionUID = 1L;
 
    @Resource
    private ManagedExecutorService managedExecutorService;
 
    private static final ThreadLocal<Boolean> asyncInvocation = new ThreadLocal<Boolean>();
 
    @AroundInvoke
    public synchronized Object submitAsync(InvocationContext ctx) throws Exception {
 
        if (TRUE.equals(asyncInvocation.get())) {
            return ctx.proceed();
        }
 
        return new FutureDelegator(managedExecutorService.submit( ()-> { 
            try {
                asyncInvocation.set(TRUE);
                return ctx.proceed();
            } finally {
                 asyncInvocation.remove();
            }
        }));
    }
}

Future work

The interceptor shown here is just a bare bones copy of the EJB version, but lacks the setup of a request scope. Going further however we can add additional features, like using a completable future, optionally named thread pools, etc.

Arjan Tijms

Follow JSF 2.3 development via GitHub mirror

17 January 2015

Currently development for JSF 2.3 is well underway in the trunk of the Mojarra project.

The Mojarra project still uses SVN, and only has the default web interface up and running. Specifically this means it’s not entirely easy to browse through the commits and see diffs, as this default web interface only offers a very bare bones browsing of the repository.

While there are of course web tools for SVN that show commits and diffs etc, simply importing the SVN repository into GitHub proved to be the easiest solution. So therefor we made a mirror available on GitHub:

github.com/javaeekickoff/mojarra

This mirror is automatically updated every half an hour, so it should never be that far behind the SVN root repository. GitHub provides a number of extra features, such as feeds in atom format. Using that we can easily create widgets such as the one below that shows a near real-time overview of the 3 latest commits:


In addition to this mirror we’ve also published a fork of it, in which we made a few small changes that allows the Mojarra project to be used from Eclipse. This fork is at:

github.com/omnifaces/mojarra

This fork will function as OmniFaces’ feature branch for code that we hope will be integrated into Mojarra and thus JSF 2.3 (which is of course subject to approval by the JSF spec leads and the other EG members).

For completeness, once checked-out, Mojarra can be build using the following steps:

Assuming SOURCE_HOME is the directory containing the source code:

  1. Copy build.properties.glassfish to build.properties
  2. Edit build.properties and set jsf.build.home to SOURCE_HOME
  3. Make sure JAVA_HOME is set and points to a JDK8 install
    e.g. on Ubuntu put JAVA_HOME=/opt/jdk8 in /etc/environment

  4. From SOURCE_HOME run (on the commandline) ant main clean main

The jsf-api.jar will be in SOURCE_HOME/jsf-api/build/lib and jsf-impl.jar will be in SOURCE_HOME/jsf-ri/build/lib.

When making changes from within Eclipse (use the OmniFaces fork for that):

  1. Make changes as needed in .java files, but note that the Eclipse compiled result in SOURCE_HOME/bin must be ignored
  2. From SOURCE_HOME run (on the command line) ant clean main

The jsf-api.jar will again be in SOURCE_HOME/jsf-api/build/lib and jsf-impl.jar will be in SOURCE_HOME/jsf-ri/build/lib.

Do note that the initial build command is ant main clean main, but all following builds happen via the command ant clean main. This is due to a circular dependency, that will likely be removed in the (near) feature if/when the entire project becomes a Maven project. Also note that when that happens, the Eclipse specific changes in the OmniFaces fork of Mojarra will not be needed anymore either.

Arjan Tijms

Mysterious 4.4.1.20150109 Eclipse Luna update is SR1a

15 January 2015

Two days back I noticed Eclipse had a mysterious update available; Eclipse IDE for Java EE Developers, version 4.4.1.20150109-0740 with id “epp.package.jee”:

eclipse-luna-sr1a

Of course there was no info on what this update was about and Googling for it yielded no results. Googling again for it today gave a single hit:

download.eclipse.org/technology/epp/packages/luna/SR1a/p2.diff.txt

Looking at the URL revealed that “4.4.1.20150109-0740” is the alternative universe version for what’s otherwise known as “Luna SR1a”. Googling for the latter gave some more results, particularly the following one:

Eclipse Ships Luna SR1a Git Security Release

A bit out of character, but the Eclipse organization even linked to this from their homepage!

Why it’s so difficult for Eclipse to show a description for their updates is still a small mystery, but at least the mystery of what “4.4.1.20150109-0740” is about is now solved ๐Ÿ˜‰

Arjan Tijms

Bridging Undertow’s authentication events to CDI

22 December 2014

Undertow’s native security system has an incredible useful feature that’s painfully missing in the security system of Java EE; authentication events.

While Java EE applications could directly use the Undertow events, it’s not directly clear how to do this. Furthermore having Undertow specific dependencies sprinkled throughout the code of an otherwise general Java EE application is perhaps not entirely optimal.

The following code shows how the Undertow dependencies can be centralized to a single drop-in jar, by creating an Undertow extension (handler) that bridges the native Undertow events to standard CDI ones. Upon adding such jar to a Java EE application, the application code only has to know about general CDI events.

First create the handler itself:

import io.undertow.security.api.NotificationReceiver;
import io.undertow.security.api.SecurityNotification;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import javax.enterprise.inject.spi.CDI;
import org.omnifaces.security.events.AuthenticatedEvent;
import org.omnifaces.security.events.LoggedOutEvent;
 
public final class AuthEventHandler implements HttpHandler {
 
    private final HttpHandler next;
    private static final SecurityNotificationReceiver NOTIFICATION_RECEIVER = new SecurityNotificationReceiver();
 
    public AuthEventHandler(final HttpHandler next) {
        this.next = next;
    }
 
    @Override
    public void handleRequest(HttpServerExchange exchange) throws Exception {
        exchange.getSecurityContext().registerNotificationReceiver(NOTIFICATION_RECEIVER);    
        next.handleRequest(exchange);
    }
 
    private static class SecurityNotificationReceiver implements NotificationReceiver {
 
        @Override
        public void handleNotification(final SecurityNotification notification) {
 
            switch (notification.getEventType()) {
                case AUTHENTICATED:
                    CDI.current().getBeanManager().fireEvent(new AuthenticatedEvent(notification, notification.getAccount().getPrincipal()));
                    break;
                case LOGGED_OUT:
                    CDI.current().getBeanManager().fireEvent(new LoggedOutEvent(notification, notification.getAccount().getPrincipal()));
                    break;
                default:
                    break;
            }
        }
    }
}

Note that the AuthenticatedEvent and LoggedOutEvent types come from OmniSecurity, but they are just used for the example. As the types contain no required logic, any simple type could be used..

Next register the handler in an extension as follows:

import io.undertow.servlet.ServletExtension;
import io.undertow.servlet.api.DeploymentInfo;
import javax.servlet.ServletContext;
 
public class UndertowHandlerExtension implements ServletExtension {
    @Override
    public void handleDeployment(final DeploymentInfo deploymentInfo, final ServletContext servletContext) {
        deploymentInfo
           .addInnerHandlerChainWrapper(handler -> new AuthEventHandler(handler));
    }
}

Finally register the extension by adding its fully qualified class name to the file /META-INF/services/io.undertow.servlet.ServletExtension.

Now jar the result up and add that jar to a Java EE application. In such application, the two authentication events shown in the source above can now be observed as follows:

@SessionScoped
public class SessionAuthListener implements Serializable {
 
    private static final long serialVersionUID = 1L;
 
    public void onAuthenticated(@Observes AuthenticatedEvent event) {
        String username = event.getUserPrincipal().getName();
        // Do something with name, e.g. audit, 
        // load User instance into session, etc
    }
 
    public void onLoggedOut(@Observes LoggedOutEvent event) {
        // take some action, e.g. audit, null out User, etc
    }
}

Experimenting with the above code proved that it indeed worked and it appears to be incredibly useful. Unfortunately this is now all specific to Undertow and thus only usable there and in servers that use Undertow (e.g. JBoss). It would be a real step forward for security in Java EE if it would support these simple but highly effective authentication events using a standardized API.

Arjan Tijms

css.php best counter