Saturday, November 30, 2013

Java EE 7 Tip of the Day: Web Application Lifecycle Listeners

Introduction

I have been trying to come up with a way to highlight these fascinating classes in a way that demonstrates their power and usefulness. In my post Java EE 7 Tip of the Day: Programmatic Control of Session Tracking with ServletContextListener I highlighted the use of the ServletContextListener to set session tracking programmatically. However, there are a number of listener classes that can help you. Here they are in generally decreasing scope:

Web Application Lifecycle Listeners

ServletContainerInitializer

The ServletContainerInitializer is a special case that must be registered with the Service Provider Interface (SPI) loading mechanism. Please see the Javadocs for more details on its usage. That is a topic for a different post.

ServletContextListener

The ServletContextListener has been previously mentioned in another post as noted above. This listener is notified of lifecycle changes, e.g. context initialized and destroyed. This gives the developer the opportunity to perform application setup such as setting a logger, or enabling other listeners. It also provides a clean mechanism to handle application cleanup when an application is shutdown, or disabled.

ServletContextAttributeListener

The ServletContextAttributeListener listens for events that occur when attributes are added, modified, or removed from the ServletContext. This could be used to modify those attributes, or perhaps log them. Interestingly, the order in which the implementations are invoked is not specified.

HttpSessionListener

The HttpSessionListener listens for session creation, and destruction events. I have found this to be one of the most useful classes in a web application. This can be used to set up a persistent data connection while the session is valid, and close the connection when the session expires, or is invalidated.

The listener implementations are invoked in the order of declaration, and destroyed in reverse order. A way to think of this is like the layers of filo dough. If you go from top to bottom, then you must reverse order from bottom to top.

HttpSessionAttributeListener

The sibling interface HttpSessionAttributeListener is the second most used class in my toolbox for listeners behind the ServletContextListener and HttpSessionListener. I have found that I often need to examine when attributes are added, modified, or removed from a session. This is the tool I use to manage that.

Keep in mind that the HttpSessionAttributeListener behaves like the ServletContextAttributeListener in that the order in which the implementations are invoked is unspecified. This means that you can not rely on a specified ordering to take place across containers. Usually, the ordering is consistent on a container basis, but the contract is explicit that the ordering is undefined.

HttpSessionIdListener

This is a new to Java EE 7. The HttpSessionBindingListener is used to handle when a HttpSession ID changes. This is usually the result of a HttpServletRequest.changeSessionId() command. This was added in Java EE 7 to help solve a security issue called Session Fixation. Typically, you would change the session id after the user successfully authenticates. This can help you with this transition. Otherwise, you need to do a lot of work to make it happen. In this case, it is just great to be able to let the API handle it.

This interface will allow you to modify your application based on the ID change. Please note that the order in which the implementations are invoked is not specified.

HttpSessionBindingListener

The HttpSessionBindingListener listens for binding events. This occurs when an object is bound, or unbound from a session. There are a couple of examples from BalusC on Stackoverflow. Here are a couple: Getting SessionScoped bean from HttpSessionListener? and How to access HTTP sessions in Java. These are just a couple of examples on its usage. I personally have never used it directly. I was just thinking I should come up with my own example for it.

ServletRequestListener

The ServletRequestListener is another listener that I use. However its usage is not as frequent as the other aforementioned ones. I usually end up checking the ServletRequest with an instanceof for HttpServletRequest and casting it. This listener will allow you to modify an individual request.

Since it has every request pass through it, you should make sure that there is no excessive overhead in the listener such as database lookups, or excessive logging. Abuse of this listener can result in massive performance issues.

ServletRequestAttributeListener

Finally, we have the ServletRequestAttributeListener that listens for request attribute changes. The same warning as noted in the ServletRequestListener applies here. Any excessive overhead will result in dramatic performance decreases.

Like the other attribute listeners, this listener does not have a specific order in which they are invoked.

Code Examples

The code for this project was developed using NetBeans 7.4, Apache Maven, Mercurial, and is hosted on Bitbucket. The code in the examples is somewhat contrived, but does demonstrate the functionality of the listeners.

I strongly recommend downloading the code, executing it, and looking at the output. It will give you a better picture of how the code works, and also show you some hidden "features" of your application that you may be unaware of.

