Showing posts with label Spring. Show all posts
Showing posts with label Spring. Show all posts

7/20/2015

Ehcache Tutorial: Dynamically configuration and Cache Abstraction(Since Spring 3.1)

There are basically two ways to config Ehcache in java project.

1. Dynamically configuration
2. Through XML

Before getting started, we need to add the dependency of Ehcache. For example, if use maven, add below dependency into pom.xml:

<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>2.10.0</version>
</dependency>

1. Dynamically configuration

More flexible than XML configuration. But instead, you need to write and manage more code.

Cache Initialization:

public abstract class CommonDao{

    @Value("${cache.enable:true}")
    protected boolean cacheEnable;

    @Value("${cache.timetolive:21600}")
    private int cacheTimeToLive;
    
    @Value("${cache.timetoidle:21600}")
    private int cacheTimeToIdle;
    
    protected CacheManager cacheManager;
    
    protected static final String userCacheName = "userCache";
    protected static final String orgCacheName = "orgCache";

    @PostConstruct
    void initCache(){
        Configuration config = new Configuration();
        CacheConfiguration defaultCacheConfiguration = new CacheConfiguration();
        defaultCacheConfiguration.setEternal(false);
        defaultCacheConfiguration.setTimeToIdleSeconds(cacheTimeToIdle);
        defaultCacheConfiguration.setTimeToLiveSeconds(cacheTimeToLive);
        config.addDefaultCache(defaultCacheConfiguration);
        cacheManager = CacheManager.create(config);
        cacheManager.addCacheIfAbsent(userCacheName);
        cacheManager.addCacheIfAbsent(orgCacheName);
    }
    
    protected void putCacheElement(String cacheName, String key, Object value){
        
        Cache cache = null;
        if(BooleanUtils.isTrue(cacheEnable)){
            cache = cacheManager.getCache(cacheName);
            if(cache != null){
                cache.put(new Element(key, value));
            }
        }
    }
    
    protected void putCacheElement(Cache cache, String key, Object value){
        
        if(BooleanUtils.isTrue(cacheEnable)){
            if(cache != null){
                cache.put(new Element(key, value));
            }
        }
    }
    
    protected Object getCacheElement(String cacheName, String key){
        
        Cache cache = null;
        if(BooleanUtils.isTrue(cacheEnable)){
            cache = cacheManager.getCache(cacheName);
            if(cache != null){
                Element element = cache.get(key);
                return element == null ? null : element.getObjectValue();
            }
        }
        return null;
    }
    
    protected Object getCacheElement(Cache cache, String key){
        
        if(BooleanUtils.isTrue(cacheEnable)){
            if(cache != null){
                Element element = cache.get(key);
                return element == null ? null : element.getObjectValue();
            }
        }
        return null;
    }
    
    protected void removeCacheElement(String cacheName, String key){
        
        Cache cache = null;
        if(BooleanUtils.isTrue(cacheEnable)){
            cache = cacheManager.getCache(cacheName);
            if(cache != null){
                cache.remove(key);
            }
        }
    }
    
    protected void removeCacheElement(Cache cache, String key){
        
        if(BooleanUtils.isTrue(cacheEnable)){
            if(cache != null){
                cache.remove(key);
            }
        }
    }
    
    protected CacheManager getCacheManager(){
        return cacheManager;
    }
}

Dynamically update:

If in the system, you have the requirement to run time update the caching configuration:

Cache cache = dao.getCacheManager().getCache("userCache"); 
CacheConfiguration config = cache.getCacheConfiguration(); 
config.setTimeToIdleSeconds(60); 
config.setTimeToLiveSeconds(120); 
config.setmaxEntriesLocalHeap(10000); 
config.setmaxEntriesLocalDisk(1000000);

Reference:
http://ehcache.org/generated/2.10.0/html/ehc-all/index.html#page/Ehcache_Documentation_Set/co-cfgbasics_dynamically_changing_cache_config.html#wwconnect_header

2. XML Configuration

