Making Application Multi-Tenant with Spring-Data-JPA, Hibernate

>> Jul 16, 2014

Problem Statement – Make an application multitenant build with Spring core technologies (Spring MVC, Sprig Data-JPA) and related technology stack having separate schema, shared database.

Technical Challenge – Application needs capability to determine tenant for each request and keep track of tenant to the last and switch database connection to relative schema. As far as "passing in" tenant identified is still easy and can be tracked, but switching DB connection for identified tenant with Spring Data – JPA is killing and moreover it does not support for passing providers to EntityManager.

Before I go further –Disclaimer – Files/code/ and information shared below is taken from multiple sites while solving this issue. Few of them are changed, some are used as it is and few added. I appreciate the work shared by different people and my team, who kept on fighting for this….

Solution Hibernate 4 added support for handling Multitenancy . It provides MultiTenantConnectionProvider - Hibernate needs to be able to obtain Connections in a tenant specific manner, CurrentTenantIdentifierResolver - Hibernate to be able to resolve what the application considers the current tenant identifier.

But original problem remains as it is – Pass it to Entity Manager

Changes We Made –

Moved to <hibernate.version>4.3.5.Final</hibernate.version>

ApplicationContext –

Set the PersistenceUnitManager to use for obtaining the JPA persistence unit that this FactoryBean is supposed to build an EntityManagerFactory for. This is cruces of entire problem and controlling stuff with hibernate traditions. Understand DefaultPersistenceUnitManager, which supports standard JPA scanning for persistence.xml files, with configurable file locations, JDBC DataSource lookup and load-time weaving.


 
		

	
	
		
			
			
			
		
	
	
		
			false
			50
			true
			true
		
	

	



	
		
			classpath*:META-INF/persistence.xml
		
	

	
		
			
		
	
	



	
		
			com.demo.admin.domain
		
	



	
	




Also look at PersistenceScanner (original author unknown)

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.MappedSuperclass;

import org.hibernate.MappingException;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.orm.jpa.persistenceunit.MutablePersistenceUnitInfo;
import org.springframework.orm.jpa.persistenceunit.PersistenceUnitPostProcessor;
import org.springframework.util.ClassUtils;

public class PersistenceScanner implements PersistenceUnitPostProcessor {

    private static final String RESOURCE_PATTERN = "**/*.class";

    private String[] packagesToScan;

    private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();

    private TypeFilter[] entityTypeFilters = new TypeFilter[] {
            new AnnotationTypeFilter(Entity.class, false),
            new AnnotationTypeFilter(Embeddable.class, false),
            new AnnotationTypeFilter(MappedSuperclass.class, false) };

    @Override
    public void postProcessPersistenceUnitInfo(MutablePersistenceUnitInfo pui) {
        String[] entities = scanPackages();
        for (String entity : entities) {
            pui.addManagedClassName(entity);
        }
    }

    /**
     * 
     * Set whether to use Spring-based scanning for entity classes in the
     * classpath instead of listing annotated classes explicitly.
     * 
     * <p>
     * 
     * Default is none. Specify packages to search for autodetection of your
     * entity classes in the classpath. This is analogous to
     * 
     * Spring's component-scan feature
     * (org.springframework.context.annotation.ClassPathBeanDefinitionScanner}).
     */

    public void setPackagesToScan(String[] packagesToScan) {
        this.packagesToScan = packagesToScan;
    }

    /**
     * 
     * Perform Spring-based scanning for entity classes.
     * 
     * @see #setPackagesToScan
     */

    protected String[] scanPackages() {
        Set<String> entities = new HashSet<String>();
        if (this.packagesToScan != null) {
            try {
                for (String pkg : this.packagesToScan) {
                    String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
                            + ClassUtils.convertClassNameToResourcePath(pkg)
                            + RESOURCE_PATTERN;
                    Resource[] resources = this.resourcePatternResolver
                            .getResources(pattern);
                    MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(
                            this.resourcePatternResolver);
                    for (Resource resource : resources) {
                        if (resource.isReadable()) {
                            MetadataReader reader = readerFactory
                                    .getMetadataReader(resource);
                            String className = reader.getClassMetadata()
                                    .getClassName();
                            if (matchesFilter(reader, readerFactory)) {
                                entities.add(className);
                            }
                        }
                    }
                }
            } catch (IOException ex) {
                throw new MappingException(
                        "Failed to scan classpath for unlisted classes", ex);
            }
        }
        return entities.toArray(new String[entities.size()]);
    }