The source code can be downloaded from here: web-application-listeners.

ServletContextListenerImpl.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.bluelotussoftware.web.listener.impl;
 
import java.text.MessageFormat;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Set;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.SessionTrackingMode;
import javax.servlet.annotation.WebListener;
 
/**
 * An implementation of {@link ServletContextListener} web application lifecycle
 * listener.
 *
 * @author John Yeary
 * @version 1.0
 */
@WebListener
public class ServletContextListenerImpl implements ServletContextListener {
 
    /**
     * {@inheritDoc}
     * <p>
     * We add a {@code HashMap<String,Object>} attribute to the
     * {@link ServletContext}, and set the session tracking to use Cookie
     * tracking. This will override the web.xml file.</p>
*/
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println(MessageFormat.format("ServletContext initialized {0}", sce.getServletContext().getServerInfo()));
 
        // Add an attribute to the ServletContext.
        sce.getServletContext().setAttribute("com.bluelotussoftware.SERVLET_CONTEXT_MAP", new HashMap<String, Object>());
 
        // Set the session tracking globally for this servlet context to Cookie. This will override web.xml session tracking.
        Set<sessiontrackingmode> modes = EnumSet.noneOf(SessionTrackingMode.class);
        modes.add(SessionTrackingMode.COOKIE);
        sce.getServletContext().setSessionTrackingModes(modes);
 
    }
 
    /**
     * {@inheritDoc}
     * <p>
     * Destroy the {@code HashMap<String,Object>} attribute when the context is
     * destroyed.</p>
*/
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
 
        // Remove the attribute we set in the ServletContext.
        sce.getServletContext().removeAttribute("com.bluelotussoftware.SERVLET_CONTEXT_MAP");
        System.out.println("ServletContext destroyed.");
    }
}

ServletContextAttributeListenerImpl.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.bluelotussoftware.web.listener.impl;
 
import java.text.MessageFormat;
import javax.servlet.ServletContextAttributeEvent;
import javax.servlet.ServletContextAttributeListener;
import javax.servlet.annotation.WebListener;
 
/**
 * Web application lifecycle listener.
 *
 * @author John Yeary
 * @version 1.0
 */
@WebListener
public class ServletContextAttributeListenerImpl implements ServletContextAttributeListener {
 
    /**
     * {@inheritDoc}
     */
    @Override
    public void attributeAdded(ServletContextAttributeEvent event) {
        System.out.println(MessageFormat.format("{0} added servlet context attribute {1}", event.getName(), event.getValue()));
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    public void attributeRemoved(ServletContextAttributeEvent event) {
        System.out.println(MessageFormat.format("{0} removed servlet context attribute {1}", event.getName(), event.getValue()));
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    public void attributeReplaced(ServletContextAttributeEvent event) {
        System.out.println(MessageFormat.format("{0} replaced servlet context attribute {1}", event.getName(), event.getValue()));
    }
}

HttpSessionListenerImpl.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.bluelotussoftware.web.listener.impl;
 
import java.text.DateFormat;
import java.text.MessageFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.UUID;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
 
/**
 * Web application lifecycle listener.
 *
 * @author John Yeary
 * @version 1.0
 */
@WebListener
public class HttpSessionListenerImpl implements HttpSessionListener {
 
    private static final DateFormat df = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG);
 
    /**
     * {@inheritDoc}
     */
    @Override
    public void sessionCreated(HttpSessionEvent se) {
        System.out.println(MessageFormat.format("Session ID {0} created at {1}", se.getSession().getId(), df.format(new Date(se.getSession().getCreationTime()))));
 
        // Add a session attribute and add the key to the ServletContext attribute com.bluelotussoftware.SERVLET_CONTEXT_MAP.
        UUID uuid = UUID.randomUUID();
        se.getSession().setAttribute(uuid.toString(), uuid);
        HashMap<String, Object> map = (HashMap<String, Object>) se.getSession().getServletContext().getAttribute("com.bluelotussoftware.SERVLET_CONTEXT_MAP");
        map.put(se.getSession().getId(), uuid.toString());
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
         
        // Remove ServletContext attribute. We don't need to remove our session attribute since the session is being destroyed.
        HashMap<String, Object> map = (HashMap<String, Object>) se.getSession().getServletContext().getAttribute("com.bluelotussoftware.SERVLET_CONTEXT_MAP");
        map.remove(se.getSession().getId());
        System.out.println(MessageFormat.format("Session ID {0} destroyed at {1}", se.getSession().getId(), df.format(new Date(System.currentTimeMillis()))));
    }
}

HttpSessionAttributeListenerImpl.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.bluelotussoftware.web.listener.impl;
 
import java.text.MessageFormat;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
 
/**
 * Web application lifecycle listener.
 *
 * @author John Yeary
 * @version 1.0
 */
@WebListener
public class HttpSessionAttributeListenerImpl implements HttpSessionAttributeListener {
 
