I am facing problems while integrating Spring MVC 3 , AJAX and apache tiles. Specially with AJAX.
Kindly suggest some links for this.
I am trying to load results on a tile with help of ajax call from another tile containing search criteria.
Thanks in advance.
you need to reconfigure this:
<bean id="tilesViewResolver" class="org.springframework.js.ajax.AjaxUrlBasedViewResolver">
<property name="viewClass" value="org.springframework.js.ajax.tiles3.AjaxTilesView"/>
</bean>
<bean class="org.springframework.web.servlet.view.tiles3.TilesConfigurer" id="tilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/layouts/layouts.xml</value>
<!-- Scan views directory for Tiles configurations -->
<value>/WEB-INF/views/**/views.xml</value>
</list>
</property>
</bean>
where AjaxUrlBasedViewResolver is in spring-js-2.3.1-RELEASE.jar and
AjaxTilesView is a custom implementation based on org.springframework.js.ajax.tiles2.AjaxTilesView and org.apache.tiles.web.util.TilesDispatchServlet.doGet() like that:
package org.springframework.js.ajax.tiles3;
/*
* Copyright 2004-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.el.ELContext;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.JspContext;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.el.ExpressionEvaluator;
import javax.servlet.jsp.el.VariableResolver;
import org.apache.tiles.Attribute;
import org.apache.tiles.AttributeContext;
import org.apache.tiles.Definition;
import org.apache.tiles.TilesContainer;
import org.apache.tiles.access.TilesAccess;
import org.apache.tiles.context.TilesRequestContextHolder;
import org.apache.tiles.request.ApplicationContext;
import org.apache.tiles.request.Request;
import org.apache.tiles.request.jsp.JspUtil;
import org.apache.tiles.request.servlet.ServletRequest;
import org.apache.tiles.request.servlet.ServletUtil;
import org.springframework.js.ajax.AjaxHandler;
import org.springframework.js.ajax.SpringJavascriptAjaxHandler;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.support.JstlUtils;
import org.springframework.web.servlet.support.RequestContext;
import org.springframework.web.servlet.view.tiles3.TilesView;
/**
* Tiles view implementation that is able to handle partial rendering for Spring
* Javascript Ajax requests.
*
* <p>
* This implementation uses the {#link SpringJavascriptAjaxHandler} by default
* to determine whether the current request is an Ajax request. On an Ajax
* request, a "fragments" parameter will be extracted from the request in order
* to determine which attributes to render from the current tiles view.
* </p>
*
* #author Jeremy Grelle
* #author David Winterfeldt
*/
public class AjaxTilesView extends TilesView {
private static final String FRAGMENTS_PARAM = "fragments";
private TilesRequestContextHolder tilesRequestContextFactory;
private AjaxHandler ajaxHandler = new SpringJavascriptAjaxHandler();
public void afterPropertiesSet() throws Exception {
super.afterPropertiesSet();
tilesRequestContextFactory = new TilesRequestContextHolder();
}
public AjaxHandler getAjaxHandler() {
return ajaxHandler;
}
public void setAjaxHandler(AjaxHandler ajaxHandler) {
this.ajaxHandler = ajaxHandler;
}
protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response)
throws Exception {
ServletContext servletContext = getServletContext();
if (ajaxHandler.isAjaxRequest(request, response)) {
String[] fragmentsToRender = getRenderFragments(model, request, response);
if (fragmentsToRender.length == 0) {
logger.warn("An Ajax request was detected, but no fragments were specified to be re-rendered. "
+ "Falling back to full page render. This can cause unpredictable results when processing "
+ "the ajax response on the client.");
super.renderMergedOutputModel(model, request, response);
return;
}
ApplicationContext tilesRequestContext = org.apache.tiles.request.servlet.ServletUtil
.getApplicationContext(getServletContext());
ServletRequest servletRequest = new ServletRequest(tilesRequestContext,
request, response);
TilesContainer container = TilesAccess.getContainer(tilesRequestContext);
if (container == null) {
throw new ServletException("Tiles container is not initialized. "
+ "Have you added a TilesConfigurer to your web application context?");
}
exposeModelAsRequestAttributes(model, request);
JstlUtils.exposeLocalizationContext(new RequestContext(request, servletContext));
Definition compositeDefinition = container.getDefinition(getUrl(), servletRequest);
Map flattenedAttributeMap = new HashMap();
flattenAttributeMap(container, tilesRequestContext, flattenedAttributeMap, compositeDefinition,
servletRequest);
addRuntimeAttributes(container, flattenedAttributeMap, servletRequest);
if (fragmentsToRender.length > 1) {
request.setAttribute(ServletRequest.FORCE_INCLUDE_ATTRIBUTE_NAME, true);
}
for (int i = 0; i < fragmentsToRender.length; i++) {
Attribute attributeToRender = (Attribute) flattenedAttributeMap.get(fragmentsToRender[i]);
if (attributeToRender == null) {
throw new ServletException("No tiles attribute with a name of '" + fragmentsToRender[i]
+ "' could be found for the current view: " + this);
} else {
// container.inheritCascadedAttributes(compositeDefinition);
container.render(attributeToRender, servletRequest);
container.endContext(servletRequest);
}
}
} else {
super.renderMergedOutputModel(model, request, response);
}
}
protected String[] getRenderFragments(Map model, HttpServletRequest request, HttpServletResponse response) {
String attrName = request.getParameter(FRAGMENTS_PARAM);
String[] renderFragments = StringUtils.commaDelimitedListToStringArray(attrName);
return StringUtils.trimArrayElements(renderFragments);
}
/**
* <p>
* Iterate over all attributes in the given Tiles definition. Every
* attribute value that represents a template (i.e. start with "/") or is a
* nested definition is added to a Map. The method class itself recursively
* to traverse nested definitions.
* </p>
*
* #param container
* the TilesContainer
* #param requestContext
* the TilesRequestContext
* #param resultMap
* the output Map where attributes of interest are added to.
* #param compositeDefinition
* the definition to search for attributes of interest.
* #param request
* the servlet request
* #param response
* the servlet response
*/
protected void flattenAttributeMap(TilesContainer container, ApplicationContext requestContext, Map resultMap,
Definition compositeDefinition, ServletRequest servletRequest) {
Set<String> cascadedAttributeNames = compositeDefinition.getCascadedAttributeNames();
Iterator iterator = null;
if (cascadedAttributeNames ==null){
iterator = compositeDefinition.getLocalAttributeNames().iterator();
}else{
iterator = cascadedAttributeNames.iterator();
}
while (iterator.hasNext()) {
String attributeName = (String) iterator.next();
Attribute attribute = compositeDefinition.getAttribute(attributeName);
if (attribute.getValue() == null || !(attribute.getValue() instanceof String)) {
continue;
}
String value = attribute.getValue().toString();
if (value.startsWith("/")) {
resultMap.put(attributeName, attribute);
} else if (container.isValidDefinition(value, servletRequest)) {
resultMap.put(attributeName, attribute);
Definition nestedDefinition = container.getDefinition(value, servletRequest);
Assert.isTrue(nestedDefinition != compositeDefinition, "Circular nested definition: " + value);
flattenAttributeMap(container, requestContext, resultMap, nestedDefinition, servletRequest);
}
}
}
/**
* <p>
* Iterate over dynamically added Tiles attributes (see
* "Runtime Composition" in the Tiles documentation) and add them to the
* output Map passed as input.
* </p>
*
* #param container
* the Tiles container
* #param resultMap
* the output Map where attributes of interest are added to.
* #param request
* the Servlet request
* #param response
* the Servlet response
*/
protected void addRuntimeAttributes(TilesContainer container, Map resultMap, ServletRequest servletRequest) {
AttributeContext attributeContext = container.getAttributeContext(servletRequest);
Set attributeNames = new HashSet();
if (attributeContext.getLocalAttributeNames() != null) {
attributeNames.addAll(attributeContext.getLocalAttributeNames());
}
if (attributeContext.getCascadedAttributeNames() != null) {
attributeNames.addAll(attributeContext.getCascadedAttributeNames());
}
Iterator iterator = attributeNames.iterator();
while (iterator.hasNext()) {
String name = (String) iterator.next();
Attribute attr = attributeContext.getAttribute(name);
resultMap.put(name, attr);
}
}
}
Hope this help you.
Last Spring WebFlow 2.4 includes a new FlowAjaxTiles3View that extends org.springframework.js.ajax.tiles3.AjaxTilesView and works with Tiles 3 yet. It permits to define render fragments in flow definition besides using "fragments" request param:
<bean id="tilesViewResolver" class="org.springframework.js.ajax.AjaxUrlBasedViewResolver">
<property name="viewClass" value="org.springframework.webflow.mvc.view.FlowAjaxTiles3View"/>
</bean>
Also you shouldn't forget to point your view factory to this viewResolver:
<webflow:flow-builder-services id="flowBuilderServices" view-factory-creator="mvcViewFactoryCreator" />
<!-- Configures Web Flow to use Tiles to create views for rendering; Tiles allows for applying consistent layouts to your views -->
<bean id="mvcViewFactoryCreator" class="org.springframework.webflow.mvc.builder.MvcViewFactoryCreator">
<property name="viewResolvers" ref="tilesViewResolver"/>
</bean>
Related
I'm using spring-security-core 2.0 in my grails 2.4.4 application. I limited my concurrent session access of a user to 1.
So the problem is after session timeout of 30 minutes I couldn't login with that username and password, it will throw the concurrent session maximum exceeded exception.
I'm doubtful that, on the session timeout my logout is not working properly so that session can still be active.
I'm a newbi to spring-security so can anyone tell me what to do?
Here I'm giving What are changes I have made in my code to limit the concurrent access.
resources.groovy
// Place your Spring DSL code here
/*beans = {
messageSource(org.springframework.context.support.ReloadableResourceBundleMessageSource) { basename = "classpath:grails-app/src/resource_bundle" }
}*/
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy;
import com.custom.sessiontime.CustomSessionLogoutHandler
beans = {
sessionRegistry(SessionRegistryImpl)
customSessionLogoutHandler(CustomSessionLogoutHandler,ref('sessionRegistry'))
concurrentSessionControlAuthenticationStrategy(ConcurrentSessionControlAuthenticationStrategy,ref('sessionRegistry')){
exceptionIfMaximumExceeded = true
maximumSessions = 1
}
sessionFixationProtectionStrategy(SessionFixationProtectionStrategy){
migrateSessionAttributes = true
alwaysCreateSession = true
}
registerSessionAuthenticationStrategy(RegisterSessionAuthenticationStrategy,ref('sessionRegistry'))
sessionAuthenticationStrategy(CompositeSessionAuthenticationStrategy,[ref('concurrentSessionControlAuthenticationStrategy'),ref('sessionFixationProtectionStrategy'),ref('registerSessionAuthenticationStrategy')])
}
CustomSessionLogoutHandler
package com.custom.sessiontime
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.util.Assert;
import org.springframework.security.core.session.SessionRegistry;
/**
* {#link CustomSessionLogoutHandler} is in charge of removing the {#link SessionRegistry} upon logout. A
* new {#link SessionRegistry} will then be generated by the framework upon the next request.
*
* #author Mohd Qusyairi
* #since 0.1
*/
public final class CustomSessionLogoutHandler implements LogoutHandler {
private final SessionRegistry sessionRegistry;
/**
* Creates a new instance
* #param sessionRegistry the {#link SessionRegistry} to use
*/
public CustomSessionLogoutHandler(SessionRegistry sessionRegistry) {
Assert.notNull(sessionRegistry, "sessionRegistry cannot be null");
this.sessionRegistry = sessionRegistry;
}
/**
* Clears the {#link SessionRegistry}
*
* #see org.springframework.security.web.authentication.logout.LogoutHandler#logout(javax.servlet.http.HttpServletRequest,
* javax.servlet.http.HttpServletResponse,
* org.springframework.security.core.Authentication)
*/
public void logout(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) {
this.sessionRegistry.removeSessionInformation(request.getSession().getId());
}
}
Finally added below line to Config.groovy
grails.plugin.springsecurity.logout.handlerNames = ['customSessionLogoutHandler','securityContextLogoutHandler','rememberMeServices']
I want to cache a List of Category when level == 0, but keeping getting IllegalArgumentException. What am I missing?
In Service class:
#Override
#Transactional(readOnly = true)
#Cacheable(value="categories", condition="#level == 0")
public List<Category> findCategoryByLevel(int level) throws DataAccessException {
return categoryRepository.findCategoryByLevel(level);
}
Error:
java.lang.IllegalArgumentException: Cannot find cache named 'categories' for CacheableOperation[public java.util.List com.mySite.service.DidicityServiceImpl.findCategoryByLevel(int) throws org.springframework.dao.DataAccessException] caches=[categories] | key='' | condition='#level == 0' | unless=''
What caching provider are you using in Spring's Cache Abstraction? (I.e. ehcache, Guava, Hazelcast, etc)
It appears you are missing an explicit "Cache" definition and instance in your actual caching provider. For example, when using Pivotal GemFire as a caching provider in Spring's Cache Abstraction, you need to define a Region (a.k.a. Cache in the Spring Cache Abstraction), using your example above, like so...
<gfe:cache ...>
<gfe:replicated-region id="categories" persistent="false"...>
...
</gfe:replicated-region>
Spring Data GemFire goes onto lookup the "Cache" when the cached application service|repository method is invoked, and so the actual backing "Cache" (i.e. the GemFire Region) must exist, otherwise the Spring Cache Abstraction throws an IllegalArgumentException.
So, by way of a more explicit example, I wrote the following test...
/*
* Copyright 2014-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.spring.cache;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.spring.cache.CachingWithConcurrentMapUsingExplicitlyNamedCachesTest.ApplicationConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* The CachingWithConcurrentMapUsingExplicitlyNamedCachesTest class is a test suite of test cases testing the contract
* and functionality of Spring Cache Abstraction using the ConcurrentMap-based Cache Management Strategy
* with explicitly named "Caches".
*
* NOTE: when the Cache(s) [is|are] explicitly named using the ConcurrentMapCacheManager, then "dynamic" is disabled
* and corresponding the named Cache in the #Cacheable annotation of the cached service method must exist
* (or be declared). If no explicitly named Caches are provided to the ConcurrentMapManager constructor, then dynamic
* is enabled and the Cache will be created at runtime, on the fly.
*
* #author John Blum
* #see org.junit.Test
* #see org.junit.runner.RunWith
* #see org.springframework.cache.Cache
* #see org.springframework.cache.CacheManager
* #see org.springframework.cache.annotation.Cacheable
* #see org.springframework.cache.annotation.EnableCaching
* #see org.springframework.cache.concurrent.ConcurrentMapCacheManager
* #see org.springframework.context.annotation.Bean
* #see org.springframework.context.annotation.Configuration
* #see org.springframework.test.context.ContextConfiguration
* #see org.springframework.test.context.junit4.SpringJUnit4ClassRunner
* #since 1.0.0
*/
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = ApplicationConfiguration.class)
#SuppressWarnings("unused")
public class CachingWithConcurrentMapUsingExplicitlyNamedCachesTest {
#Autowired
private NumberCategoryService numberCategoryService;
#Test
public void numberCategoryCaching() {
assertThat(numberCategoryService.isCacheMiss(), is(false));
List<NumberCategory> twoCategories = numberCategoryService.classify(2.0);
assertThat(twoCategories, is(notNullValue()));
assertThat(twoCategories.size(), is(equalTo(3)));
assertThat(twoCategories.containsAll(Arrays.asList(
NumberCategory.EVEN, NumberCategory.POSITIVE, NumberCategory.WHOLE)), is(true));
assertThat(numberCategoryService.isCacheMiss(), is(true));
List<NumberCategory> twoCategoriesAgain = numberCategoryService.classify(2.0);
assertThat(twoCategoriesAgain, is(sameInstance(twoCategories)));
assertThat(numberCategoryService.isCacheMiss(), is(false));
List<NumberCategory> negativeThreePointFiveCategories = numberCategoryService.classify(-3.5);
assertThat(negativeThreePointFiveCategories, is(notNullValue()));
assertThat(negativeThreePointFiveCategories.size(), is(equalTo(3)));
assertThat(negativeThreePointFiveCategories.containsAll(Arrays.asList(
NumberCategory.ODD, NumberCategory.NEGATIVE, NumberCategory.FLOATING)), is(true));
assertThat(numberCategoryService.isCacheMiss(), is(true));
}
#Configuration
#EnableCaching
public static class ApplicationConfiguration {
#Bean
public CacheManager cacheManager() {
//return new ConcurrentMapCacheManager("Categories");
return new ConcurrentMapCacheManager("Temporary");
}
#Bean
public NumberCategoryService numberCategoryService() {
return new NumberCategoryService();
}
}
#Service
public static class NumberCategoryService {
private volatile boolean cacheMiss;
public boolean isCacheMiss() {
boolean localCacheMiss = this.cacheMiss;
this.cacheMiss = false;
return localCacheMiss;
}
protected void setCacheMiss() {
this.cacheMiss = true;
}
#Cacheable("Categories")
public List<NumberCategory> classify(double number) {
setCacheMiss();
List<NumberCategory> categories = new ArrayList<>(3);
categories.add(isEven(number) ? NumberCategory.EVEN : NumberCategory.ODD);
categories.add(isPositive(number) ? NumberCategory.POSITIVE : NumberCategory.NEGATIVE);
categories.add(isWhole(number) ? NumberCategory.WHOLE : NumberCategory.FLOATING);
return categories;
}
protected boolean isEven(double number) {
return (isWhole(number) && Math.abs(number) % 2 == 0);
}
protected boolean isFloating(double number) {
return !isWhole(number);
}
protected boolean isNegative(double number) {
return (number < 0);
}
protected boolean isOdd(double number) {
return !isEven(number);
}
protected boolean isPositive(double number) {
return (number > 0);
}
protected boolean isWhole(double number) {
return (number == Math.floor(number));
}
}
public enum NumberCategory {
EVEN,
FLOATING,
NEGATIVE,
ODD,
POSITIVE,
WHOLE
}
}
This test example is currently setup to throw the IllegalArgumentException. If you change this...
return new ConcurrentMapCacheManager("Temporary");
To this...
return new ConcurrentMapCacheManager("Categories");
Then all is well.
Hopefully this adequately illustrates the problem you are having and how to fix it.
Cheers,
John
Looks like you want to use the key as a static hardcoded string.
Try the following "'categories'"
#Override
#Transactional(readOnly = true)
#Cacheable(value="'categories'", condition="#level == 0")
public List<Category> findCategoryByLevel(int level) throws DataAccessException {
return categoryRepository.findCategoryByLevel(level);
}
I've downloaded the k-means(in hadoop mapreduce) opensource. But, it has compile errors.
---------------------SOURCE----------------------------
/*
* Copyright 2012
* Parallel and Distributed Systems Group (PVS)
* Institute of Computer Science (IFI)
* Heidelberg University
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package examples;
import algorithms.kmeans.Cluster;
import algorithms.kmeans.Clusters;
import algorithms.kmeans.SamplesCache;
import org.apache.*;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.*;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapred.JobContext;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.mahout.math.DenseVector;
import org.apache.mahout.math.DenseVectorWritable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
public class KMeansHadoop {
private final static Logger LOG = LoggerFactory.getLogger(KMeansHadoop.class);
public static class KMeansMapper extends
MRMapper<LongWritable, Text, IntWritable, Clusters, Clusters> {
private SamplesCache cache = new SamplesCache(500);
private int cacheSize = 10000;
private Clusters clusters = null;
private int k = 0;
private int nextCentroidToInit = 0;
/**
* Configures the mapper by reading two configuration options:
* - "numClusters": the k in k-Means
* - "numAuxClusters": the number of in-memory auxiliary clusters representing the input data
*
* #param context the mapper context, used to access the configuration
* #throws IOException
* #throws InterruptedException
*/
#Override
protected void setup(Context context) throws IOException, InterruptedException {
super.setup(context);
Configuration conf = context.getConfiguration();
this.k = conf.getInt("numCluster", 5);
this.clusters = new Clusters(k);
this.cacheSize = conf.getInt("numAuxCluster", 500);
this.cache = new SamplesCache(cacheSize);
}
/**
* Maps the input lines to initial centroids and, as a side-effect, stores auxiliary clusters representing the
* input data in memory
*
* #param key the key provided by the input format, not used here
* #param value one line of the input; input format: one data point per line, vector components delimited by spaces
* #param context the mapper context used to send initial centroids to the reducer
* #throws IOException
* #throws InterruptedException
*/
#Override
public void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
// Input format: one data point per line, components delimited by spaces
final List<Double> doubleValues = new ArrayList<Double>();
final StringTokenizer tk = new StringTokenizer(value.toString());
while(tk.hasMoreElements()) {
final String token = tk.nextToken();
doubleValues.add(Double.parseDouble(token));
}
double[] dv = new double[doubleValues.size()];
for(int i=0; i<doubleValues.size(); i++) {
dv[i] = doubleValues.get(i);
}
DenseVector dvec = new DenseVector(dv);
DenseVectorWritable sample = new DenseVectorWritable(dvec);
// add sample to local auxiliary clusters
this.cache.addSample(sample);
// first k points are chosen as initial centroids
if (nextCentroidToInit < k) {
this.clusters.set(nextCentroidToInit, new Cluster(sample, sample));
this.nextCentroidToInit += 1;
} else if (nextCentroidToInit == k) {
// send initial centroids to reducer
context.write(new IntWritable(0), this.clusters);
this.nextCentroidToInit += 1;
}
}
/**
* Remaps the input data when a new set of preliminary clusters is received from the reducer by recalculating
* the assignment of the local input data, as represented by the auxiliary clusters, to the preliminary clusters
* and sends the updated centroids to the reducer.
* #param cs the preliminary clusters computed by the reducer
* #param context the mapper context used to send the locally recomputed centroids to the reducer
* #throws IOException
* #throws InterruptedException
*/
public void remap(List<Clusters> cs, Context context) throws IOException, InterruptedException {
LOG.info("Remapping preliminary clusters");
// set the preliminary clusters as new clusters
this.clusters = cs.get(0).clone();
this.clusters.reset();
// reassign the local input data, represented by the auxiliary clusters, to the clusters, thereby readjusting
// the clusters centroids
this.cache.reAssignAll(clusters);
// send the locally updated clusters to the reducer
context.write(new IntWritable(0), this.clusters);
}
}
public static class KMeansReducer extends
MRReducer<IntWritable, Clusters, IntWritable, Clusters, Clusters> {
private double lastError = Double.MAX_VALUE;
private float epsilon = Float.MAX_VALUE;
/**
* Configures the mapper by reading the configuration option "epsilon": The minimum change of the MSE needed to
* trigger a new iteration.
*
* #param context the reducer context, used to access the configuration
* #throws IOException
* #throws InterruptedException
*/
#Override
protected void setup(Context context) throws IOException, InterruptedException {
Configuration conf = context.getConfiguration();
epsilon = conf.getFloat("epsilon", 100f);
}
/**
* Reduces a list of clusters locally computed by the mappers into a preliminary global set of clusters, which
* is then restreamed to the mappers, or, iff the MSE of the global set of clusters has not changed by more than
* epsilon since the last reduce invocation ends the iteration by emiting the final set of clusters.
*
* #param key the key set by the mapper, not used here
* #param values the list of locally computed clusters computed by the mappers
* #param context the reducer context, used to restream preliminary clusters to the mappers and emit the final
* clusters
* #throws IOException
* #throws InterruptedException
*/
#Override
protected void reduce(IntWritable key, Iterable<Clusters> values,
MRReduceContext<IntWritable, Clusters, IntWritable, Clusters, Clusters> context) throws IOException, InterruptedException {
// Merge the list of clusters into one set of clusters
Clusters results = null;
for(Clusters clusters : values) {
if( results == null ) {
results = clusters;
} else {
results.merge(clusters);
}
}
Double error = results.getMSE();
LOG.info("Last error " + lastError + ", current error " + error);
if (lastError < Double.MAX_VALUE &&
error <= lastError + epsilon &&
error >= lastError - epsilon) {
// MSE has changed by less than epsilon: Emit final result
context.write(new IntWritable(0), results);
LOG.info("Final result written.");
} else {
// MSE has changed by more than epsilon: Send recomputed preliminary clusters to mappers to start a new
// iteration
this.lastError = error;
results.computeNewCentroids();
context.restream(results);
LOG.info("Preliminary result restreamed.");
}
}
}
/**
* Executes the streaming Hadoop MapReduce program
* #param args first arg is input path, second arg is output path
* #throws Exception
*/
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
conf.setBoolean("mrstreamer.hadoop.streaming", true);
// has to be 1 to ensure the algorithm producing valid results
conf.setInt(JobContext.NUM_REDUCES, 1);
conf.setInt(JobContext.NUM_MAPS, 4);
conf.set("numCluster", "5");
conf.set("numAuxCluster", "500");
Job job = new MRSJob(conf, "kmeanshadoop");
job.setOutputKeyClass(IntWritable.class);
job.setOutputValueClass(Clusters.class);
job.setMapperClass(KMeansMapper.class);
job.setReducerClass(KMeansReducer.class);
job.setInputFormatClass(TextInputFormat.class);
job.setOutputFormatClass(TextOutputFormat.class);
FileInputFormat.addInputPath(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
job.waitForCompletion(true);
}
}
---------------------------ERROR------------------------------
Exception in thread "main" java.lang.Error: Unresolved compilation problems:
NUM_REDUCES cannot be resolved or is not a field
NUM_MAPS cannot be resolved or is not a field
at examples.KMeansHadoop.main(KMeansHadoop.java:222)
Probably, you are not using the same version of Hadoop as the authors of this code. It should be covered by the line:
import org.apache.hadoop.mapred.JobContext;
Update to hadoop version 2.2.0 (or later) if you want to use these settings.
Otherwise, you can use instead of these two commands, the following on the old API:
conf.setNumReduceTasks(1);
conf.setNumMapTasks(4); //but this is only a suggestion to hadoop
My question is very simple. I want to use another taglib with freemarker.
I read in the freemarker doc that it's possible.
I want to use the kendoui taglib
<%#taglib prefix="kendo" uri="http://www.kendoui.com/jsp/tags"%>
the api doc says we must do that by adding a line like
<#assign html=JspTaglibs["/WEB-INF/struts-html.tld"]>.
But when i do that, i have the error
The following has evaluated to null or missing: ==> JspTaglibs
cordially.
thankyou for your answerd. it works thine when i use freemarkerServlet.
But i want to use a single servlet that will allow me to configure freemarker (in init method) and parse the html response to response writer.
req.setCharacterEncoding(cfg.getOutputEncoding());
resp.setContentType("text/html; charset=" + cfg.getOutputEncoding());
resp.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, post-check=0, pre-check=0");
resp.setHeader("Pragma", "no-cache");
resp.setHeader("Expires", "Thu, 01 Dec 1994 00:00:00 GMT");
Writer out = resp.getWriter();
template.process(page.getRoot(), out);
in fact, i want to extend freemarkerServlet class as mentionned here : http://schakrap.wordpress.com/2009/09/05/using-freemarkerservlet-in-google-guice-to-inject-configuration/ .
But i still have the same error.
for the matter what does mean the config parameter TemplatePath ?
The JspTaglibs variable, and the whole environment that's needed for custom JSP tags to feel home, is set up by the freemarker.ext.servlet.FreemarkerServlet. So currently the only way to use taglibs is with FreemarkerServlet, which was made so that you can use FTL files instead of JSP files in legacy JSP Model-2 frameworks.
package presentation;
import java.io.IOException;
import java.io.StringReader;
import java.io.Writer;
import java.text.DateFormat;
import java.util.Locale;
import java.util.Map;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import freemarker.core.Macro;
import freemarker.template.Configuration;
import freemarker.template.ObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateExceptionHandler;
public class ControllerServlet extends freemarker.ext.servlet.FreemarkerServlet
{
//
private Configuration cfg;
private Handler handler;
public ControllerServlet()
{
super();
}
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
String userAgentString = "";
StringBuffer url = req.getRequestURL();
String sUrl = url.toString();
// on instancie les classes/structures pour Freemarker
FreemarkerParameterMap page = new FreemarkerParameterMap();
handler = new Handler();
try
{
userAgentString = req.getHeader("User-Agent");
System.out.println("User-Agent : " + userAgentString);
handler.handle(req, resp, page);
if (page.getDataType() == FreemarkerParameterMap.AJAX_JSON)
{
Gson gson = new GsonBuilder().setDateFormat(DateFormat.FULL).setPrettyPrinting().create();
Map<String, Object> map = page.getRoot();
resp.setContentType("application/json;charset=UTF-8");
resp.getWriter().println(gson.toJson(map));
resp.getWriter().flush();
}
else if (page.getDataType() == FreemarkerParameterMap.HTML_REDIRECT && page.getRedirect() != null)
{
resp.sendRedirect(page.getRedirect());
}
else if (page.getDataType() == FreemarkerParameterMap.HTML_FORWARD && page.getForward() != null)
{
RequestDispatcher rd = req.getRequestDispatcher(page.getForward());
rd.forward(req, resp);
}
else
{
if (page.getTemplate() != null)
{
Template t = cfg.getTemplate("theme/" + page.getTemplate());
if (page.getDataType() == FreemarkerParameterMap.AJAX_HTML)
{
Map mapMacro = t.getMacros();
Macro macro = (Macro) mapMacro.get(page.getMacro());
t = new Template("name", new StringReader(macro.getSource()+"<#"+page.getMacro()+"/>"), cfg);
}
else if (page.getDataType() == FreemarkerParameterMap.HTML)
{
}
req.setCharacterEncoding(cfg.getOutputEncoding());
resp.setContentType("text/html; charset=" + cfg.getOutputEncoding());
resp.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, post-check=0, pre-check=0");
resp.setHeader("Pragma", "no-cache");
resp.setHeader("Expires", "Thu, 01 Dec 1994 00:00:00 GMT");
Writer out = resp.getWriter();
t.process(page.getRoot(), out);
}
else
{
throw new ServletException("aucune action specifiée");
}
}
}
/*catch (EcoliaException e)
{
} */
catch (Exception e)
{
e.printStackTrace();
}
}
/**
* The doPost method of the servlet. <br>
*
* This method is called when a form has its tag value method equals to post.
*
* #param request the request send by the client to the server
* #param response the response send by the server to the client
* #throws ServletException if an error occurred
* #throws IOException if an error occurred
*/
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
doGet(req, resp);
}
/**
* Initialization of the servlet. <br>
*
* #throws ServletException if an error occurs
*/
public void init() throws ServletException
{
// Initialize the FreeMarker configuration;
// - Create a configuration instance
cfg = new Configuration();
// - Templates are stored in the WEB-INF/templates directory of the Web app.
cfg.setServletContextForTemplateLoading(getServletContext(), "");
// - Set update dealy to 0 for now, to ease debugging and testing.
// Higher value should be used in production environment.
cfg.setTemplateUpdateDelay(0);
// - Set an error handler that prints errors so they are readable with
// a HTML browser.
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.HTML_DEBUG_HANDLER);
// - Use beans wrapper (recommmended for most applications)
cfg.setObjectWrapper(ObjectWrapper.BEANS_WRAPPER);
// - Set the default charset of the template files
cfg.setDefaultEncoding("UTF-8");
// - Set the charset of the output. This is actually just a hint, that
// templates may require for URL encoding and for generating META element
// that uses http-equiv="Content-type".
cfg.setOutputEncoding("UTF-8");
// - Set the default locale
cfg.setLocale(Locale.US);
System.out.println("init controleur");
}
}
and in the web.xml i have this :
<servlet>
<description>This is the description of my J2EE component</description>
<display-name>This is the display name of my J2EE component</display-name>
<servlet-name>ControllerServlet</servlet-name>
<servlet-class>presentation.ControllerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ControllerServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
I'm working on an application library with a utility class called "Config" which is backed by the Spring Environment object and provides strongly typed getters for all the applications configuration values.
The property sources for the configuration can vary depending on environment (DEV/PROD) and usage (standalone/test/webapp), and can range from the default ones (system & env props) to custom database and JNDI sources.
What I'm struggling with is how to let the apps consuming this library easily configure the property source(s) used by Environment, such that the properties are available for use in our Config class and via the PropertySourcesPlaceholderConfigurer.
We're still using XML configuration, so ideally this could be configured in XML something like.
<bean id="propertySources" class="...">
<property name="sources">
<list>
<ref local="jndiPropertySource"/>
<ref local="databasePropertySource"/>
</list>
</property>
</bean>
...and then injected somehow into the Environment's property sources collection.
I've read that something like this may not be possible due to the timing of the app context lifecycle, and that this may need to be done using an application initializer class.
Any ideas?
It depends on how you want to use the properties, if it is to inject the properties using ${propertyname} syntax, then yes just having PropertySourcesPlaceHolderConfigurer will work, which internally has access to the PropertySources registered in the environment.
If you plan to use Environment directly, using say env.getProperty(), then you are right - the properties using PropertySourcesPlaceHolderConfigurer are not visible here. The only way then is to inject it using Java code, there are two ways that I know of:
a. Using Java Config:
#Configuration
#PropertySource("classpath:/app.properties")
public class SpringConfig{
}
b. Using a custom ApplicationContextInitializer, the way it is described here
I came up with the following which seems to work, but I'm fairly new to Spring, so I'm not so sure how it will hold up under different use cases.
Basically, the approach is to extend PropertySourcesPlaceholderConfigurer and add a setter to allow the user to easily configure a List of PropertySource objects in XML. After creation, the property sources are copied to the current Environment.
This basically allows the property sources to be configured in one place, but used by both placholder configuration and Environment.getProperty scenarios.
Extended PropertySourcesPlaceholderConfigurer
public class ConfigSourcesConfigurer
extends PropertySourcesPlaceholderConfigurer
implements EnvironmentAware, InitializingBean {
private Environment environment;
private List<PropertySource> sourceList;
// Allow setting property sources as a List for easier XML configuration
public void setPropertySources(List<PropertySource> propertySources) {
this.sourceList = propertySources;
MutablePropertySources sources = new MutablePropertySources();
copyListToPropertySources(this.sourceList, sources);
super.setPropertySources(sources);
}
#Override
public void setEnvironment(Environment environment) {
// save off Environment for later use
this.environment = environment;
super.setEnvironment(environment);
}
#Override
public void afterPropertiesSet() throws Exception {
// Copy property sources to Environment
MutablePropertySources envPropSources = ((ConfigurableEnvironment)environment).getPropertySources();
copyListToPropertySources(this.sourceList, envPropSources);
}
private void copyListToPropertySources(List<PropertySource> list, MutablePropertySources sources) {
// iterate in reverse order to insure ordering in property sources object
for(int i = list.size() - 1; i >= 0; i--) {
sources.addFirst(list.get(i));
}
}
}
beans.xml file showing basic configuration
<beans>
<context:annotation-config/>
<context:component-scan base-package="com.mycompany" />
<bean class="com.mycompany.ConfigSourcesConfigurer">
<property name="propertySources">
<list>
<bean class="org.mycompany.CustomPropertySource" />
<bean class="org.springframework.core.io.support.ResourcePropertySource">
<constructor-arg value="classpath:default-config.properties" />
</bean>
</list>
</property>
</bean>
<bean class="com.mycompany.TestBean">
<property name="stringValue" value="${placeholder}" />
</bean>
</beans>
The following worked for me with Spring 3.2.4 .
PropertySourcesPlaceholderConfigurer must be registered statically in order to process the placeholders.
The custom property source is registered in the init method and as the default property sources are already registered, it can itself be parameterized using placeholders.
JavaConfig class:
#Configuration
#PropertySource("classpath:propertiesTest2.properties")
public class TestConfig {
#Autowired
private ConfigurableEnvironment env;
#Value("${param:NOVALUE}")
private String param;
#PostConstruct
public void init() {
env.getPropertySources().addFirst(new CustomPropertySource(param));
}
#Bean
public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
#Bean
public TestBean1 testBean1() {
return new TestBean1();
}
}
Custom property source:
public class CustomPropertySource extends PropertySource<Object> {
public CustomPropertySource(String param) {
super("custom");
System.out.println("Custom property source initialized with param " + param + ".");
}
#Override
public Object getProperty(String name) {
return "IT WORKS";
}
}
Test bean (getValue() will output "IT WORKS"):
public class TestBean1 {
#Value("${value:NOVALUE}")
private String value;
public String getValue() {
return value;
}
}
I had a similar problem, in my case I'm using Spring in a standalone application, after load the default configurations I may need apply another properties file (lazy load configs) present in a config directory. My solution was inspired this Spring Boot documentation, but with no dependency of Spring Boot. See below the source code:
#PropertySources(#PropertySource(value = "classpath:myapp-default.properties"))
public class PersistenceConfiguration {
private final Logger log = LoggerFactory.getLogger(getClass());
private ConfigurableEnvironment env;
#Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurerDev(ConfigurableEnvironment env) {
return new PropertySourcesPlaceholderConfigurer();
}
#Autowired
public void setConfigurableEnvironment(ConfigurableEnvironment env) {
for(String profile: env.getActiveProfiles()) {
final String fileName = "myapp-" + profile + ".properties";
final Resource resource = new ClassPathResource(fileName);
if (resource.exists()) {
try {
MutablePropertySources sources = env.getPropertySources();
sources.addFirst(new PropertiesPropertySource(fileName,PropertiesLoaderUtils.loadProperties(resource)));
} catch (Exception ex) {
log.error(ex.getMessage(), ex);
throw new RuntimeException(ex.getMessage(), ex);
}
}
}
this.env = env;
}
...
}
I recently ran into the issue of how to register custom property sources in the environment. My specific problem is that I have a library with a Spring configuration that I want to be imported into the Spring application context, and it requires custom property sources. However, I don't necessarily have control over all of the places where the application context is created. Because of this, I do not want to use the recommended mechanisms of ApplicationContextInitializer or register-before-refresh in order to register the custom property sources.
What I found really frustrating is that using the old PropertyPlaceholderConfigurer, it was easy to subclass and customize the configurers completely within the Spring configuration. In contrast, to customize property sources, we are told that we have to do it not in the Spring configuration itself, but before the application context is initialized.
After some research and trial and error, I discovered that it is possible to register custom property sources from inside of the Spring configuration, but you have to be careful how you do it. The sources need to be registered before any PropertySourcesPlaceholderConfigurers execute in the context. You can do this by making the source registration a BeanFactoryPostProcessor with PriorityOrdered and an order that is higher precedence than the PropertySourcesPlaceholderConfigurer that uses the sources.
I wrote this class, which does the job:
package example;
import java.io.IOException;
import java.util.Properties;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.PropertiesLoaderSupport;
/**
* This is an abstract base class that can be extended by any class that wishes
* to become a custom property source in the Spring context.
* <p>
* This extends from the standard Spring class PropertiesLoaderSupport, which
* contains properties that specify property resource locations, plus methods
* for loading properties from specified resources. These are all available to
* be used from the Spring configuration, and by subclasses of this class.
* <p>
* This also implements a number of Spring flag interfaces, all of which are
* required to maneuver instances of this class into a position where they can
* register their property sources BEFORE PropertySourcesPlaceholderConfigurer
* executes to substitute variables in the Spring configuration:
* <ul>
* <li>BeanFactoryPostProcessor - Guarantees that this bean will be instantiated
* before other beans in the context. It also puts it in the same phase as
* PropertySourcesPlaceholderConfigurer, which is also a BFPP. The
* postProcessBeanFactory method is used to register the property source.</li>
* <li>PriorityOrdered - Allows the bean priority to be specified relative to
* PropertySourcesPlaceholderConfigurer so that this bean can be executed first.
* </li>
* <li>ApplicationContextAware - Provides access to the application context and
* its environment so that the created property source can be registered.</li>
* </ul>
* <p>
* The Spring configuration for subclasses should contain the following
* properties:
* <ul>
* <li>propertySourceName - The name of the property source this will register.</li>
* <li>location(s) - The location from which properties will be loaded.</li>
* <li>addBeforeSourceName (optional) - If specified, the resulting property
* source will be added before the given property source name, and will
* therefore take precedence.</li>
* <li>order (optional) - The order in which this source should be executed
* relative to other BeanFactoryPostProcessors. This should be used in
* conjunction with addBeforeName so that if property source factory "psfa"
* needs to register its property source before the one from "psfb", "psfa"
* executes AFTER "psfb".
* </ul>
*
* #author rjsmith2
*
*/
public abstract class AbstractPropertySourceFactory extends
PropertiesLoaderSupport implements ApplicationContextAware,
PriorityOrdered, BeanFactoryPostProcessor {
// Default order will be barely higher than the default for
// PropertySourcesPlaceholderConfigurer.
private int order = Ordered.LOWEST_PRECEDENCE - 1;
private String propertySourceName;
private String addBeforeSourceName;
private ApplicationContext applicationContext;
private MutablePropertySources getPropertySources() {
final Environment env = applicationContext.getEnvironment();
if (!(env instanceof ConfigurableEnvironment)) {
throw new IllegalStateException(
"Cannot get environment for Spring application context");
}
return ((ConfigurableEnvironment) env).getPropertySources();
}
public int getOrder() {
return order;
}
public void setOrder(int order) {
this.order = order;
}
public String getPropertySourceName() {
return propertySourceName;
}
public void setPropertySourceName(String propertySourceName) {
this.propertySourceName = propertySourceName;
}
public String getAddBeforeSourceName() {
return addBeforeSourceName;
}
public void setAddBeforeSourceName(String addBeforeSourceName) {
this.addBeforeSourceName = addBeforeSourceName;
}
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
/**
* Subclasses can override this method to perform adjustments on the
* properties after they are read.
* <p>
* This should be done by getting, adding, removing, and updating properties
* as needed.
*
* #param props
* properties to adjust
*/
protected void convertProperties(Properties props) {
// Override in subclass to perform conversions.
}
/**
* Creates a property source from the specified locations.
*
* #return PropertiesPropertySource instance containing the read properties
* #throws IOException
* if properties cannot be read
*/
protected PropertySource<?> createPropertySource() throws IOException {
if (propertySourceName == null) {
throw new IllegalStateException("No property source name specified");
}
// Load the properties file (or files) from specified locations.
final Properties props = new Properties();
loadProperties(props);
// Convert properties as required.
convertProperties(props);
// Convert to property source.
final PropertiesPropertySource source = new PropertiesPropertySource(
propertySourceName, props);
return source;
}
#Override
public void postProcessBeanFactory(
ConfigurableListableBeanFactory beanFactory) throws BeansException {
try {
// Create the property source, and get its desired position in
// the list of sources.
if (logger.isDebugEnabled()) {
logger.debug("Creating property source [" + propertySourceName
+ "]");
}
final PropertySource<?> source = createPropertySource();
// Register the property source.
final MutablePropertySources sources = getPropertySources();
if (addBeforeSourceName != null) {
if (sources.contains(addBeforeSourceName)) {
if (logger.isDebugEnabled()) {
logger.debug("Adding property source ["
+ propertySourceName + "] before ["
+ addBeforeSourceName + "]");
}
sources.addBefore(addBeforeSourceName, source);
} else {
logger.warn("Property source [" + propertySourceName
+ "] cannot be added before non-existent source ["
+ addBeforeSourceName + "] - adding at the end");
sources.addLast(source);
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("Adding property source ["
+ propertySourceName + "] at the end");
}
sources.addLast(source);
}
} catch (Exception e) {
throw new BeanInitializationException(
"Failed to register property source", e);
}
}
}
Of note here is that the default order of this property source factory class is higher precedence than the default order of PropertySourcesPlaceholderConfigurer.
Also, the registration of the property source happens in postProcessBeanFactory, which means that it will execute in the correct order relative to the PropertySourcesPlaceholderConfigurer. I discovered the hard way that InitializingBean and afterPropertiesSet do not respect the order parameter, and I gave up on that approach as being wrong and redundant.
Finally, because this is a BeanFactoryPostProcessor, it is a bad idea to try to wire much in the way of dependencies. Therefore, the class accesses the environment directly through the application context, which it obtains using ApplicationContextAware.
In my case, I needed the property source to decrypt password properties, which I implemented using the following subclass:
package example;
import java.util.Properties;
/**
* This is a property source factory that creates a property source that can
* process properties for substituting into a Spring configuration.
* <p>
* The only thing that distinguishes this from a normal Spring property source
* is that it decrypts encrypted passwords.
*
* #author rjsmith2
*
*/
public class PasswordPropertySourceFactory extends
AbstractPropertySourceFactory {
private static final PasswordHelper passwordHelper = new PasswordHelper();
private String[] passwordProperties;
public String[] getPasswordProperties() {
return passwordProperties;
}
public void setPasswordProperties(String[] passwordProperties) {
this.passwordProperties = passwordProperties;
}
public void setPasswordProperty(String passwordProperty) {
this.passwordProperties = new String[] { passwordProperty };
}
#Override
protected void convertProperties(Properties props) {
// Adjust password fields by decrypting them.
if (passwordProperties != null) {
for (String propName : passwordProperties) {
final String propValue = props.getProperty(propName);
if (propValue != null) {
final String plaintext = passwordHelper
.decryptString(propValue);
props.setProperty(propName, plaintext);
}
}
}
}
}
Finally, I specifed the property source factory in my Spring configuration:
<!-- Enable property resolution via PropertySourcesPlaceholderConfigurer.
The order has to be larger than the ones used by custom property sources
so that those property sources are registered before any placeholders
are substituted. -->
<context:property-placeholder order="1000" ignore-unresolvable="true" />
<!-- Register a custom property source that reads DB properties, and
decrypts the database password. -->
<bean class="example.PasswordPropertySourceFactory">
<property name="propertySourceName" value="DBPropertySource" />
<property name="location" value="classpath:db.properties" />
<property name="passwordProperty" value="db.password" />
<property name="ignoreResourceNotFound" value="true" />
<!-- Order must be lower than on property-placeholder element. -->
<property name="order" value="100" />
</bean>
To be honest, with the defaults for order in PropertySourcesPlaceholderConfigurer and AbstractPropertySourceFactory, it is probably not even necessary to specify order in the Spring configuration.
Nonetheless, this works, and it does not require any fiddling with the application context initialization.