    /**
     * 
     * Check whether any of the configured entity type filters matches the
     * current class descriptor contained in the metadata
     * 
     * reader.
     */

    private boolean matchesFilter(MetadataReader reader,
            MetadataReaderFactory readerFactory) throws IOException {
        if (this.entityTypeFilters != null) {
            for (TypeFilter filter : this.entityTypeFilters) {
                if (filter.match(reader, readerFactory)) {
                    return true;
                }
            }
        }
        return false;
    }
}

CurrentTenantIdentifierResolver implementation - SchemaResolverAdmin
It is a contract for Hibernate to be able to resolve what the application considers the current tenant identifier.

import org.hibernate.context.spi.CurrentTenantIdentifierResolver;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.demo.multitenancy.service.SchemaResolver;

public class SchemaResolverAdmin extends SchemaResolver implements CurrentTenantIdentifierResolver {
    
    private static final Logger LOGGER = LoggerFactory.getLogger(SchemaResolverAdmin.class);

    @Override
    public String resolveCurrentTenantIdentifier() {
        
        String tenantResoloved = resolveTenant();
        
        LOGGER.debug("Tenant Schema resolved by SchemaResolverAdmin " + tenantResoloved);

        return tenantResoloved;
    }

    @Override
    public boolean validateExistingCurrentSessions() {
        return false;
    }
}


Schema Resolver – Base class for SchemaResolverAdmin. It just picks the relevant tenant's schema name for current thread.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.ApplicationContext;

import com.demo.multitenancy.util.MultiTenancyUtils;
import com.demo.multitenancy.util.SpringUtils;
import com.demo.multitenancy.util.TenantThreadLocal;

public class SchemaResolver {
    
    private static final Logger LOGGER = LoggerFactory.getLogger(SchemaResolver.class);

    protected String resolveTenant() {
    
        LOGGER.debug("Checking tenant from ThreadLocal.. ");
        String tenant = TenantThreadLocal.tenantThreadLocal.get();
        LOGGER.debug("ThreadLocal tenant - " + tenant);
        
        if (tenant != null) return tenant;

    
        final ApplicationContext applicationContext = SpringUtils.getSpringContext();
        String tenantSchema = null;
        TenantService tenantService = null;
        MultiTenancyUtils multiTenancyUtils = null;
        if (null == applicationContext) {
            LOGGER.error("Spring application context is null");
            throw new IllegalStateException(
                    "Spring application context is null");
        }
        tenantService = applicationContext.getBean(TenantServiceImpl.class);
        multiTenancyUtils = applicationContext.getBean(MultiTenancyUtils.class);
        try {
            tenantService.getTenantSchemaName();
        } catch (final BeanCreationException e) {
            tenantSchema = multiTenancyUtils.getDefaultSchema();
            LOGGER.info("Applicatino Startup, Request not initialised yet. Default schema is used {}", tenantSchema);
            return tenantSchema;
        }
        tenantSchema =  tenantService.getTenantSchemaName();
        LOGGER.debug("Tenant Schema is {}", tenantSchema);
        return tenantSchema;
    }
}


TenantThreadLocal and SpringUtils -
Interface to be implemented by any object that wishes to be notified of the ApplicationContext that it runs in.

public class TenantThreadLocal {
   public static final ThreadLocal<String> tenantThreadLocal = new ThreadLocal<String>();
}

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class SpringUtils implements ApplicationContextAware {

private static ApplicationContext applicationContext = null;

public static ApplicationContext getSpringContext() {
return applicationContext;
}

@Override
public void setApplicationContext(ApplicationContext appContext)
throws BeansException {
SpringUtils.applicationContext = appContext; }
}
MultiTenancyUtils -
Utility class to do extra like set default database for situation like deploying application on server, where tenant is not known.

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class MultiTenancyUtils {
    
    private String defaultSchema = null;

    public String getDefaultSchema() {
        return defaultSchema;
    }

    @Value("${DEFAULT_DATASOURCE}")
    public void setDefaultSchema(String defaultSchema) {
        this.defaultSchema = defaultSchema;
    }
}