    /**
     * {@inheritDoc}
     */
    @Override
    public void attributeAdded(HttpSessionBindingEvent event) {
        System.out.println(MessageFormat.format("Session ID {0} {1} added {2}", event.getSession().getId(), event.getName(), event.getValue()));
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    public void attributeRemoved(HttpSessionBindingEvent event) {
        System.out.println(MessageFormat.format("Session ID {0} {1} removed {2}", event.getSession().getId(), event.getName(), event.getValue()));
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    public void attributeReplaced(HttpSessionBindingEvent event) {
        System.out.println(MessageFormat.format("Session ID {0} {1} replaced {2}", event.getSession().getId(), event.getName(), event.getValue()));
    }
}

ServletRequestListenerImpl.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package com.bluelotussoftware.web.listener.impl;
 
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.UUID;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
 
/**
 * Web application lifecycle listener.
 *
 * @author John Yeary
 * @version 1.0
 */
@WebListener
public class ServletRequestListenerImpl implements ServletRequestListener {
 
    /**
     * {@inheritDoc}
     */
    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        if (sre.getServletRequest() instanceof HttpServletRequest) {
            HttpServletRequest hsr = (HttpServletRequest) sre.getServletRequest();
            HttpSession session = hsr.getSession(false);
             
            // We will remove the old UUID and set a new UUID
            if (session != null) {
                HashMap<String, Object> map = (HashMap<String, Object>) session.getServletContext().getAttribute("com.bluelotussoftware.SERVLET_CONTEXT_MAP");
                String uuid_key = (String) map.get(session.getId());
                session.removeAttribute(uuid_key);
                UUID uuid = UUID.randomUUID();
                session.setAttribute(uuid.toString(), uuid);
                map.put(session.getId(), uuid.toString());
                hsr.removeAttribute("com.bluelotussoftware.UUID_TOKEN");
            }
        }
        System.out.println("Servlet Request destroyed.");
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        System.out.println("Servlet Request initialized.");
        if (sre.getServletRequest() instanceof HttpServletRequest) {
            HttpServletRequest hsr = (HttpServletRequest) sre.getServletRequest();
            Cookie[] cookies = hsr.getCookies();
            if (cookies != null && cookies.length > 0) {
                for (Cookie cookie : cookies) {
                    System.out.println(MessageFormat.format("Cookie --> {0} value --> {1} domain --> {2} max-age --> {3}", cookie.getName(), cookie.getValue(), cookie.getDomain(), cookie.getMaxAge()));
                }
            }
 
            // We want to set a unique UUID for each request.
            HttpSession session = hsr.getSession(false);
            if (session != null) {
                HashMap<String, Object> map = (HashMap<String, Object>) session.getServletContext().getAttribute("com.bluelotussoftware.SERVLET_CONTEXT_MAP");
                String uuid_key = (String) map.get(session.getId());
                UUID uuid = (UUID) session.getAttribute(uuid_key);
                hsr.setAttribute("com.bluelotussoftware.UUID_TOKEN", uuid.toString());
            }
 
        }
    }
}

ServletRequstAttributeListenerImpl.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.bluelotussoftware.web.listener.impl;
 
import java.text.MessageFormat;
import javax.servlet.ServletRequestAttributeEvent;
import javax.servlet.ServletRequestAttributeListener;
import javax.servlet.annotation.WebListener;
 
/**
 * Web application lifecycle listener.
 *
 * @author John Yeary
 * @version 1.0
 */
@WebListener
public class ServletRequstAttributeListenerImpl implements ServletRequestAttributeListener {
 