a.  Config the ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true" monitoring="autodetect">
    <defaultCache
        eternal="false"
        maxElementsInMemory="1000"
        overflowToDisk="false"
        diskPersistent="false"
        timeToIdleSeconds="3600"
        timeToLiveSeconds="3600"
        memoryStoreEvictionPolicy="LRU" />
    <cache
        name="userCache"
        eternal="false"
        maxElementsInMemory="200"
        overflowToDisk="false"
        diskPersistent="false"
        timeToIdleSeconds="3600"
        timeToLiveSeconds="3600"
        memoryStoreEvictionPolicy="LRU" />
    <cache
        name="orgCache"
        eternal="false"
        maxElementsInMemory="200"
        overflowToDisk="false"
        diskPersistent="false"
        timeToIdleSeconds="3600"
        timeToLiveSeconds="3600"
        memoryStoreEvictionPolicy="LRU" />
</ehcache>

In above example, we configured two cache named "userCache" and "orgCache".
The file can be put in classpath or any location.


Tip:

If the system is developed using Spring, there is another way to configure the cache, which is more preferable. Example below.


b. Create spring context file for Ehcache and added it into spring context.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cache="http://www.springframework.org/schema/cache"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
       http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.0.xsd">

    <cache:annotation-driven cache-manager="cacheManager" />

    <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cache-manager-ref="ehcache" />

    <bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:config-location="classpath:ehcache.xml" />
        
    <bean id="realCacheManager" factory-bean="cacheManager" factory-method="getCacheManager" />
    
    <!-- If cache is not configured in ehcache.xml, can also be created here -->
    <bean id="userCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
        <property name="cacheManager" ref="realCacheManager" />
        <property name="eternal" value="false" />
        <property name="maxElementsInMemory" value="${user_cache_max_elements_in_memory}" />
        <property name="overflowToDisk" value="false" />
        <property name="diskPersistent" value="false" />
        <property name="timeToIdle" value="${user_cache_time_to_idle}" />
        <property name="timeToLive" value="${user_cache_time_to_live}" />
        <property name="memoryStoreEvictionPolicy" value="LRU" />
    </bean>

    <bean id="orgCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
        <property name="cacheManager" ref="realCacheManager" />
        <property name="eternal" value="false" />
        <property name="maxElementsInMemory" value="${org_cache_max_elements_in_memory}" />
        <property name="overflowToDisk" value="false" />
        <property name="diskPersistent" value="false" />
        <property name="timeToIdle" value="${org_cache_time_to_idle}" />
        <property name="timeToLive" value="${org_cache_time_to_live}" />
        <property name="memoryStoreEvictionPolicy" value="LRU" />
    </bean>
</beans>

Reference:
http://ehcache.org/generated/2.10.0/html/ehc-all/#page/Ehcache_Documentation_Set%2Fco-cfgbasics_xml_configuration.html%23

c. Cache Abstraction (Since Spring 3.1)

Since 3.1, Spring added support for transparently adding caching into an existing Spring application. 
Similar to other feature in Spring, it supports annotation-based and xml-based configuration. Here we just include example for annotation-based configuration.

@Repository
public interface UserDao extends CrudRepository<User, String> {

    @Cacheable(value="userCache", key="#p0", unless="#result == null")
    Channel findByUserName(String userName);
}
@Repository
public interface OrganizationDao extends CrudRepository<Organization, String> {

    @Cacheable(value="orgCache", key="#p0", unless="#result == null")
    Organization findByName(String name);

    @CacheEvict(value="orgCache", key="#p0")
    Long deleteByName(String name);
}
Tips:

1. The key in above example is using p0 to represent the first param of the method. This is in case:

"Name of any of the method argument. If for some reason the names are not available (ex: no debug information), the argument names are also available under the a<#arg> where #arg stands for the argument index (starting from 0)."

Details on how to customize the configuration will not be included here in this tutorial, because you could almost find everything in the official document. Just do a little bit research based on your own requirement.

Spring expression language can also be used in the configuration.

Reference:


Good luck!


9/26/2014

Spring RestTemplate does not capture response body when error code 40x is returned

When consuming REST web services using SpringTemplate default configuration, when the response returns error HTTP status code, SpringTemplate will throw HttpClientErrorException . But you can still get the response body by calling:

clientEx.getResponseBodyAsByteArray();

OR

clientEx.getResponseBodyAsString();

But there is one special case, when error code is 40x, it fails to capture the response body.

Reason is described in ticket SPR-9999

