View Javadoc
1   /*
2    * Copyright (C) 2007-2012 Argeo GmbH
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.argeo.slc.core.execution;
17  
18  import java.beans.PropertyDescriptor;
19  import java.util.HashSet;
20  import java.util.Iterator;
21  import java.util.List;
22  import java.util.Map;
23  import java.util.Set;
24  
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  import org.argeo.slc.SlcException;
28  import org.argeo.slc.execution.ExecutionContext;
29  import org.argeo.slc.execution.ExecutionFlow;
30  import org.springframework.beans.BeansException;
31  import org.springframework.beans.MutablePropertyValues;
32  import org.springframework.beans.PropertyValue;
33  import org.springframework.beans.PropertyValues;
34  import org.springframework.beans.factory.BeanDefinitionStoreException;
35  import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
36  import org.springframework.beans.factory.config.TypedStringValue;
37  import org.springframework.beans.factory.support.ManagedList;
38  import org.springframework.beans.factory.support.ManagedMap;
39  import org.springframework.beans.factory.support.ManagedSet;
40  import org.springframework.util.ObjectUtils;
41  import org.springframework.util.StringUtils;
42  
43  /**
44   * Spring post processor which ensures that execution parameters are properly
45   * set. It is used at two levels: first during instantiation for instantiation
46   * parameters which allow to implement templates, then at runtime in order to
47   * interpret @{} placeholders when object of scope execution are instantiated.
48   */
49  public class ExecutionParameterPostProcessor extends
50  		InstantiationAwareBeanPostProcessorAdapter {
51  
52  	private final static Log log = LogFactory
53  			.getLog(ExecutionParameterPostProcessor.class);
54  
55  	private ExecutionContext executionContext;
56  	private InstantiationManager instantiationManager;
57  
58  	private String placeholderPrefix = "@{";
59  	private String placeholderSuffix = "}";
60  	private String nullValue;
61  
62  	@Override
63  	public PropertyValues postProcessPropertyValues(PropertyValues pvs,
64  			PropertyDescriptor[] pds, Object bean, String beanName)
65  			throws BeansException {
66  
67  		// TODO: resolve at execution only if scope is execution
68  		// TODO: deal with placeholders in RuntimeBeanReference and
69  		// RuntimeBeanNameReference
70  
71  		MutablePropertyValues newPvs = new MutablePropertyValues();
72  
73  		boolean changesOccured = false;
74  
75  		for (PropertyValue pv : pvs.getPropertyValues()) {
76  			Object convertedValue = resolveValue(beanName, bean, pv.getValue());
77  			newPvs.addPropertyValue(new PropertyValue(pv, convertedValue));
78  			if (convertedValue != pv.getValue()) {
79  				changesOccured = true;
80  			}
81  		}
82  
83  		return changesOccured ? newPvs : pvs;
84  	}
85  
86  	@Override
87  	public boolean postProcessAfterInstantiation(Object bean, String beanName)
88  			throws BeansException {
89  		if (bean instanceof ExecutionFlow)
90  			instantiationManager.flowInitializationStarted(
91  					(ExecutionFlow) bean, beanName);
92  		return true;
93  	}
94  
95  	@Override
96  	public Object postProcessAfterInitialization(Object bean, String beanName)
97  			throws BeansException {
98  		if (bean instanceof ExecutionFlow)
99  			instantiationManager.flowInitializationFinished(
100 					(ExecutionFlow) bean, beanName);
101 		return bean;
102 	}
103 
104 	protected String resolvePlaceholder(Object bean, String placeholder) {
105 		if (instantiationManager.isInFlowInitialization())
106 			return instantiationManager.getInitializingFlowParameter(
107 					placeholder).toString();
108 
109 		else {// execution
110 				// next call fail if no execution context available
111 			Object obj = executionContext.getVariable(placeholder);
112 			if (obj != null) {
113 				return obj.toString();
114 			}
115 		}
116 
117 		return null;
118 	}
119 
120 	public Object resolveValue(String beanName, Object bean, Object value) {
121 		if (value instanceof TypedStringValue) {
122 			TypedStringValue tsv = (TypedStringValue) value;
123 			String originalValue = tsv.getValue();
124 
125 			String convertedValue = resolveString(beanName, bean, originalValue);
126 			if (convertedValue == null)
127 				return null;
128 			return convertedValue.equals(originalValue) ? value
129 					: new TypedStringValue(convertedValue);
130 		} else if (value instanceof String) {
131 			String originalValue = value.toString();
132 			String convertedValue = resolveString(beanName, bean, originalValue);
133 			if (convertedValue == null)
134 				return null;
135 			return convertedValue.equals(originalValue) ? value
136 					: convertedValue;
137 		} else if (value instanceof ManagedMap) {
138 			Map<?, ?> mapVal = (Map<?, ?>) value;
139 
140 			Map<Object, Object> newContent = new ManagedMap<Object, Object>();
141 			boolean entriesModified = false;
142 			for (Iterator<?> it = mapVal.entrySet().iterator(); it.hasNext();) {
143 				Map.Entry<?, ?> entry = (Map.Entry<?, ?>) it.next();
144 				Object key = entry.getKey();
145 				int keyHash = (key != null ? key.hashCode() : 0);
146 				Object newKey = resolveValue(beanName, bean, key);
147 				int newKeyHash = (newKey != null ? newKey.hashCode() : 0);
148 				Object val = entry.getValue();
149 				Object newVal = resolveValue(beanName, bean, val);
150 				newContent.put(newKey, newVal);
151 				entriesModified = entriesModified
152 						|| (newVal != val || newKey != key || newKeyHash != keyHash);
153 			}
154 
155 			return entriesModified ? newContent : value;
156 		} else if (value instanceof ManagedList) {
157 			List<?> listVal = (List<?>) value;
158 			List<Object> newContent = new ManagedList<Object>();
159 			boolean valueModified = false;
160 
161 			for (int i = 0; i < listVal.size(); i++) {
162 				Object elem = listVal.get(i);
163 				Object newVal = resolveValue(beanName, bean, elem);
164 				newContent.add(newVal);
165 				if (!ObjectUtils.nullSafeEquals(newVal, elem)) {
166 					valueModified = true;
167 				}
168 			}
169 			return valueModified ? newContent : value;
170 		} else if (value instanceof ManagedSet) {
171 			Set<?> setVal = (Set<?>) value;
172 			Set<Object> newContent = new ManagedSet<Object>();
173 			boolean entriesModified = false;
174 			for (Iterator<?> it = setVal.iterator(); it.hasNext();) {
175 				Object elem = it.next();
176 				int elemHash = (elem != null ? elem.hashCode() : 0);
177 				Object newVal = resolveValue(beanName, bean, elem);
178 				int newValHash = (newVal != null ? newVal.hashCode() : 0);
179 				newContent.add(newVal);
180 				entriesModified = entriesModified
181 						|| (newVal != elem || newValHash != elemHash);
182 			}
183 			return entriesModified ? newContent : value;
184 		} else {
185 			// log.debug(beanName + ": " + value.getClass() + " : " + value);
186 			return value;
187 		}
188 
189 	}
190 
191 	private String resolveString(String beanName, Object bean, String strVal) {
192 		// in case <null/> is passed
193 		if (strVal == null)
194 			return null;
195 
196 		String value = parseStringValue(bean, strVal, new HashSet<String>());
197 
198 		if (value == null)
199 			throw new SlcException("Could not resolve placeholder '" + strVal
200 					+ "' in bean '" + beanName + "'");
201 
202 		return (value.equals(nullValue) ? null : value);
203 	}
204 
205 	public void setPlaceholderPrefix(String placeholderPrefix) {
206 		this.placeholderPrefix = placeholderPrefix;
207 	}
208 
209 	public void setPlaceholderSuffix(String placeholderSuffix) {
210 		this.placeholderSuffix = placeholderSuffix;
211 	}
212 
213 	public void setNullValue(String nullValue) {
214 		this.nullValue = nullValue;
215 	}
216 
217 	public void setInstantiationManager(
218 			InstantiationManager instantiationManager) {
219 		this.instantiationManager = instantiationManager;
220 	}
221 
222 	public void setExecutionContext(ExecutionContext executionContext) {
223 		this.executionContext = executionContext;
224 	}
225 
226 	//
227 	// Following methods hacked from the internals of
228 	// PropertyPlaceholderConfigurer
229 	//
230 
231 	protected String parseStringValue(Object bean, String strVal,
232 			Set<String> visitedPlaceholders)
233 			throws BeanDefinitionStoreException {
234 
235 		// in case <null/> is passed
236 		if (strVal == null)
237 			return null;
238 
239 		StringBuffer buf = new StringBuffer(strVal);
240 
241 		int startIndex = strVal.indexOf(placeholderPrefix);
242 		while (startIndex != -1) {
243 			int endIndex = findPlaceholderEndIndex(buf, startIndex);
244 			if (endIndex != -1) {
245 				String placeholder = buf.substring(startIndex
246 						+ placeholderPrefix.length(), endIndex);
247 				if (!visitedPlaceholders.add(placeholder)) {
248 					throw new BeanDefinitionStoreException(
249 							"Circular placeholder reference '" + placeholder
250 									+ "' in property definitions");
251 				}
252 				// Recursive invocation, parsing placeholders contained in
253 				// the placeholder key.
254 				placeholder = parseStringValue(bean, placeholder,
255 						visitedPlaceholders);
256 				// Now obtain the value for the fully resolved key...
257 				String propVal = resolvePlaceholder(bean, placeholder);
258 				if (propVal != null) {
259 					// Recursive invocation, parsing placeholders contained
260 					// in the
261 					// previously resolved placeholder value.
262 					propVal = parseStringValue(bean, propVal,
263 							visitedPlaceholders);
264 					buf.replace(startIndex,
265 							endIndex + placeholderSuffix.length(), propVal);
266 					if (log.isTraceEnabled()) {
267 						log.trace("Resolved placeholder '" + placeholder + "'");
268 					}
269 					startIndex = buf.indexOf(placeholderPrefix, startIndex
270 							+ propVal.length());
271 				} else {
272 					throw new BeanDefinitionStoreException(
273 							"Could not resolve placeholder '" + placeholder
274 									+ "'");
275 				}
276 				visitedPlaceholders.remove(placeholder);
277 			} else {
278 				startIndex = -1;
279 			}
280 		}
281 
282 		return buf.toString();
283 	}
284 
285 	private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
286 		int index = startIndex + placeholderPrefix.length();
287 		int withinNestedPlaceholder = 0;
288 		while (index < buf.length()) {
289 			if (StringUtils.substringMatch(buf, index, placeholderSuffix)) {
290 				if (withinNestedPlaceholder > 0) {
291 					withinNestedPlaceholder--;
292 					index = index + placeholderSuffix.length();
293 				} else {
294 					return index;
295 				}
296 			} else if (StringUtils
297 					.substringMatch(buf, index, placeholderPrefix)) {
298 				withinNestedPlaceholder++;
299 				index = index + placeholderPrefix.length();
300 			} else {
301 				index++;
302 			}
303 		}
304 		return -1;
305 	}
306 
307 }