    /**
     * {@inheritDoc}
     */
    @Override
    public void attributeAdded(ServletRequestAttributeEvent srae) {
        System.out.println(MessageFormat.format("{0} added request attribute {1}", srae.getName(), srae.getValue()));
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    public void attributeRemoved(ServletRequestAttributeEvent srae) {
        System.out.println(MessageFormat.format("{0} removed request attribute {1}", srae.getName(), srae.getValue()));
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    public void attributeReplaced(ServletRequestAttributeEvent srae) {
        System.out.println(MessageFormat.format("{0} replaced request attribute {1}", srae.getName(), srae.getValue()));
    }
}

Conclusion

Web application lifecycle listeners are essential tools for the web developer. If you are not using them, you should consider them to simplify your web development. Hopefully the explanations and code provided help you along your path to becoming a great developer.

How to use PowerMock and Mockito to test static and private methods

Introduction

This article is not another diatribe to tell you the importance of unit testing. I think we can all agree that it is important. This is about solving an issue that comes up frequently in unit testing. How do I test static methods, and how do you test private methods.

Until PowerMock, most developers were told you can't really test a static method per se. I will show how to do it below.

A common mechanism for testing private methods is to change them to protected. This is not a good solution for a number of reasons. So how do we test them? Again, PowerMock has come to the rescue.
I want solutions. I use Arquillian, but I am not such a zealot that I will chant the mantra that mocking is evil and should not be done. It is a means to an end. That being said, we use Mockito which in my professional experience is the best current mocking framework available. It is easy to setup and use.
However, Mockito has its limitations. Two are mentioned above. The fine developers of PowerMock have come up with an additional framework to use in conjunction with Mockito to get around the limitations.

Solution

The Apache Maven sample code for the project was developed using NetBeans and can be found on Bitbucket here: unit-testing-mockito-powermockito.

Since I am a Apache Maven user, I simply add the relevant frameworks to my pom.xml as shown below:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<dependencies>
<dependency>
<groupid>junit</groupId>
<artifactid>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
<type>jar</type>
</dependency>
<dependency>
<groupid>org.mockito</groupId>
<artifactid>mockito-core</artifactId>
<version>1.9.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupid>org.powermock</groupId>
<artifactid>powermock-module-junit4</artifactId>
<version>1.5.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupid>org.powermock</groupId>
<artifactid>powermock-api-mockito</artifactId>
<version>1.5.1</version>
<scope>test</scope>
</dependency>
</dependencies>
Once the test frameworks are in place, I can use the PowerMockRunner to help me with my testing. The following are some simple classes to demonstrate how to test. The first is a class to generate UUID values for IDs.

IdentityUtilities.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.bluelotussoftware.example;
 
import java.util.UUID;
 
/**
*
* @author John Yeary
*/
public class IdentityUtilities {
 
/**
* Generates a {@link UUID}
*
* @return a {@code String} representation of a UUID.
*/
public static String getUUID() {
return UUID.randomUUID().toString();
}
}
Next, I need a class to represent a person. I am interested in testing the private method generateId.

Person.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package com.bluelotussoftware.example;
 
import java.text.MessageFormat;
import java.util.Objects;
 
/**
*
* @author John Yeary
*/
public class Person {
 
private String id;
private String firstName;
private String lastName;
 
public Person() {
}
 
public void initialize() {
if (null == id) {
id = generateId();
}
}
 
private String generateId() {
return IdentityUtilities.getUUID();
}
 
public String getFirstName() {
return firstName;
}
 
public void setFirstName(String firstName) {
this.firstName = firstName;
}
 
public String getLastName() {
return lastName;
}
 
public void setLastName(String lastName) {
this.lastName = lastName;
}
 
public String getId() {
return id;
}
 
public String getFullName() {
return MessageFormat.format("{0} {1}", firstName, lastName);
}
 
@Override
public int hashCode() {
int hash = 7;
hash = 97 * hash + Objects.hashCode(this.id);
hash = 97 * hash + Objects.hashCode(this.firstName);
hash = 97 * hash + Objects.hashCode(this.lastName);
return hash;
}
 
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Person other = (Person) obj;
if (!Objects.equals(this.id, other.id)) {
return false;
}
if (!Objects.equals(this.firstName, other.firstName)) {
return false;
}
if (!Objects.equals(this.lastName, other.lastName)) {
return false;
}
return true;
}
 
@Override
public String toString() {
return "Person{" + "id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + '}';
}
 
}
Finally, we need our PersonTest class to test our Person object. The explanation follows the code.

PersonTest.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.bluelotussoftware.example;
 
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import static org.mockito.Mockito.*;
import org.powermock.api.mockito.PowerMockito;
import static org.powermock.api.mockito.PowerMockito.verifyPrivate;
 
/**
*
* @author John Yeary
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({IdentityUtilities.class, Person.class})
public class PersonTest {
 
public PersonTest() {
}
 
/**
* Test of initialize method, of class Person.
*/
@Test
public void testInitialize() {
System.out.println("initialize");
PowerMockito.mockStatic(IdentityUtilities.class);
when(IdentityUtilities.getUUID()).thenReturn("ABC-123");
Person instance = new Person();
instance.initialize();
String result = instance.getId();
assertEquals(result, "ABC-123");
}
 
@Test
public void testGenerateId() throws Exception {
System.out.println("generateId()");
Person instance = PowerMockito.spy(new Person());
 
/*
* Setup the expectation to the private method using the method name
*/
PowerMockito.when(instance, "generateId").thenReturn("UNIT-1A");
 
instance.initialize();
 
assertEquals("UNIT-1A", instance.getId());
 
// Optionally verify that the private method was actually called
verifyPrivate(instance).invoke("generateId");
 
}
 
@Test
public void ObjectsNotEqual() {
System.out.println("Objects not Equal");
Person p = new Person();
Object o = new Object();
assertFalse(p.equals(o));
}
 
}
The @RunWith(PowerMockRunner.class) annotation tells jUnit that we are using an extension to handle testing of this class. This tells jUnit to use this test runner instead of its own test runner.
The @PrepareForTest({IdentityUtilities.class, Person.class}) serves multiple purposes here. The first is to tell PowerMock that we need to do some preparation of the classes to instrument them for our testing. Classes defined using this annotation are typically those that needs to be byte-code manipulated. This includes final classes, classes with final, private, static or native methods that should be mocked and also classes that should be return a mock object upon instantiation. IdentityUtilities.class is our class with a static method, and Person.class contains our private method.

The first test testInitialize() behaves like any other Mockito test with the exception that we mock it using PowerMockito.mockStatic(IdentityUtilities.class) to initialize it. This makes testing static methods as easy as any other Mockito test.

The second test requires a little more explanation. In this case, we spy on the object we are creating. This allows us to instrument it. Then using the "spied" instance we use PowerMockito.when(instance, "generateId").thenReturn("UNIT-1A") to tell the framework that when the private method "generateId" is called, we want to return a specific value. Then we initialize the instance, and check the value is what we expected. Finally, we can optionally check to make sure that the method was actually called by using verifyPrivate(instance).invoke("generateId").

Conclusion

Don't short change yourself, and don't modify your beautifully architected code just to satisfy a testing framework. Find a framework like PowerMock to help you test your code as written instead. You should only change the code because you want to do so, or because you discover flaws in it.

Friday, November 29, 2013

Cleaning Multiple Maven and Ant Projects

I am trying to put a bunch of my code on my Google Drive to back it up. I have had a couple of bad experiences lately around failed hardware, and needing some code that I could not get to from a remote machine. I copied all of the projects to the Google Drive in the interim. I am not sure if this is a good idea since the builds in the IDE seem cause it some issues, but until I have everything on Bitbucket, I will need to have it on the drive.

Anyway, I was looking for a way to make sure all of the projects were clean so that I could conserve space, save money, and keep the scanning to a more minimal level. So how do you step through each directory and issue commands to clean the projects. Here are my examples:

Maven


1
find . -maxdepth 1 -type d -exec sh -c '(cd {} && mvn clean)' ';'

Ant


1
find . -maxdepth 1 -type d -exec sh -c '(cd {} && ant clean)' ';'

Please note that I set the maxdepth to 1 so that it would not recursively keep digging down directory levels invoking the command. The -type d option is to only examine directories starting from the current directory. The cd{} shell command tells the script to go into the directory that was found and execute the following command.

I hope that this might save someone a few minutes.

Monday, November 25, 2013

Java EE 7 Tip of the Day: Programmatic Control of Session Tracking with ServletContextListener

Have you ever had a developer modify a web.xml file to change session configuration? Have they forgotten to change it back from "developer mode" back to "deployment mode"? I am sure all of you have done something like that at some point in time. I am no exception. This tip of the day comes from that very issue.

The issue here is that we want to control session tracking. In the web.xml file you may have something that looks like:

1
2
3
4
<session-config>
    <session-timeout>30</session-timeout>
    <tracking-mode>COOKIE</tracking-mode>
</session-config>
OK, let me re-phrase that. You were expecting that, but instead you have:
1
2
3
4
<session-config>
    <session-timeout>30</session-timeout>
    <tracking-mode>URL</tracking-mode>
</session-config>

This is a problem. You see we are trying to be more secure around our session and having the JSESSIONID in the URL is not helping much with that. Well, we can perhaps keep our application safe, and frustrate our developer slightly if he doesn't figure out this little bit of magic. We can control the session tracking programmatically in an "obvious" well-known location. We can enlist a ServletContextListener to help us.

The ServletContextListener can help us by listening for when our application is being initialized, and set the session tracking back to COOKIE for us.  The implementation is simple, and will help foil the "developer mode-itis" that sometimes infects the code.

ServletContextListenerImpl.java

Note: Even if you didn't have a value set in the web.xml file, this would set it to COOKIE.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.bluelotussoftware.web.listener.impl;
 
import java.text.MessageFormat;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Set;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.SessionTrackingMode;
import javax.servlet.annotation.WebListener;
 
/**
 * An implementation of {@link ServletContextListener} web application lifecycle
 * listener.
 *
 * @author John Yeary
 * @version 1.0
 */
@WebListener
public class ServletContextListenerImpl implements ServletContextListener {
 