TenantService/ TenantServiceImpl –

Your logic to determine tenant based on request and your design. I have used Pattern MY_PATTERN = Pattern .compile(".*/tenant/(\\T*)/?.*") which is if URL has /tenant/Tenant1.... it takes tenant as tenant1 and proceed. This logic will vary as per your needs and evantually should be able to determine from where request is coming.... Look for TenantRequestListener, a listener spying for you

import org.springframework.stereotype.Component;

public interface TenantService {
    
    String getTenantSchemaName();

    void setTenantSchemaName(String schemaName);
}

import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.Resource;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Service;
import org.springframework.web.context.WebApplicationContext;

@Service
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class TenantServiceImpl implements TenantService {

private static final Logger LOGGER = LoggerFactory
.getLogger(TenantServiceImpl.class);

private String tenantName = null;

@Resource(name = "multitenancyConfiguration")
private Properties properties;

@Override
public String getTenantSchemaName() {
return tenantName;
}

@Override
public void setTenantSchemaName(final String path) {
LOGGER.debug("Request URL path is {}", path);
if (StringUtils.isBlank(path)) {
LOGGER.error("Request URL is blank");
throw new IllegalArgumentException("Request URL is blank");
}
String tenantKey = null;
String tenant = null;

if (login match with criteria) {
tenantKey = "tenant1";
tenant = properties.getProperty(tenantKey);
} else if {
Keep on adding more patterns if required to distinguish between
} else {
LOGGER.warn("Tenant is not identified in Request URL path");
throw new IllegalStateException(
"Tenant is not identified in Request URL path");
}

if (StringUtils.isBlank(tenant)) {
LOGGER.error(
"No tenant schema exists in property file for key with value {}",
tenantKey);
throw new IllegalStateException(
"No tenant schema exists in property file for key with value"
+ tenantKey);
}
// I have not talked about it this this is to segregate log file
MDC.put("tenant", tenantKey);
LOGGER.debug("Tenant for demo request is {}", tenantKey);

tenantName = tenant;
}

}
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;


public class TenantRequestListener implements ServletRequestListener {
	private static final Logger LOGGER = LoggerFactory
			.getLogger(TenantRequestListener.class);

	@Override
	public void requestDestroyed(final ServletRequestEvent sre) {
	}

	@Override
	public void requestInitialized(final ServletRequestEvent servletRequestEvent) {

		LOGGER.debug("Initiating TenantRequestListener");
		final HttpServletRequest request = (HttpServletRequest) servletRequestEvent
				.getServletRequest();
		final String path = request.getServletPath();
		LOGGER.debug("Request URL Path is {}", path);
		final ApplicationContext applicationContext = SpringUtils
				.getSpringContext();
		TenantService tenantService = null;
		if (null == applicationContext) {
			LOGGER.error("Application Context is null");
			throw new IllegalStateException("Application Context is null");
		}
		tenantService = applicationContext.getBean(TenantService.class);
		if (null == tenantService) {
			LOGGER.error("Tenant Service is null");
			throw new IllegalStateException("Tenant Service is null");
		}
		tenantService.setTenantSchemaName(path);
	}
}
demo.properties
DEFAULT_DATASOURCE=adminDS
tenant1=tenant1DS

Persistence.xml –

Having details of package to be scanned and default tenant resolver




	
		org.hibernate.ejb.HibernatePersistence
		java:comp/env/jdbc/adminDS

		
			
			
			
			
			
			
			
		
	


Datasource file –

placed under catalina/ localhost folder



   
		
			
		 
		
		


Hope it should solve problem by in large. Regarding design, since it is sort of workaround further enhancement is required. I have tested this solution with Spring MVC and REST services.