While solution is quite simple as some one mentioned in the ticket. Use the "org.springframework.http.client.HttpComponentsClientHttpRequestFactory" instead of the default one.

So the configuration should be:

<bean id="clientHttpRequestFactory" class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory"/>
    
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
     <property name="requestFactory" ref="clientHttpRequestFactory"/>
</bean>




9/10/2014

Spring Test: Inject Mock Object into proxy-based Spring bean.(Like bean with @Transactional or @Aync ...)

Spring Test, which I have to say, is the perfect lib for testing projects build on Spring.

There is one very useful class called "ReflectionTestUtils". With that, we dont need to put any setter or getter  for autowired fields inside our services and we can partially replace one autowired field/service to our defined mock object. Very convenient and flexible.

Example:

FooDao mockFooDao = Mockito.mock(FooDao.class);
Mockito.when(mockFooDao.foo()).thenReturn("Var");
ReflectionTestUtils.setField(fooService, "fooDao", mockFooDao);

There is one thing we need to pay attention, is, component bean may be wrapped by proxy class when we use annotations from spring like @Transactional or @Aync. In this case, we need to unwrap the bean before we inject the mock object, otherwise, the "ReflectionTestUtils" cannot inject the mock object, because the "fooService" is proxy object, does not have the "fooDao" field inside it!

So we need to unwrap the bean like this:

protected Object unwrapService(Object service) throws Exception {
   if(AopUtils.isAopProxy(service) && service instanceof Advised) {
 Object target = ((Advised) service).getTargetSource().getTarget();
 return target;
   }
   return null;
}
And:
ReflectionTestUtils.setField(unwrapService(fooService), "fooDao", mockFooDao);

In this way, it works perfect.

Enjoy.



6/10/2014

Unit Test for Web Services Client who use Spring Template

Recently was doing one client project which will call the web services provided by our core platform. That project is using SpringTemplate to call the Web services. How to write Junit tests for the client code brought up my interest.

After doing some research, the most commonly used method is to use Mock, of course. Some uses Mockito or EasyMock, which are all fine to me. But what I thought was we are using Spring framework anyway, there should be some Mock classes specially for SpringTemplate, and it should already be there. Come on, it's SPRING! In this case, we do not need to write Mock classed for SpringTemplate ourselves. There it is, as I thought, there are plenty of Mock classes sitting there for easy usage.

I made several test cases, although setup the dependencies costs me some time, but that is because of our project's limitation, which is not...you know. All the test cases are easy to implement, and covers well. I'm pretty satisfied. So just gives a record here, because I think it's interesting and the problems that I have encounted may have been faced by other devs too.

Dependencies:

The lib that I use is

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test-mvc</artifactId>
    <version>1.0.0.M2</version>
    <scope>test</scope>
</dependency>

Actually this library has already been integrated into spring-test in Spring version 3.2.X, but our projects are all using Spring 3.1.3.RELEASE. So I have to add it saparately. And be cautious on the dependency conflict(This will really cause trouble...when there are multiple versions of lib exist or same name of class exist but with different content and if the wrong lib/class is loaded, the class not found exception/method not found exception will be thrown, you dont want to see this...).

There are several ways to check the dependencies.

1. Use mvn dependency:tree/mvn dependency:analyze. It will print the structure of the dependencies.
2. You can check on the website: http://mvnrepository.com/artifact/junit/junit/4.10. It will normally tells you which dependencies the jar depends on.
3. You could do Ctrl+shift+T in Eclipse to check how many classes with same name exist in your work space.

The best practice is to make sure there is no dependency conflict. You will never know when it will gives you that exception.

Junit 4.10 and Mockito-all:1.9.5 CANNOT be used, because they all include hamcrest-core:1.1 inside its package which will not work with spring-test-mvc:1.0.0.M2 and there is no way to exclude them. So use Junit 4.11 and Mockito-core:1.9.5 instead.

But, Junit 4.11 and Mockito-core:1.9.5 both have dependency org.hamcrest:hamcrest-core, but they require different version of them. So they are not compatible, and we need to exclude hamcrest-core in both of them and use org.hamcrest:hamcrest-all:1.3