    /**
     * {@inheritDoc}
     * * We add a {@code HashMap<string bject="">} attribute to the
     * {@link ServletContext}, and set the session tracking to use Cookie
     * tracking. This will override the web.xml file.</string>
 
 
     */
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        // Set the session tracking globally for this servlet context to Cookie. This will override web.xml session tracking.
        Set<sessiontrackingmode> modes = EnumSet.noneOf(SessionTrackingMode.class);
        modes.add(SessionTrackingMode.COOKIE);
        sce.getServletContext().setSessionTrackingModes(modes);
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
    }
}

Friday, November 22, 2013

ServiceLoader<S> Dynamic Reloading on JEE7 Web Application

Introduction

We had a technical discussion about updating an application on the fly in Java EE. There were a couple of issues that we were trying to resolve. One was a simple way to add functionality to a web application while deployed, and update the running application. It could be functionality like a new module, or service, or something like, the classic example for the ServiceLoader, codecs.

Additionally, we needed to be able to add the functionality without adding another framework to make it happen. It needed to be something that was available in the existing Java SE/EE APIs. Again, the ServiceLoader seemed to be a possible solution.

I did a Proof of Concept (POC) for using a ServiceLoader to accomplish adding additional services to our application. That worked, but required a restart of the server, or a reload of the application at a minimum. This assumes that the application was NOT auto-deployed. It turns out that worked, but really was only a half-measure. I wanted to see if I could solve the dynamic reloading part, and I did.

Solution

Before we see the code, how does it work in general. We use the ServiceLoader which is part of the Service Provider Interface (SPI) functionality of Java. It is a hidden gem for those who need it, and framework creators can take advantage of this simple, easy to use technology. The ServiceLoader is managed by a Singleton that returns an instance of the ServiceLoader that will return our SPI implementations. In my example, I create an interface that is packaged separately in its own jar and is shared between the deployed web application and the service implementations. The ServiceLoader loads this interface and makes the implementations available. The cool part is that our Singleton class also has some cool NIO and NIO.2 help with the ZipFileSystemProvider to load the implementations from newly added jars. It also has some demo of how to use a URLClassLoader to add our new implementations and update the ServiceLoader.

The code for the project can be found here:

Log.java

Here is our interface that is common between the web service and the SPI implementations.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.bluelotussoftware.service.spi;
 
/**
 * Example logging interface.
 *
 * @author John Yeary
 * @version 1.0
 */
public interface Log {
 