Read more...

Integrating Web Application with OpenID using Spring Security

>> Apr 17, 2013

Overview
During the course of this article, I will try to put steps for integrating web application with Spring Security using OpenID as authentication medium and using Spring MVC. (This article expects that user has some knowledge of Spring MVC, Spring Security, Maven and Tomcat.) In detail Step1: Create a Web Application I have used NetBeans IDE 7.3, Tomcat7. To create a project, Choose File -New Project - Java Web - Web Application - ProjectName - Server - Spring MVC. Since I am using maven, add a pom file in the project. Make sure you have a basic structure of application before moving ahead. You can create a web application based on framework of your choice. Step2: Add Spring Security to it I have three pages 1. Login - open for all 2. landing - user with ROLE_USER access 3. Admin - User with ROLE_ADMIN access In Spring-Security.xml

  
  
  
  
       
   
        



   




  

Step3:Change web.xml

  contextConfigLocation
    
          /WEB-INF/spring-security.xml,
          /WEB-INF/applicationContext.xml
   

 servlet>
   AppSecurityDis
 org.springframework.web.servlet.DispatcherServlet
 1
   
 
   
 AppSecurityDis
 /apps/*
   
Step4: Add controller code to handle request 
1. AppController.java
@Controller
@RequestMapping("/main")
public class AppController {

    @RequestMapping(value = "/landing", method = RequestMethod.GET)
    public String getCommonPage() {
        return "landingpage";
    }

    @RequestMapping(value = "/admin", method = RequestMethod.GET)
    public String getAdminPage() {
        return "adminpage";
    }
}
2. SecurityController.java
@Controller
@RequestMapping("/secure")
public class SecuritytController {

    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public String getLoginPage(@RequestParam(value = "error", required = false) boolean error,
            ModelMap model) {
        if (error == true) {
            model.put("error", "Please enter valid username or password!");
        } else {
            model.put("error", "");
        }

        return "loginpage";
    }
Step5: Add code to AppSecurityDis-servlet

 
 

Step6: Add Controller package to ApplicationContect.xml
Step7 : Add code to loginpage.jsp


Welcome to App Security


Login to AppSecurity

${error}
For Google users:

OR If you have OpenID Login with OpenID:
:
and for adminpage.jsp and landingpage.jsp, you can add code as per your requirements 
Step8: Compile and Deploy it. 
Once deployed access it at "http://localhost:8080/AppSecurity/apps/secure/login". You will get two options to login. If valid credentials and match with username value, you should be able to view landing page. Step9: Moving it to database Create follwing tables in mySQL database.
CREATE TABLE `users` (
  `USER_ID` int(10) unsigned NOT NULL,
  `USERNAME` varchar(145) NOT NULL,  
  `PASSWORD` varchar(45) DEFAULT NULL,
  `ENABLED` tinyint(1) NOT NULL
  PRIMARY KEY (`USER_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8$$

CREATE TABLE `user_roles` (
  `USER_ROLE_ID` int(10) unsigned NOT NULL,
  `USER_ID` int(10) NOT NULL,
  `AUTHORITY` varchar(45) NOT NULL,
  PRIMARY KEY (`USER_ROLE_ID`),
  KEY `FK_user_roles` (`USER_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8$$

Add these records in Users and user_role tables

101, https://www.google.com/accounts/o8/id?id=AItOawm3ktMIUIGaqDzhyeZsK_amZ4paBhJN8kA, , 1

1, 101, ROLE_USER
2, 101, ROLE_ADMIN
BTW, this username "https://www.google.com/accounts/o8/id?id=AItxxioJSDLFJLjxcksdfjOpAASDFosSSoJ0E" is mine. you need to change it with your id and you can it track it as written above. Add spring-database.xml in WEB-INF folder


 
  
  
  
  
 

Add this in web.xml as well.
contextConfigLocation
 
  /WEB-INF/spring-database.xml,
         /WEB-INF/spring-security.xml,
         /WEB-INF/applicationContext.xml
 
Step10/Step2a: Make changes in spring-security file.
Although this is still a work around and should be part of UserDetailsService class. Which I will implement in next blog. Make sure you comment code written in step 2 and replace it with new one.

   
 
       
  
 
Recompile and run it again. You should be able to login using new changes in database.

Read more...

Understanding JAX-WS Web Services

>> Jul 4, 2011

Overview:

Most of the articles, I have read for “Creating Web Services” are ofcourse good resources to learn and I have learned a lot from them. I always wanted to have a article, giving details to its root level and this article is a step towards that. Hope people will like it.

JAX-WS:

JAX-WS (Java API for XML Web services) uses WS-I Basic Profile 1.1 standard along with Java Architecture for XML Binding (JAXB) and SOAP with Attachments API for Java (SAAJ).

Web Services use SOAP to communicate with client and vice-varsa, which means they understand soap request message and soap response message comprising body and payloads. Now both side sould have capabilities to convert soap message to Java objects (actual implementation) and then back to soap messages.

JAXB has hidden capabilities to map XML schema to Java object and back to XML schema. Best part of this is, developer is not even aware of these transformations and parsing.

Understanding it in detail:

Let us create a simple Calculator application and take it further.
JAX-WS has powerful capabilities to convert POJO into Web-Services with annotaions.

Simple POJO, having sum capabilities
public class CalculatorWS {
    public int add( int i, int j) {
      return i + j;
   }
}
Let's annotate it
@WebService()
public class CalculatorWS {
   public int add( int i, int j) {
     return i + j;
   }
}
We have annotaed CalculatorWS with the javax.jws.WebService annotation and it adds capabilties to publish this class as SEI (Service Endpoint Interface). We have annotaed it with minimum required stuff (ofcourse with all default parameter/ settings) . After deploying access wsdl at http://localhost:8080/CalculatorApp/CalculatorWSService?wsdl . Names in wsdl are created with default vales like namespace, service name etc.

This aricle covers JAX-WS and working example is at Creating Web Services

  
     
         
     
  

    


    

Let's dig it further and give proper name
@WebService(serviceName = "CalculatorWS", portName = "CalculatorPort", targetNamespace = "http://sandeepgupta.info/calculator")
@SOAPBinding(style=SOAPBinding.Style.DOCUMENT,use=SOAPBinding.Use.LITERAL)//Optional
public class CalculatorWS {
   @WebMethod(operationName = "add") //Optional
   public int add(@WebParam(name = "i") int i, @WebParam(name = "j") int j) {
      return i + j;
   }
}
Note: By default all methods are published. You can have better control by using @WebMethod with setting exclude to true.
and wsdl generated is

    
      
      
      
   
   
      
        
           
              
                 
              
              
                  
              
        
    
    //Message Part (WSDL)
    
         
    
    
        
    
Now instead of leaving everything on default values, we have given proper names. It pays while handing large applications.

There is one more important factor, which plays vital role in Web Services.
@SOAPBinding(style=SOAPBinding.Style.DOCUMENT, use=SOAPBinding.Use.LITERAL, parameterStyle = ParameterStyle.WRAPPED) // Default values of JAX-WS

What it does ?

Document/ RPC
SOAP supports to communication styles RPC and Document (Default). If operation is RPC-oriented (messages containing parameters and return values) or document-oriented (message containing document(s)). This information may be used to select an appropriate programming model. The value of this attribute also affects the way in which the Body of the SOAP message is constructed.

Literal/ Encoded *
If use is encoded, then each message part references an abstract type using the type attribute. These abstract types are used to produce a concrete message by applying an encoding specified by the encodingStyle attribute. The part names, types and value of the namespace attribute are all inputs to the encoding, although the namespace attribute only applies to content not explicitly defined by the abstract types. If the referenced encoding style allows variations in it’s format (such as the SOAP encoding does), then all variations MUST be supported.

If use is literal, then each part references a concrete schema definition using either the element or type attribute. In the first case, the element referenced by the part will appear directly under the Body element (for document style bindings) or under an accessor element named after the message part (in rpc style). In the second, the type referenced by the part becomes the schema type of the enclosing element (Body for document style or part accessor element for rpc style).

ParameterStyle (Bare or Wrapped (Default))

The parameterStyle is used along with document style attribute and defines how the web service request/reponse messages needs to be interpreted by a Web service provider/consumer.

When the SOAPBinding.ParameterStyle.WRAPPED is used



     
         
         
    


     
         
     

It represents that the root element is the name of the operation and child element following it are the payloads. The SOAPBinding.ParameterStyle.WRAPPED is useful where you have requirement of overloading.

When the SOAPBinding.ParameterStyle.BARE is used

it tells that only payload is passed to service and not operation (root element) is present.


It allows one parameter to bind.

com.sun.xml.ws.model.RuntimeModelerException: runtime modeler error: SEI org.me.calculator.CalculatorWS has method add annotated as BARE but it has more than one parameter bound to body. This is invalid. Please annotate the method with annotation: @SOAPBinding(parameterStyle=SOAPBinding.ParameterStyle.WRAPPED)

* Taken from w3.org

Read more...

Sending Binary Attachment With Web-Services Using MTOM

>> May 20, 2011

W3C Recommendation on “SOAP Message Transmission Optimization Mechanism”, hereafter MTOM, handles binary data transmission very diligently and it allows you to send attachment like images, jar, PDF files and more along with Web Service request.

Any Web-Service can be made MTOM-aware by using either

@MTOM(enabled=true, threshold=10)
or
@BindingType(value = SOAPBinding.SOAP11HTTP_MTOM_BINDING)
or
@BindingType(value = SOAPBinding.SOAP12HTTP_MTOM_BINDING)

Note: If @MTOM is present, it takes priority
Message size less than threshold limit will be sent as inlined with XML and base64
Server Side
@MTOM
@WebService(targetNamespace = "http://sandeepgupta.info")
public class MTOMImpl { .................
public void fileUpload(String fileName, ...) {
    //File handling code
}
Client Side
private void fileUpload() {
        MTOMImplService service2 = new MTOMImplService();
        info.sandeepgupta.MTOMImpl port2 = service2.getMTOMImplPort(new MTOMFeature());
        DataHandler dataH = new DataHandler(new FileDataSource("/home/sandeep/atomikos-util-3.6.4.jar"));
        port2.fileUpload("/home/sandeep/atomikos-util-3.6.4_back.jar", dataH);
    }
WSDL new entry

That's it. You should be able to upload files to destination.

Read more...

Java Web Application Performance Tuning

>> May 17, 2011

Based on my experience and steps followed to tune my web application, I am writing .......

1. Use of CDN (Content Delivery Network)
Let CDN server handle static contents of your application. Akamai and Amazon S3 are popular solutions. Store CSS, JS, images and other static contents to these servers. If cost is big factor, you can create your own server to handle these request and let application server concentrate on processing of dynamic contents.

2. Use Pack:Tag
pack:tag is a JSP-Taglib that minifies, compresses and combines resources (like JavaScript and CSS) and caches them in memory or in a generated file. It works transparent to the user/developer and the compressing-algorithms are pluggable.

Example:


3. Divide and Load

Instead of loading everything in one go, prioritise the data required upfront and data for further links and load it asynchronously (may be using threads). By the time page will be loaded fully and user decides to drill it further, data is ready and access it from session.

public void run(){
Future future = threadExecutor.submit(new Callable () {
public ArrayList call() { return MyDAO.getList(userName); }
});

Future future1 = threadExecutor.submit(new Callable () {
public ArrayList call() { return

MyDAO.getMemberAddressBook(userName); }
});
.
.
session.putValue("cacheList", future.get());
session.putValue("cacheAddressList", future1.get());
4. Minimize HTTP Request
Making request over HTTP to load data on every page is costly affair and try to use browser cache to make fewer calls to server. On next request (for image, js etc) , data will be loaded from browser cache. Make sure you set their expiry as well.

5. Use of pre compiled JSP files
When application is in production, JSP pages don't change, you can improve performance by pre-compiling your JSPs.While deploying web application, choose pre-compiled JSPs option.

6. Use of Object Cache Framework
Object caching will allow your application to share objects across multiple requests. Most frequently used objects or time-consuming (to load) objects can be loaded in cache. OSCache, EHCache, MemCache are good options.

7. Minor Tuning

  • Load CSS at the top
  • Move JS to bottom
  • Avoid redirect and DNS lookup, if required use RequestDispatcher.forward() instead of HttpServletResponse.sendRedirect()
  • Add an Expires Header
  • Try to use latest JRE release
  • Minimize logging and log critical errors
  • Remove unnecessary/ unused code
  • Do not put everything in session
  • If session is not required, add <%@ page session="false"%> to deactivate it
  • Start the JVM with required heap memory size (the -Xms switch)
  • Tune the Connector (web server) thread pool settings to more closely match the web request load you have
  • Tune database connection pool settings ie maxActive, maxIdle, and maxWait attributes of the Resource element

Read more...

Integrate Jasper Reports with Struts2 using Maven

>> Apr 25, 2011

This article will help you to integrate your web application (Struts 2 + Maven) with Jasper Reports.
(Assumption: You have some knowledge of Struts2 and Jasper)
Step 1: Create a Struts2 based application

1.1 Create a simple POJO
public class StockList {

    private Long id;
    private String name;
    private String quantity;

    public StockList() {
    }

    public StockList(Long id, String name, String quantity) {
        this.id = id;
        this.name = name;
        this.quantity = quantity;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getQuantity() {
        return quantity;
    }

    public void setQuantity(String quantity) {
        this.quantity = quantity;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
1.2 Create a POM file

Before you jump further, this is important to understand the dependencies required to create this example apart from regular Struts 2 and common* dependencies. 

You need - jasperreports-4.0.1.jar, jdt-compiler-3.1.1, struts2-jasperreports-plugin.jar and  may be itext-2.1.7.jar


            net.sf.jasperreports
            jasperreports
            ${jasper.varsion}
        
        
            jdt-compiler
            jdt-compiler
            3.1.1
        
        
        
            org.apache.struts
            struts2-jasperreports-plugin
            2.2.1
        
Also
1.1.1
        1.2.2
        1.8.3
        3.2.1
        2.0.1
        2.0
        2.6

        2.2.1.1
        2.1.3
        2.3.9
        2.1.7
        4.0.1

1.3 Create a Action Class

public class JasperAction extends ActionSupport {

    private List stList;

    public String execute() throws Exception {

        //Dummy Data to show on Jasper Report
        StockList st1 = new StockList(new Long(1), "Pen", "10");
        StockList st2 = new StockList(new Long(2), "Note Book", "100");
        StockList st3 = new StockList(new Long(3), "Laptop", "2");
        StockList st4 = new StockList(new Long(4), "Boxes", "100");

       
        stList = new ArrayList();
        stList.add(st1);
        stList.add(st2);
        stList.add(st3);
        stList.add(st4);

         try {
            JasperCompileManager.compileReportToFile(
                    "/home/sandeep/tomcat6018/webapps/StrutsJasper/jasper/sample.jrxml",
                    "/home/sandeep/tomcat6018/webapps/StrutsJasper/jasper/sample.jasper");
        } catch (Exception e) {
            e.printStackTrace();
            return ERROR;
        }

        return SUCCESS;
    }

     public List getStList() {
        return stList;
    }
}


Till this point, there is hardly any change as compared to normal Struts2 application. This is very important to understand the ResultType in Struts2.0.

Now, what you need to do is, change struts.xml accordingly 

Step 2: Create a struts.xml


        
            
        

        
            
                /jasper/sample.jasper
                stList
                HTML
            
        
    
Here, result-type is changed to jasper and package extends jasperreports-default.

 Step 3: Create a sample.jrxml

  
  
  
  
  
    
      
        
        
          
        
        
      
      
        
        
          
        
        
      
    
  
  
    
      
        
        
        
      
      
        
        
        
      
    
  
 




Step 4: Test is http://localhost:8080/StrutsJasper/StrutsJasperAction

You should be able to see report showing the stock data. 

Note: This example is created with help of http://struts.apache.org tutorials.

Read more...