So the dependencies that I use is like:

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11/version>
    <exclusions>
        <exclusion>
            <artifactId>hamcrest-core</artifactId>
            <groupId>org.hamcrest</groupId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>1.9.5</version>
    <exclusions>
        <exclusion>
            <artifactId>hamcrest-core</artifactId>
            <groupId>org.hamcrest</groupId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest-all</artifactId>
    <version>1.3</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test-mvc</artifactId>
    <version>1.0.0.M2</version>
    <scope>test</scope>
</dependency>

So if you are using different versions of the dependencies, check before you put it!

Ok, we can go to the main part.

Code to be tested: 

The code that I was testing is:

    @SuppressWarnings("rawtypes")
    @Override
    public long count(UserRef ref) throws IntegrationException {
        try {
            UserMessageCountCriteria criteria = new UserMessageCountCriteria();
            criteria.setIncludeAgentMessage(true);
            criteria.setOrgName(ref.getLoginOrganizationName());
            criteria.setReadStatus(MessageReadStatusType.unRead);
            criteria.setLabels(new ArrayList<String>());
            criteria.getLabels().add(serviceInfo.getMessageCountLabel());
            criteria.setUserName(ref.getUser().getUserName());
            
            ResponseEntity<Map> responseEntity = null;
            HttpEntity<Map<String, Object>> entity = new HttpEntity<Map<String, Object>>(this.getUserAuthRequestMap(criteria, ref), this.getHttpHeaders());
            responseEntity = restTemplate.exchange(serviceInfo.getServiceMessageCountUrl(), HttpMethod.POST, entity, Map.class);
            
            return (Long) getResponseBody(responseEntity, Long.class);
        } catch (Exception ex) {
            ServiceExceptionHandler.handle(ex, this.getClass().getSimpleName());
        }
        
        return 0L;
    }

protected Object getResponseBody(ResponseEntity<Map> responseEntity, Class clazz) throws IntegrationException {

        if (LOGGER.isDebugEnabled()) {
            try {
                LOGGER.debug(JsonCodec.marshal(responseEntity));
            } catch (Exception ex) {
                throw new IllegalArgumentException(ex);
            }
        }

        if (responseEntity == null) {
            throw new RuntimeException("Response is empty");
        }

        HttpHeaders headers = responseEntity.getHeaders();
        if (headers == null) {
            throw new RuntimeException("Response Header is empty");
        }

        Map<String, Object> responseBody = responseEntity.getBody();
        Map<String, String> status = (Map<String, String>) responseBody.get("status");
        String statusCode = status.get("code");
        String statusMsg = status.get("message");
        if (!StringUtils.equals(statusCode, serviceInfo.getAPISuccessCode())) {
            throw new IntegrationException(statusCode, statusMsg);
        }

        Object object = responseBody.get("content");
        if (clazz == null) {
            return object;
        }
        
        if(object == null)
            return null;

        try {
            String json = JsonCodec.marshal(object);
            if (StringUtils.isEmpty(json)) {
                return null;
            }
            return JsonCodec.unmarshal(clazz, json);
        } catch (Exception ex) {
            throw new IllegalArgumentException(ex);
        }
    }

It's pretty basic.

Test Case:

First I create one Base class to setup my Mock server, this class can be extended by all the other service test class.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:spring/test-portalintegration-servlet.xml" })
public class ServiceClientImplTestBase {

    private static final Logger logger = Logger.getLogger(ServiceClientImplTestBase.class);
    
    protected MockRestServiceServer mockServer;
    
    @Autowired
    private ApplicationContext appContext;
    
    @Autowired
    protected MessageServiceClient messageServiceClient;
    
    @Autowired
    protected ServiceInfo serviceInfo;
    
    @Before
    public void setUp() {
        mockServer = MockRestServiceServer.createServer((RestTemplate) appContext.getBean("restTemplate")); // (1)
        
    }
    
    protected String loadFile(String path){
        
        byte[] content;
        try {
            content = IOUtils.toByteArray(this.getClass().getClassLoader().getResourceAsStream(path));
        } catch (IOException e) {
            logger.error(String.format("failed to load file from classpath: %s", path), e);
            return null;
        }
        
        return new String(content);
    }
}

Then I create one class to test one of the service client.