    void trace(String message);
 
    void debug(String message);
 
    void info(String message);
 
    void warn(String message);
 
    void severe(String message);
 
    void error(String message);
 
    void fatal(String message);
}

LogImpl.java

This simple interface will allow me to demonstrate the power of the SPI. Here is an implementation of the API.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.bluelotussoftware.service.impl;
 
import com.bluelotussoftware.service.spi.Log;
 
/**
 * Example implementation of {@link Log}.
 * @author John Yeary
 * @version 1.0
 */
public class LogImpl implements Log {
 
    @Override
    public void trace(String message) {
        log("TRACE --> " + message);
    }
 
    @Override
    public void debug(String message) {
        log("DEBUG --> " + message);
    }
 
    @Override
    public void info(String message) {
        log("INFO --> " + message);
    }
 
    @Override
    public void warn(String message) {
        log("WARN --> " + message);
    }
 
    @Override
    public void severe(String message) {
        log("SEVERE --> " + message);
    }
 
    @Override
    public void error(String message) {
        log("ERROR --> " + message);
    }
 
    @Override
    public void fatal(String message) {
        log("FATAL --> " + message);
    }
 
    private void log(String message) {
        System.out.println(message);
    }
}

LogService

This class is the magic behind the SPI and allows us to dyanmically reload the new implementations as we add them to the WEB-INF/lib directory.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package com.bluelotussoftware.service;
 
import com.bluelotussoftware.service.spi.Log;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.Charset;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.spi.FileSystemProvider;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ServiceLoader;
import javax.faces.context.FacesContext;
import javax.servlet.ServletContext;
 
/**
 * Singleton {@link Log} service with {@link ClassLoader} and
 * {@link ServiceLoader} reloading capabilities for use in a web application.
 *
 * @author John Yeary
 * @version 1.0
 */
public class LogService {
 
