Thursday, August 23, 2007

Monday, August 20, 2007

Handling Security with Ajax, DWR and Acegi

This is an extension of a previous post that described how to secure your method calls using Acegi security. Here, I will go through how to secure your Asynchronous calls, using the same example with some modifications to include Ajax calls using Direct Web Remoting (DWR).
  1. Create the example project as shown in "Spring security with Acegi Security Framework". This will be the starting point.
  2. Create the SecureDAO object.
    package test;

    public class SecureDAO {
    public String create() {
    System.out.println("Create");
    return "create";
    }

    public String read() {
    System.out.println("read");
    return "read";
    }

    public String update() {
    System.out.println("update");
    return "update";
    }
    }
  3. Update the applicationContext.xml file to include the security definitions by adding the following bean definitions as shown below
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

    <beans>

    <bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
    <property name="filterInvocationDefinitionSource">
    <value>
    CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
    PATTERN_TYPE_APACHE_ANT
    /**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
    </value>
    </property>
    </bean>

    <bean id="httpSessionContextIntegrationFilter" class="org.acegisecurity.context.HttpSessionContextIntegrationFilter"/>

    <bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter">
    <constructor-arg value="/index.jsp"/>
    <constructor-arg>
    <list>
    <ref bean="rememberMeServices"/>
    <bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>
    </list>
    </constructor-arg>
    </bean>

    <bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
    <property name="authenticationManager" ref="authenticationManager"/>
    <property name="authenticationFailureUrl" value="/login.jsp?errorId=1"/>
    <property name="defaultTargetUrl" value="/"/>
    <property name="filterProcessesUrl" value="/j_acegi_security_check"/>
    <property name="rememberMeServices" ref="rememberMeServices"/>
    </bean>

    <bean id="securityContextHolderAwareRequestFilter" class="org.acegisecurity.wrapper.SecurityContextHolderAwareRequestFilter"/>

    <bean id="rememberMeProcessingFilter" class="org.acegisecurity.ui.rememberme.RememberMeProcessingFilter">
    <property name="authenticationManager" ref="authenticationManager"/>
    <property name="rememberMeServices" ref="rememberMeServices"/>
    </bean>

    <bean id="anonymousProcessingFilter" class="org.acegisecurity.providers.anonymous.AnonymousProcessingFilter">
    <property name="key" value="changeThis"/>
    <property name="userAttribute" value="anonymousUser,ROLE_ANONYMOUS"/>
    </bean>

    <bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
    <property name="authenticationEntryPoint">
    <bean class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
    <property name="loginFormUrl" value="/login.jsp"/>
    <property name="forceHttps" value="false"/>
    </bean>
    </property>
    <property name="accessDeniedHandler">
    <bean class="org.acegisecurity.ui.AccessDeniedHandlerImpl">
    <property name="errorPage" value="/denied.jsp"/>
    </bean>
    </property>
    </bean>

    <bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
    <property name="authenticationManager" ref="authenticationManager"/>
    <property name="accessDecisionManager">
    <bean class="org.acegisecurity.vote.AffirmativeBased">
    <property name="allowIfAllAbstainDecisions" value="false"/>
    <property name="decisionVoters">
    <list>
    <bean class="org.acegisecurity.vote.RoleVoter"/>
    <bean class="org.acegisecurity.vote.AuthenticatedVoter"/>
    </list>
    </property>
    </bean>
    </property>
    <property name="objectDefinitionSource">
    <value>
    CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
    PATTERN_TYPE_APACHE_ANT
    /secure/admin/**=ROLE_ADMIN
    /secure/**=IS_AUTHENTICATED_REMEMBERED
    /**=IS_AUTHENTICATED_ANONYMOUSLY
    </value>
    </property>
    </bean>

    <bean id="rememberMeServices" class="org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices">
    <property name="userDetailsService" ref="userDetailsService"/>
    <property name="tokenValiditySeconds" value="1800"></property>
    <property name="key" value="changeThis"/>
    </bean>

    <bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
    <property name="providers">
    <list>
    <ref local="daoAuthenticationProvider"/>
    <bean class="org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider">
    <property name="key" value="changeThis"/>
    </bean>
    <bean class="org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider">
    <property name="key" value="changeThis"/>
    </bean>
    </list>
    </property>
    </bean>

    <bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
    <property name="userDetailsService" ref="userDetailsService"/>
    <property name="userCache">
    <bean class="org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache">
    <property name="cache">
    <bean class="org.springframework.cache.ehcache.EhCacheFactoryBean">
    <property name="cacheManager">
    <bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>
    </property>
    <property name="cacheName" value="userCache"/>
    </bean>
    </property>
    </bean>
    </property>
    </bean>

    <bean id="userDetailsService" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
    <property name="userProperties">
    <bean class="org.springframework.beans.factory.config.PropertiesFactoryBean">
    <property name="location" value="/WEB-INF/users.properties"/>
    </bean>
    </property>
    </bean>

    <bean id="loggerListener" class="org.acegisecurity.event.authentication.LoggerListener"/>

    <bean id="methodSecurityInterceptor" class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
    <property name="authenticationManager">
    <ref bean="authenticationManager" />
    </property>
    <property name="accessDecisionManager">
    <bean class="org.acegisecurity.vote.AffirmativeBased">
    <property name="allowIfAllAbstainDecisions" value="false" />
    <property name="decisionVoters">
    <list>
    <bean class="org.acegisecurity.vote.RoleVoter" />
    <bean class="org.acegisecurity.vote.AuthenticatedVoter" />
    </list>
    </property>
    </bean>
    </property>
    <property name="objectDefinitionSource">
    <value>
    test.SecureDAO.*=IS_AUTHENTICATED_REMEMBERED
    test.SecureDAO.u=ROLE_ADMIN
    </value>
    </property>
    </bean>


    <bean id="autoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <property name="interceptorNames">
    <list>
    <value>methodSecurityInterceptor</value>
    </list>
    </property>
    <property name="beanNames">
    <list>
    <value>secureDAO</value>
    </list>
    </property>
    </bean>

    <bean id="secureDAO" class="test.SecureDAO" />
    </beans>
  4. Update the Web deployment descriptor to forward DWR requests to the DWR Servlet
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
    <display-name>DWRSpring</display-name>
    <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>
    <listener>
    <display-name>SpringListener</display-name>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <filter>
    <filter-name>Acegi Filter Chain Proxy</filter-name>
    <filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
    <init-param>
    <param-name>targetClass</param-name>
    <param-value>org.acegisecurity.util.FilterChainProxy</param-value>
    </init-param>
    </filter>

    <filter-mapping>
    <filter-name>Acegi Filter Chain Proxy</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>
    <servlet>
    <servlet-name>dwr-invoker</servlet-name>
    <servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class>
    <init-param>
    <param-name>debug</param-name>
    <param-value>true</param-value>
    </init-param>
    </servlet>

    <servlet-mapping>
    <servlet-name>dwr-invoker</servlet-name>
    <url-pattern>/dwr/*</url-pattern>
    </servlet-mapping>
    <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.jsp</welcome-file>
    </welcome-file-list>
    </web-app>

  5. In the DWR configuration file, set the creator to Spring
    <!DOCTYPE dwr PUBLIC
    "-//GetAhead Limited//DTD Direct Web Remoting 1.0//EN"
    "http://www.getahead.ltd.uk/dwr/dwr10.dtd">
    <dwr>
    <allow>
    <convert match="java.lang.Exception" converter="exception"/>
    <create creator="spring" javascript="secureDAO">
    <param name="beanName" value="secureDAO"/>
    </create>
    </allow>
    </dwr>
    Note:
    • The converter is used to convert the Java exception to Javascript exception.
  6. The main change will be made to the secure/authenticatedusers.jsp file. This will use DWR to make Ajax requests. Here is the code for the file
    <%@ page import="org.acegisecurity.context.SecurityContextHolder"%>
    <html>
    <head>

    <script type='text/javascript' src='/DWRSpring/dwr/interface/secureDAO.js'></script>
    <script type="text/javascript" src="../dwr/engine.js"> </script>
    <script type="text/javascript" src="../dwr/util.js"> </script>
    <script>
    dwr.engine.setErrorHandler(errorHandlerFn);
    function update() {
    var name = dwr.util.getValue("method");
    switch(name) {
    case "create":
    secureDAO.create(callBackFn)
    break;
    case "read":
    secureDAO.read(callBackFn);
    break;
    case "update":
    secureDAO.update(callBackFn);
    break;
    }

    }

    function callBackFn(str) {
    dwr.util.setValue("selectedAction","Server Returned : " + str);
    }

    function errorHandlerFn(message, exception) {
    dwr.util.setValue("selectedAction", "Error : " + message);
    }
    </script>

    </head>
    <body>
    <h1>Welcome: <%=SecurityContextHolder.getContext().getAuthentication().getName()%></h1>
    <p><a href="../">Home</a>
    <form name="testForm" action=""><select name="method" onchange="update()">
    <option value=""></option>
    <option value="create">create</option>
    <option value="read">read</option>
    <option value="update">update</option>
    </select></form>

    <div id="selectedAction"></div>
    <p><a href="../j_acegi_logout">Logout</a></p>
    </body>
    </html>
    Note:
    • The classes that are exposed through DWR will be available through the /WEB_APP_NAME/dwr/interface/JAVASCRIPT_NAME.js files.
      <script type='text/javascript' src='/DWRSpring/dwr/interface/secureDAO.js'></script>
    • The setErrorhandler call sets the global error handling function.
      dwr.engine.setErrorHandler(errorHandlerFn);
      Alternatively, the error handling function can be set for individual method calls (as described in DWR documentationa
      Remote.method(params, {
      callback:function(data) { ... },
      errorHandler:function(errorString, exception) { ... }
      });
    • util.js file contains utility functions for getting and setting values for the document elements.
  7. Make sure you have the following Jar files in your classpath:
    • acegi-security-1.0.3.jar
    • ant-junit.jar
    • cglib-nodep-2.1_3.jar
    • commons-codec-1.3.jar
    • commons-logging.jar
    • dwr.jar
    • ehcache-1.2.3.jar
    • jstl.jar
    • spring.jar
    • standard.jar

Wednesday, May 09, 2007

OpenJDK and JavaFX

Java is now completely open source. The JDK source code is now available through the OpenJDK project. According to the marketing manager of the OpenJDK project Rich Sands, Developers can,
... learn how the JDK is put together, fix that bug that's been 
driving you nuts, join the conversations in the mailing lists,
start or participate in projects to improve the implementation.
It's good to see that the governing body consists of not just Sun employees.

On a related note, Sun has announced the new JavaFX product family. Following Adobe's Flex, and Microsoft's Silverlight, JavaFX script is another product for building rich internet applicaitons. JavaFX Mobile is a complete mobile operating and application environment built around Java and Linux. JavaFX Mobile includes support for Java ME applications and other standard Java APIs.

Monday, May 07, 2007

Securing EJB 3.0 Beans

The Java EE 5 Security services are provided by the container and can be implemented using declarative or programmatic techniques. In addition to declarative and programmatic ways to implement security (in J2EE), Java EE 5 supports the use of metadata annotations for security. This post will describe how to secure EJB 3.0 beans. The post consists of a simple EJB, with a web client. In order to run the example, follow these steps.
Create Users in Glassfish
  1. Go to Configuration->Security->Realms->file in the Glassfish admin console.
  2. In the file realm, click on manage users.
  3. Add new users by clicking on add there.

The EJB Component
  1. Start with a Simple Java project in Eclipse.
  2. Remote Interface
    package ejb;

    import javax.ejb.Remote;

    @Remote
    public interface DABean {
    public String create();

    public String read();

    public String update();

    public String delete();
    }
    ejb/DABean.java
  3. The Bean:
    package ejb;

    import javax.annotation.security.DeclareRoles;
    import javax.annotation.security.RolesAllowed;
    import javax.ejb.Stateless;

    @Stateless (mappedName = "ejb/secureEJB")
    @DeclareRoles({"emp","guest"})

    public class SecureEJB implements DABean {

    @RolesAllowed({"emp","guest"})
    public String create() {
    return "create";
    }

    @RolesAllowed({"emp","guest"})
    public String read() {
    return "read";
    }

    @RolesAllowed("emp")
    public String update() {
    return "update";
    }

    @RolesAllowed("emp")
    public String delete() {
    return "delete";
    }

    }
    ejb/SecureEJB.java
    • The declaredRoles and RolesAllowed annotations take a string array as a parameter.
  4. Deployment descriptor:
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE sun-ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 9.0 EJB 3.0//EN" "http://www.sun.com/software/appserver/dtds/sun-ejb-jar_3_0-0.dtd">
    <sun-ejb-jar>
    <security-role-mapping>
    <role-name>guest</role-name>
    <group-name>guest</group-name>
    </security-role-mapping>

    <security-role-mapping>
    <role-name>emp</role-name>
    <group-name>employee</group-name>
    </security-role-mapping>

    <enterprise-beans>
    <unique-id>0</unique-id>
    <ejb>
    <ejb-name>SecureEJB</ejb-name>
    <jndi-name>ejb/secureEJB</jndi-name>
    <gen-classes />
    </ejb>
    </enterprise-beans>
    </sun-ejb-jar>
    META-INF/sun-ejb-jar.xml

The Web Client
For a little bit more detail explanation on the Web Application, see the previous post Securing Java EE 5 Web Applications
  1. The EJB Client Jar file: When you deploy the EJB application in Glassfish, it creates a corresponding EJB Client jar file for the EJB component, which can be used in the clients. The file will created in the following directory.
    GLASSFISH_HOME\domains\DOMAIN_NAME/generated\xml/j2ee-modules/APPLICATION_NAME
  2. Selection page
    <html>
    <body>
    <h1>Home Page</h1>
    Anyone can view this page.

    <form action="securityServlet"><select name="method">
    <option value="create">create</option>
    <option value="read">read</option>
    <option value="update">update</option>
    <option value="delete">delete</option>
    </select> <input type="submit" name="submit" /></form>
    </body>
    </html>
    index.jsp
  3. Servlet
    package servlets;

    import java.io.IOException;
    import java.io.PrintWriter;

    import javax.annotation.security.DeclareRoles;
    import javax.ejb.EJB;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;

    import ejb.DABean;

    @DeclareRoles("emp")
    public class SecurityServlet extends javax.servlet.http.HttpServlet implements javax.servlet.Servlet {

    @EJB(name = "timerBean", mappedName = "corbaname:iiop:localhost:3700#ejb/secureEJB")
    private DABean daBean;

    public SecurityServlet() {
    super();
    }

    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    PrintWriter out = response.getWriter();
    String method = request.getParameter("method");
    try {
    String result = "";
    if (method.equals("create")) {
    result = daBean.create();
    }
    if (method.equals("read")) {
    result = daBean.read();
    }

    if (method.equals("update")) {
    result = daBean.update();
    }

    if (method.equals("delete")) {
    result = daBean.delete();
    }

    out.println(request.getUserPrincipal() + " is an Authorized User");
    } catch (Exception e) {
    e.printStackTrace();
    out.println(request.getUserPrincipal() + " is not an Authorized to see this page.");
    }
    }
    }
    SecurityServlet.java
  4. Deployment descriptor
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    <display-name>Java5Security</display-name>

    <servlet>
    <description></description>
    <display-name>SecurityServlet</display-name>
    <servlet-name>SecurityServlet</servlet-name>
    <servlet-class>servlets.SecurityServlet</servlet-class>
    <security-role-ref>
    <role-name>emp</role-name>
    <role-link>emp</role-link>
    </security-role-ref>
    </servlet>
    <servlet-mapping>
    <servlet-name>SecurityServlet</servlet-name>
    <url-pattern>/securityServlet</url-pattern>
    </servlet-mapping>


    <login-config>
    <auth-method>FORM</auth-method>
    <realm-name>file</realm-name>
    <form-login-config>
    <form-login-page>/login.jsp</form-login-page>
    <form-error-page>/error.jsp</form-error-page>
    </form-login-config>
    </login-config>

    <security-constraint>
    <web-resource-collection>
    <web-resource-name>Protected Area</web-resource-name>
    <url-pattern>/*</url-pattern>
    <http-method>PUT</http-method>
    <http-method>DELETE</http-method>
    <http-method>GET</http-method>
    <http-method>POST</http-method>
    </web-resource-collection>
    <auth-constraint>
    <role-name>guest</role-name>
    <role-name>emp</role-name>
    </auth-constraint>
    </security-constraint>

    <security-constraint>
    <web-resource-collection>
    <web-resource-name>Protected Area</web-resource-name>
    <url-pattern>/secure/*</url-pattern>
    <http-method>PUT</http-method>
    <http-method>DELETE</http-method>
    <http-method>GET</http-method>
    <http-method>POST</http-method>
    </web-resource-collection>
    <auth-constraint>
    <role-name>emp</role-name>
    </auth-constraint>
    </security-constraint>
    <!-- Security roles referenced by this web application -->
    <security-role>
    <role-name>guest</role-name>
    </security-role>
    <security-role>
    <role-name>emp</role-name>
    </security-role>

    <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    </welcome-file-list>
    </web-app>
    web.xml
  5. Glassfish Deployment descriptor
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE sun-web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 8.1 Servlet 2.4//EN" "http://www.sun.com/software/appserver/dtds/sun-web-app_2_4-1.dtd">
    <sun-web-app>
    <context-root>/Java5Security</context-root>
    <security-role-mapping>
    <role-name>guest</role-name>
    <group-name>guest</group-name>
    </security-role-mapping>
    <security-role-mapping>
    <role-name>emp</role-name>
    <group-name>employee</group-name>

    </security-role-mapping>
    </sun-web-app>
    sun-web.xml
Environment: This example was run on Glassfish V2 Build 41 (Glassfish V2 Beta 2).

Tuesday, May 01, 2007

Java EE 5 on WebSphere

While the next version of WAS (v7.0) with Java EE 5, is yet to be released, you can now test some of the features such as EJB 3.0 with WebSphere software early programs. The new features are available as feature packs for the current WebSphere Application Server (v6.1).

Friday, April 13, 2007

Customize Acegi SecurityContext

This post describes how to modify the User details stored in the Acegi Security Context.

Acegi Security uses the SecurityContextHolder object to store details of the current security context of the application. The SecurityContext holds the details of the authenticated principal in an Authentication object. By default the SecurityContextHolder uses a ThreadLocal to store these details, so that they will be available all methods in the current thread of execution.In order to obtain the Principal, you would use the following line
Object obj = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Usually, the principal is an object of the type UserDetails. The UserDetails object is sort of an adapter between the security database and Acegi SecurityContextHolder. Acegi uses a UserDetailsSevice to build the UserDetails object. In order to modify the data stored Security Context of Acegi, you either have to
  • setContext() method of the SecurityContextHolder. The SecurityContext object can be set to hold an Authentication object. This will mean that you have to add some additional security code to your application code.
  • Implement the UserDetailsService and UserDetails interfaces. This way you can keep security code seperated from the application code.
This post will describe how to implement UserDetailsService and UserDetails Objects. Here's what to do to implement a UserDetailsService.
  1. Start with the implementing acegi security posts part 1 and part 2.
  2. Create the UserDetailsService:
    package authentication;

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

    import org.acegisecurity.GrantedAuthority;
    import org.acegisecurity.userdetails.UserDetails;
    import org.acegisecurity.userdetails.UserDetailsService;
    import org.acegisecurity.userdetails.UsernameNotFoundException;
    import org.springframework.dao.DataAccessException;

    public class MyUserDetailsService implements UserDetailsService {

    private Map users = init();

    private Map init() {
    Map tUsers = new HashMap();

    tUsers.put("scott", new User("scott", "tiger", "ROLE_USER"));
    tUsers.put("harry", new User("harry", "potter", "ROLE_ADMIN"));
    tUsers.put("frodo", new User("frodo", "baggins", "ROLE_USER"));

    return tUsers;
    }

    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException, DataAccessException {
    User user = (User) users.get(s);
    GrantedAuthority authority = new MyGrantedAuthority(user.getRole());
    UserDetails userDetails = new MyUserDetails(new GrantedAuthority[] { authority }, user.getUserId(), user.getPassword(), "Additional Data");
    return userDetails;
    }

    }
    MyUserDetailsService.java

    Notice that all the user data is defined in this class. The UserDetailsService has to return a UserDetails object using the User name.
  3. Create the UserDetails class:
    package authentication;

    import org.acegisecurity.GrantedAuthority;
    import org.acegisecurity.userdetails.UserDetails;

    public class MyUserDetails implements UserDetails {


    private GrantedAuthority[] authorities = null;
    private String password = null;
    private String username = null;
    private String additionalData = null;


    public MyUserDetails(GrantedAuthority[] authorities, String password, String username, String additionalData) {
    super();
    this.authorities = authorities;
    this.password = password;
    this.username = username;
    this.additionalData = additionalData;
    }

    public GrantedAuthority[] getAuthorities() {
    return authorities;
    }

    public String getPassword() {
    return password;
    }

    public String getUsername() {
    return username;
    }

    public boolean isAccountNonExpired() {
    return true;
    }

    public boolean isAccountNonLocked() {
    return true;
    }

    public boolean isCredentialsNonExpired() {
    return true;
    }

    public boolean isEnabled() {
    return true;
    }
    }
    MyUserDetails.java
  4. The User class:
    package authentication;

    public class User {

    private String userId;

    private String password;

    private String role;

    public String getPassword() {
    return password;
    }

    public void setPassword(String password) {
    this.password = password;
    }

    public String getRole() {
    return role;
    }

    public void setRole(String role) {
    this.role = role;
    }

    public String getUserId() {
    return userId;
    }

    public void setUserId(String userId) {
    this.userId = userId;
    }

    public User(String userId, String password, String role) {
    super();
    this.userId = userId;
    this.password = password;
    this.role = role;
    }

    }
    User.java
  5. Modify the applicationContext.xml file: Modify the userDetailsService bean in the application context to as shown below
    <bean id="userDetailsService" class="authentication.MyUserDetailsService">
    </bean>
  6. The GrantedAuthority class: The MyGrantedAuthority class is typically used to store application roles.
    package authentication;

    import org.acegisecurity.GrantedAuthority;

    public class MyGrantedAuthority implements GrantedAuthority {

    private String authority = null;

    public MyGrantedAuthority(String authority) {
    this.authority = authority;
    }
    public String getAuthority() {
    return authority;
    }

    }
    MyGrantedAuthority.java

Tuesday, April 10, 2007

Native Queries with Hibernate Annotations

Hibernate EntityManager implements the programming interfaces and lifecycle rules as defined by the EJB3 persistence specification. Together with Hibernate Annotations, this wrapper implements a complete (and standalone) EJB3 persistence solution on top of the mature Hibernate core. In this post I will describe how map native queries (plain SQL) using Hibernate Annotations. Hibernate Annotations supports the use of Native queries through the @NamedNativeQuery and the @SqlResultSetMapping annotations.
  • @NamedNativeQuery: Specifies a native SQL named query.
  • @SqlResultSetMapping: Used to specify the mapping of the result of a native SQL query.
You will not need any EJB container support. At a minimum, you will need Hibernate core and Hibernate Annotations. The entire list of required Jar files is provided at the end.
  1. The Hibernate Configuration File: Nothing new here. Except that there are no mappings defined. I used programmatic declaration of mapping for this example as shown in the following steps.
    <!DOCTYPE hibernate-configuration PUBLIC
    "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
    "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

    <hibernate-configuration>
    <session-factory>
    <property name="connection.driver_class">oracle.jdbc.driver.OracleDriver</property>
    <property name="connection.url">jdbc:oracle:thin:@localhost:1521/orcl</property>
    <property name="connection.username">scott</property>
    <property name="connection.password">tiger</property>
    <property name="dialect">org.hibernate.dialect.Oracle9Dialect</property>
    <property name="hibernate.current_session_context_class">thread</property>
    </session-factory>
    </hibernate-configuration>
    hibernate.cfg.xml
  2. The Entity class:
    package data;

    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.EntityResult;
    import javax.persistence.FieldResult;
    import javax.persistence.Id;
    import javax.persistence.NamedNativeQuery;
    import javax.persistence.SqlResultSetMapping;

    @Entity
    @SqlResultSetMapping(name = "implicit", entities = @EntityResult(entityClass = data.Employee.class))
    @NamedNativeQuery(name = "implicitSample", query = "select e.empno empNumber, e.ename empName, e.job empJob, e.sal empSalary, salg.grade empGrade from emp e, salgrade salg where e.sal between salg.losal and salg.HISAL", resultSetMapping = "implicit")
    //@SqlResultSetMapping(name = "explicit", entities = { @EntityResult(entityClass = data.Employee.class, fields = {
    // @FieldResult(name = "empNumber", column = "empno"),
    // @FieldResult(name = "empName", column = "ename"),
    // @FieldResult(name = "empJob", column = "job"),
    // @FieldResult(name = "empSalary", column = "sal"),
    // @FieldResult(name = "empGrade", column = "grade") }) })
    //@NamedNativeQuery(name = "implicitSample",
    // query = "select e.empno empno, e.ename ename, e.job job, e.sal sal, salg.grade grade from emp e, salgrade salg where e.sal between salg.losal and salg.HISAL", resultSetMapping = "explicit")
    public class Employee {

    private String empNumber;

    private String empName;

    private String empJob;

    private Double empSalary;

    private int empGrade;

    @Column
    @Id
    public int getEmpGrade() {
    return empGrade;
    }

    public void setEmpGrade(int empGrade) {
    this.empGrade = empGrade;
    }

    @Column
    public String getEmpJob() {
    return empJob;
    }

    public void setEmpJob(String empJob) {
    this.empJob = empJob;
    }

    @Column
    public String getEmpName() {
    return empName;
    }

    public void setEmpName(String empName) {
    this.empName = empName;
    }

    @Column
    public String getEmpNumber() {
    return empNumber;
    }

    public void setEmpNumber(String empNumber) {
    this.empNumber = empNumber;
    }

    @Column
    public Double getEmpSalary() {
    return empSalary;
    }

    public void setEmpSalary(Double empSalary) {
    this.empSalary = empSalary;
    }

    }
    Employee.java
    • The implitic mapping (the uncommented @NamedNativeQuery and @SqlResultSetMapping declarations are used for implicitly mapping the ResultSet to the entity class. Note that the SQL column names match the field names in the class. If the names do not match then you can set the name attribute of the @Column annotation to the column name in the query.
    • The commented @NamedNativeQuery and the @SqlResultSetMapping declarations explicitly map the fields to the columns. This will come in handy when using joins and composite keys etc.
    • Note the package definitions refer to javax.persistence and not the hibernate packages. If the packages are not declared properly, you will most likely end up with some exceptions like the following
      org.hibernate.hql.ast.QuerySyntaxException: Employee is not mapped.
      While there are other causes for this exception, the package declarations did cause a little trouble for me.
  3. The Client:
    import java.util.List;

    import org.hibernate.Session;
    import org.hibernate.SessionFactory;
    import org.hibernate.cfg.AnnotationConfiguration;
    import org.hibernate.cfg.Configuration;

    import data.Employee;

    public class Client {

    public static void main(String[] args) {
    Configuration config = new AnnotationConfiguration().addAnnotatedClass(Employee.class).configure();

    SessionFactory sessionFactory = config.buildSessionFactory();
    Session session = sessionFactory.getCurrentSession();

    List result = null;
    try {
    session.beginTransaction();

    result = session.getNamedQuery("implicitSample").list();
    System.out.println("Result size : " + result.size());
    session.getTransaction().commit();
    } catch (Exception e) {
    e.printStackTrace();
    }
    System.out.println(result.size());
    }

    }
    Client.java
  4. Jar Files: The following jar files need to be included in the classpath
    hibernate3.jar
    commons-collections-2.1.1.jar
    antlr-2.7.6.jar
    commons-logging-1.0.4.jar
    hibernate-annotations.jar
    ejb3-persistence.jar
    hibernate-commons-annotations.jar
    dom4j-1.6.1.jar
    ojdbc14.jar
    jta.jar
    log4j-1.2.11.jar
    xerces-2.6.2.jar
    xml-apis.jar
    cglib-2.1.3.jar
    asm.jar
    All the Jar files will be available in the hibernate download. The hibernate-annotations.jar file is available in the hibernate annotations download.
This example was tested on Java 5 Update 9 with Hibernate version 3.2.3 and Hibernate Annotations Version: 3.3.0.GA.

Popular Posts