public class MessageServiceClientImplTest extends ServiceClientImplTestBase{
    @Test
    public void countTest() throws Exception {
        mockServer.expect(RequestMatchers.requestTo(serviceInfo.getServiceMessageCountUrl()))
        .andExpect(RequestMatchers.method(HttpMethod.POST))
        .andRespond(ResponseCreators.withSuccess(loadFile("testdata/sample-response/message-count-success-response.json"), MediaType.APPLICATION_JSON)); // (2)

        UserRef userRef = this.getUserRef("dummy"); //this method just helps me create one dto. Nothing special.
        
        long result = messageServiceClient.count(userRef);
        
        Assert.assertEquals(1, result); // (3)
        mockServer.verify(); // (4)
    }
}

Then you can run the countTest() and it works perfect.

This example is just a very basic Mock, showing you how it works. Depends on your service and client requirement, you need to customize the expect rule to verify the functionality of your client impl.

This is really easy. I like it.

End.






12/03/2013

[MongoDB-Escape dots '.' in map key] Resolve org.springframework.data.mapping.model.MappingException: Map key foo.map.key contains dots but no replacement was configured!

Sometimes we need to save map into MongoDB. But the key of the map cannot have dots inside its keys.

If the key has dot(s), by default we'll get this kind of exception:
org.springframework.data.mapping.model.MappingException: Map key foo.bar.key contains dots but no replacement was configured! Make sure map keys don't contain dots in the first place or configure an appropriate replacement!
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.potentiallyEscapeMapKey(MappingMongoConverter.java:622)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.writeMapInternal(MappingMongoConverter.java:586)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.createMap(MappingMongoConverter.java:517)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.writePropertyInternal(MappingMongoConverter.java:424)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter$3.doWithPersistentProperty(MappingMongoConverter.java:386)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter$3.doWithPersistentProperty(MappingMongoConverter.java:373)
    at org.springframework.data.mapping.model.BasicPersistentEntity.doWithProperties(BasicPersistentEntity.java:257)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.writeInternal(MappingMongoConverter.java:373)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.writePropertyInternal(MappingMongoConverter.java:451)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter$3.doWithPersistentProperty(MappingMongoConverter.java:386)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter$3.doWithPersistentProperty(MappingMongoConverter.java:373)
    at org.springframework.data.mapping.model.BasicPersistentEntity.doWithProperties(BasicPersistentEntity.java:257)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.writeInternal(MappingMongoConverter.java:373)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.writePropertyInternal(MappingMongoConverter.java:451)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter$3.doWithPersistentProperty(MappingMongoConverter.java:386)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter$3.doWithPersistentProperty(MappingMongoConverter.java:373)
    at org.springframework.data.mapping.model.BasicPersistentEntity.doWithProperties(BasicPersistentEntity.java:257)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.writeInternal(MappingMongoConverter.java:373)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.writeInternal(MappingMongoConverter.java:345)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.write(MappingMongoConverter.java:310)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.write(MappingMongoConverter.java:77)
    at org.springframework.data.mongodb.core.MongoTemplate.doSave(MongoTemplate.java:859)
    at org.springframework.data.mongodb.core.MongoTemplate.save(MongoTemplate.java:806)
    at org.springframework.data.mongodb.core.MongoTemplate.save(MongoTemplate.java:794)


Refer to the source code:

Spring => "MappingMongoConverter.java"
/**
     * Potentially replaces dots in the given map key with the configured map key replacement if configured or aborts
     * conversion if none is configured.
     * 
     * @see #setMapKeyDotReplacement(String)
     * @param source
     * @return
     */
    protected String potentiallyEscapeMapKey(String source) {

        if (!source.contains(".")) {
            return source;
        }

        if (mapKeyDotReplacement == null) {
            throw new MappingException(String.format("Map key %s contains dots but no replacement was configured! Make "
                    + "sure map keys don't contain dots in the first place or configure an appropriate replacement!", source));
        }

        return source.replaceAll("\\.", mapKeyDotReplacement);
    }

So the solution is configure the property mapKeyDotReplacement for bean MappingMongoConverter in the spring config file.

For example:
<bean id="mongoMoxydomainConverter" class="org.springframework.data.mongodb.core.convert.MappingMongoConverter">
        <constructor-arg index="0" ref="mongoDbFactory" />
        <constructor-arg index="1">
            <bean class="org.springframework.data.mongodb.core.mapping.MongoMappingContext" />
        </constructor-arg>
        <property name="mapKeyDotReplacement" value="\\+"/>