    private static LogService service;
    private ServiceLoader serviceLoader;
 
    private LogService() {
        serviceLoader = ServiceLoader.load(Log.class);
    }
 
    public static synchronized LogService getInstance() {
        if (service == null) {
            service = new LogService();
        }
        return service;
    }
 
    public List<log> getLoggers() {
        List<log> loggers = new ArrayList<>();
        Iterator<log> it = serviceLoader.iterator();
        while (it.hasNext()) {
            loggers.add(it.next());
        }
        return loggers;
    }
 
    public Log getFirstAvailableLogger() {
        Log log = null;
        Iterator<log> it = serviceLoader.iterator();
        while (it.hasNext()) {
            log = it.next();
            break;
        }
        return log;
    }
 
    public void reload() throws MalformedURLException, IOException, ClassNotFoundException {
 
        // 1. Read the WEB-INF/lib directory and get a list of jar files.
        // 2. Examine the jar files and look for META-INF/services directories.
        // 3. If the directory has services, read the file names from each service entry.
        // 4. Store the class names in a list.
        // 5. Create a list of URLs for files.
        // 6. Create a URLClassLoader, add the parent ClassLoader and URLs.
        // 7. Call URLClassloader.loadClass(String name) using saved names.
        // 8. Create a new ServiceLoader instance with the URLClassLoader, and call load on the interface.
        ServletContext context = (ServletContext) FacesContext.getCurrentInstance().getExternalContext().getContext();
        Path webInfLibDirectory = Paths.get(context.getRealPath("WEB-INF/lib"));
        URLClassLoader urlcl;
        List<url> jarURLs = new ArrayList<>();
        FileSystemProvider provider = getZipFileSystemProvider();
        List<string> implementationsToLoad = new ArrayList<>();
 
        if (Files.exists(webInfLibDirectory, LinkOption.NOFOLLOW_LINKS)) {
            List<path> files = listJars(webInfLibDirectory);
 
            for (Path p : files) {
                info("LOCATED JAR " + p.toFile().getName());
                jarURLs.add(p.toUri().toURL());
                FileSystem fs = provider.newFileSystem(p, new HashMap<String, Object>());
                Path serviceDirectory = fs.getPath("/META-INF", "services");
                info("SCANNING SERVICES");
 
                if (Files.exists(serviceDirectory)) {
                    DirectoryStream<path> serviceListings = Files.newDirectoryStream(serviceDirectory);
 
                    for (Path px : serviceListings) {
                        List<string> services = Files.readAllLines(px, Charset.forName("UTF-8"));
                        info(MessageFormat.format("SERVICES FOUND: {0}", Arrays.toString(services.toArray())));
                        implementationsToLoad.addAll(services);
                    }
                }
            }
 
            urlcl = new URLClassLoader(jarURLs.toArray(new URL[jarURLs.size()]), context.getClassLoader());
 
            load(implementationsToLoad, urlcl);
 
            serviceLoader = ServiceLoader.load(Log.class, urlcl);
            Iterator<log> it = serviceLoader.iterator();
            while (it.hasNext()) {
                info(it.next().getClass().getName());
            }
        }
    }
 
    private List<path> listJars(Path path) throws IOException {
        List<path> jars = new ArrayList<>();
        DirectoryStream<path> ds = Files.newDirectoryStream(path, "*.jar");
 
        for (Path child : ds) {
            if (!Files.isDirectory(child)) {
                jars.add(child);
            }
        }
 
        return jars;
    }
 
    private void load(final List<string> FQCN, final ClassLoader classLoader)
            throws ClassNotFoundException {
        for (String s : FQCN) {
            info(MessageFormat.format("LOAD CLASS {0}", s));
            Class<?> clazz = classLoader.loadClass(s);
            info(MessageFormat.format("CLASS {0} LOADED", clazz.getName()));
        }
    }
 
    private static FileSystemProvider getZipFileSystemProvider() {
        for (FileSystemProvider provider : FileSystemProvider.installedProviders()) {
            if ("jar".equals(provider.getScheme())) {
                return provider;
            }
        }
        return null;
    }
 
    private void info(final String message) {
        getFirstAvailableLogger().info(message);
    }
}

com.bluelotussoftware.service.spi.Log

The SPI file located in the META-INF/services directory of your jar file. This is one version, but each implementation would have its own. The file name is the same as the interface, and the listings on each line are concrete implementations.

1
2
com.bluelotussoftware.service.impl.LogImpl #Default implementation.
com.bluelotussoftware.service.impl.SecondaryLogger

IndexBean.java

This bean has a cool NIO method of handling uploaded files. So I thought I would add it. Combined with PrimeFaces, it is functional and pretty.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package com.bluelotussoftware.web;
 
import com.bluelotussoftware.service.LogService;
import com.bluelotussoftware.service.spi.Log;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.faces.context.FacesContext;
import javax.servlet.ServletContext;
import org.primefaces.event.FileUploadEvent;
 
/**
 * Page bean for index.xhtml
 *
 * @author John Yeary
 * @version 1.0
 */
@ManagedBean
@RequestScoped
public class IndexBean {
 
    private Log log;
    private LogService service;
 
    public IndexBean() {
        service = LogService.getInstance();
        log = service.getFirstAvailableLogger();
        log.info("Constructor called.");
    }
 
    public String getLoggerName() {
        log.info("getLoggerName() called.");
        return log.getClass().getName();
    }
 
    public List<log> getLoggers() {
        return service.getLoggers();
    }
 
    public void handleFileUpload(FileUploadEvent event) {
        Path path = Paths.get(((ServletContext) FacesContext.getCurrentInstance().
                getExternalContext().getContext())
                .getRealPath("WEB-INF/lib"), event.getFile().getFileName());
        try {
            Files.copy(event.getFile().getInputstream(), path);
        } catch (IOException ex) {
            log.error(ex.getMessage());
        }
        FacesMessage msg = new FacesMessage("Succesful", event.getFile().getFileName() + " is uploaded.");
        FacesContext.getCurrentInstance().addMessage(null, msg);
    }
 
    public String reload() {
        try {
            service.reload();
            log.info("LogService reloaded.");
        } catch (IOException | ClassNotFoundException ex) {
            log.error(ex.getMessage());
        }
        return null;
    }
}

Conclusion

If you need to add some additional functionality to a web application, and reload it on the fly, Give the ServiceLoader a try. You might just be impressed.

Popular Posts