</bean>

What needs to be mentioned is that, the value that we set for "mapKeyDotReplacement" must follow the regular pattern's rule. If use reserved character, must use '\\' to translate it.




10/28/2013

Junit Config @ContextConfiguration locations

When we run Junit test for services, if the service is configured using spring, we need to load the spring context first.

Normally, the way to configure the Junit test case is:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"config.xml"})
public class MessageServiceTest {

 private static final Logger logger = Logger.getLogger(MessageServiceTest.class);
 
 //MessageService service = new MessageServiceImpl();
 
 @Autowired
 MessageService service;
 
 @Test
 public void sendMessageTest() {
        //TODO-something
        }
}

If we are not clear of how spring look for context file, it's boring to have exceptions files not found blablabla...

Just make a summary here:

@ContextConfiguration(locations={"config.xml"})
=>This means to look for config.xml under the test package that holds currently running test case.

@ContextConfiguration(locations={"/config.xml"})
@ContextConfiguration(locations={"classfile:config.xml"})
=>These two equals, means to look for config.xml under the classpath. It could be under either 'src/main/resources' or 'src/test/resources' (By default) (If you change the classpath, then configure based on your own configuration.)

@ContextConfiguration(locations={"file:src/main/webapp/config.xml"})
=>This means to look for config.xml based on relative path to the project's base directory.


If wanna configure multiple config files, then
@ContextConfiguration
(
  {
   "classpath:beans.xml",
   "file:src/main/webapp/spring/applicationContext.xml",
   "file:src/main/webapp/spring/domain-config.xml",
   "file:src/main/webapp/spring/dispatcher-servlet.xml"
  }
)

Good Luck!



5/17/2013

Spring LDAP Template VS UnboundID (ApacheDS 1.5.7 VS ApacheDS 2.0.0)

For Basic impl and compare of these two.
Refer to
CRUD for LDAP (Spring LDAP Template, ApacheDS).
             

CRUD for LDAP (UnboundID JDK, ApacheDS)


For efficiency,

I wrote several test cases to get the data:

Environment: Win7, 8G memory, 3.3GHz

Efficiency Compare of ApacheDS 1.5.7 & 2.0.0 (Use UnboundId)

1.5.7
2.0.0
create 1 records
20421ms
130ms
create first 50 records
1245862ms(hasn’t test increase)
3510ms(increase 700ms each time create 50 more)
Search 1 record from 200 records
50ms
45ms
Search 1 record from 5000 records
720ms
700ms
Efficiency Compare of Spring & UnboundId (Use ApacheDS 2.0.0 as LDAP server)

Spring
UnboundId
create first 1 records
93ms
118ms
Create the  5001st records
333ms
221ms
create first 50 records
7129ms
6443ms
Search 1 record from 200 records
31ms
45ms
Search 1 record from 5000 records
730-770 ms
650-700 ms
1000 Thread Search 1 record from 5000 records
Total Cost around 360000ms
Average around 360 ms
Total Cost around 360000ms
Average around 360ms

For apacheDS 2.0.0
set HEAP=-Xms2048m -Xmx4096m
200 initialize connection 800 max connection
Maximum support 1200 threads creating and searching at the same time.
But if the records number in the LDAP is increasing, the supported thread number will decrease.

In the compare, we can see, the create efficiency of ApacheDS 1.5.7 and 2.0.0 is not on the same level. V2.0.0 is much faster. The retrieval efficiency does not have that much difference. Guess all use the classic search logic...^^

While compare Spring LDAP template and UnboundID, found that when data set is small, spring may have a better performance, but as the data size increase, spring's efficiency is decreased and UnboundID is better.

While in the testing, we can prove that LDAP is definitely not designed to do frequently create and update. 
--The creating efficiency is so bad, especially in ApacheDS 1.5.7. 
--Also if you make create and search in the same time, when thread number increase and when data set size is huge, the server can easily crush. (Heap Space Exception. Memory is not enough.)
--If the server crush, you cannot access the data anymore, and cannot resume even restart the server, also the invalid EOF exception may be thrown out.