001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: * $Header:$
018: */
019: package org.apache.beehive.netui.pageflow;
020:
021: import org.apache.beehive.netui.util.internal.InternalStringBuilder;
022:
023: import java.util.HashMap;
024: import java.util.Iterator;
025: import java.util.Map;
026: import javax.servlet.ServletException;
027: import javax.servlet.jsp.el.VariableResolver;
028: import javax.servlet.http.HttpServletRequest;
029: import javax.servlet.http.HttpServletResponse;
030:
031: import org.apache.beehive.netui.pageflow.internal.AnyBeanActionForm;
032: import org.apache.beehive.netui.pageflow.internal.InternalUtils;
033: import org.apache.beehive.netui.script.Expression;
034: import org.apache.beehive.netui.script.ExpressionEvaluator;
035: import org.apache.beehive.netui.script.ExpressionEvaluatorFactory;
036: import org.apache.beehive.netui.script.ExpressionUpdateException;
037: import org.apache.beehive.netui.script.common.ImplicitObjectUtil;
038: import org.apache.beehive.netui.util.Bundle;
039: import org.apache.beehive.netui.util.logging.Logger;
040:
041: import org.apache.commons.beanutils.BeanUtils;
042: import org.apache.struts.action.ActionForm;
043:
044: /**
045: * Implement the processPopulate stage of the Struts / PageFlow request
046: * processing lifecycle. The {@link #populate(HttpServletRequest, HttpServletResponse, ActionForm, boolean)} method is
047: * invoked in order to take request parameters from the {@link HttpServletRequest}
048: * use the key / value pairs from the request to perform an update to the underlying
049: * JavaBean objects.
050: * <br/>
051: * <br/>
052: * Updates are performed on a key / value pair if the key is an expression; otherwise,
053: * the updates are delegated to the Struts processPopulate infrastructure.
054: *
055: */
056: public class ProcessPopulate {
057: /**
058: * This defines the name of the parameter that will contain the NetUI ID map..
059: */
060: public static final String IDMAP_PARAMETER_NAME = "netuiIdMap";
061:
062: private static final Logger LOG = Logger
063: .getInstance(ProcessPopulate.class);
064:
065: private static final String TAG_HANDLER_PREFIX = "wlw-";
066: private static final String TAG_HANDLER_SUFFIX = ":";
067: private static final Map HANDLER_MAP = new HashMap();
068:
069: // these must be kept in sync with the context names specified in the scripting languages
070: private static final String PAGE_FLOW_CONTEXT = "pageFlow";
071: private static final String GLOBAL_APP_CONTEXT = "globalApp";
072:
073: /**
074: * An inner class that represnts the data that will be used to
075: * perform an update. If a key has a prefix handler, this
076: * node is constructed and passed to the prefix handler
077: * so that the prefix handler can change the expression or
078: * values that will be used to execute the expression update.
079: */
080: public final static class ExpressionUpdateNode {
081:
082: public String expression = null;
083: public String[] values = null;
084:
085: /* Prevent construction outside of this class */
086: private ExpressionUpdateNode() {
087: }
088:
089: public String toString() {
090: InternalStringBuilder buf = new InternalStringBuilder();
091: buf.append("expression: ").append(expression).append("\n");
092: if (values != null)
093: for (int i = 0; i < values.length; i++)
094: buf.append("value[").append(i).append("]: ")
095: .append(values[i]);
096: else
097: buf.append("values are null");
098:
099: return buf.toString();
100: }
101: }
102:
103: /**
104: * Register a {@link org.apache.beehive.netui.pageflow.RequestParameterHandler} that is added to handle a
105: * particular prefix which be present as a prefix to a request parameter
106: * key. For keys that match the prefix, the key / value from the request
107: * are put in an {@link ExpressionUpdateNode} struct and handed to the
108: * {@link org.apache.beehive.netui.pageflow.RequestParameterHandler} for processing. The returned {@link ExpressionUpdateNode}
109: * is used to perform an expression update.
110: *
111: * @param prefix the String prefix that will be appended to request paramters that
112: * should pass through the {@link RequestParameterHandler} before being updated.
113: * @param handler the handler that should handle all request paramters with
114: * the given <code>prefix</code>
115: */
116: public static void registerPrefixHandler(String prefix,
117: RequestParameterHandler handler) {
118: /*
119: This synchronization point should happen very infrequently as this only happens when a prefix handler
120: is added to the set.
121: */
122: synchronized (HANDLER_MAP) {
123: String msg = "Register RequestParameterHandler with\n\tprefix: "
124: + prefix
125: + "\n\thandler: "
126: + (handler != null ? handler.getClass().getName()
127: : null);
128:
129: LOG.info(msg);
130:
131: if (HANDLER_MAP.get(prefix) == null)
132: HANDLER_MAP.put(prefix, handler);
133: }
134: }
135:
136: /**
137: * Write the handler name specified onto the given expression.
138: */
139: public static String writeHandlerName(String handler,
140: String expression) {
141: if (!ExpressionEvaluatorFactory.getInstance().isExpression(
142: expression))
143: throw new IllegalArgumentException(Bundle.getErrorString(
144: "ProcessPopulate_handler_nonAtomicExpression",
145: new Object[] { expression }));
146:
147: if (!HANDLER_MAP.containsKey(handler))
148: throw new IllegalStateException(Bundle.getErrorString(
149: "ProcessPopulate_handler_notRegistered",
150: new Object[] { handler }));
151:
152: InternalStringBuilder buf = new InternalStringBuilder();
153: buf.append(TAG_HANDLER_PREFIX);
154: buf.append(handler);
155: buf.append(TAG_HANDLER_SUFFIX);
156: buf.append(expression);
157:
158: return buf.toString();
159: }
160:
161: /**
162: * Use the request parameters to populate all properties that have expression keys into
163: * the underlying JavaBeans.
164: * Creates a <code>java.util.Map</code> of objects that will be consumed by
165: * Struts processPopulate. This includes all request attributes that
166: * were not expressions
167: *
168: * @param request the current <code>HttpServletRequest</code>
169: * @param form if this request references an action and it has an <code>ActionForm</code>
170: * associated with it, then the <code>form</code> parameter is non-null.
171: * @throws ServletException when an error occurs in populating data after
172: * the request; failure here can be caused by failures in creating
173: * or executing update expressions.
174: */
175: public static void populate(HttpServletRequest request,
176: HttpServletResponse response, ActionForm form,
177: boolean requestHasPopulated) throws ServletException {
178: String key = null;
179: Map strutsProperties = null;
180: ExpressionEvaluator ee = ExpressionEvaluatorFactory
181: .getInstance();
182:
183: /* Boolean used to avoid instanceof check below */
184: boolean isMultipart = false;
185:
186: // if this returns null, it's not a mulitpart request
187: Map params = MultipartRequestUtils.handleMultipartRequest(
188: request, form);
189:
190: // make adjustments
191: if (params != null)
192: isMultipart = true;
193: else
194: params = request.getParameterMap();
195:
196: if (params == null) {
197: LOG
198: .warn("An error occurred checking a request for multipart status. No model values were updated.");
199: return;
200: }
201:
202: /* explicitly build a variable resolver that is used to provide objects that may be updated to the expression engine */
203: VariableResolver variableResolver = ImplicitObjectUtil
204: .getUpdateVariableResolver(form, request, response,
205: true);
206:
207: Iterator iterator = params.keySet().iterator();
208: while (iterator.hasNext()) {
209: key = (String) iterator.next();
210: String expr = null;
211:
212: // if there is an expression map, lookup the real expression from the name
213: expr = key;
214: LOG.debug("key: " + key + " value type: "
215: + params.get(key).getClass().getName() + " value: "
216: + params.get(key));
217:
218: try {
219: Object paramsValue = params.get(key);
220: if (ee.containsExpression(expr)) {
221: Object updateValue = null;
222: if (!isMultipart || paramsValue instanceof String[]) {
223: String[] values = (String[]) paramsValue;
224:
225: // the only "contains" case that is accepted
226: if (expr.startsWith(TAG_HANDLER_PREFIX)) {
227: LOG
228: .debug("Found an expression requiring a TAG HANDLER");
229:
230: ExpressionUpdateNode node = doTagHandler(
231: key, expr, values, request);
232:
233: expr = node.expression;
234: values = node.values;
235: }
236:
237: if (values != null && values.length == 1)
238: updateValue = values[0];
239: else
240: updateValue = values;
241: }
242: // handle funky types that Struts returns for a file upload request handler
243: else {
244: updateValue = params.get(key);
245: }
246:
247: try {
248: // trap any bad expressions here
249: if (ee.isExpression(expr)) {
250: // common case, make this fast
251: if (!requestHasPopulated)
252: ee.update(expr, updateValue,
253: variableResolver, true);
254: // must check the expression to make sure pageFlow. and globalApp. don't get executed more than once
255: else {
256: Expression pe = ee
257: .parseExpression(expr);
258: String contextName = pe.getContext();
259: if (!contextName
260: .equals(PAGE_FLOW_CONTEXT)
261: && !contextName
262: .equals(GLOBAL_APP_CONTEXT))
263: ee.update(expr, updateValue,
264: variableResolver, true);
265: }
266: }
267: }
268: // catch any errors, particularly expression parse failures
269: catch (ExpressionUpdateException e) {
270: String s = Bundle.getString("ExprUpdateError",
271: new Object[] { expr, e });
272: LOG.error(s);
273:
274: // add binding errors via PageFlowUtils
275: InternalUtils.addBindingUpdateError(request,
276: expr, s, e);
277: }
278: } else {
279: LOG
280: .debug("HTTP request parameter key \""
281: + key
282: + "\" is not an expression, handle with Struts");
283:
284: if (strutsProperties == null)
285: strutsProperties = new HashMap();
286:
287: strutsProperties.put(key, paramsValue);
288: }
289: }
290: // catch any unexpected exception
291: catch (Exception e) {
292: String s = Bundle.getString(
293: "ProcessPopulate_exprUpdateError",
294: new Object[] { expr, e });
295:
296: LOG.warn(s, e);
297:
298: // add binding errors via PageFlowUtils
299: InternalUtils
300: .addBindingUpdateError(request, expr, s, e);
301: }
302: }
303:
304: handleStrutsProperties(strutsProperties, form);
305: }
306:
307: /**
308: * Execute a NetUI prefix handler. This method is called when a prefix is encountered in the request that
309: * has a "handler" prefix key. This allows some handler to modify the request's name / value pairs
310: * before they are passed to the expression language.
311: *
312: * @param key the request key that is being processed
313: * @param request this request's {@link javax.servlet.ServletRequest}
314: */
315: private static ExpressionUpdateNode doTagHandler(String key,
316: String expression, String[] values,
317: HttpServletRequest request) {
318:
319: LOG.debug("Found prefixed tag; handlerName: "
320: + key.substring(TAG_HANDLER_PREFIX.length(), key
321: .indexOf(TAG_HANDLER_SUFFIX)));
322:
323: String handlerName = expression.substring(TAG_HANDLER_PREFIX
324: .length(), expression.indexOf(TAG_HANDLER_SUFFIX));
325:
326: // Execute callback to a parameter handler.
327: RequestParameterHandler handler = (RequestParameterHandler) HANDLER_MAP
328: .get(handlerName);
329:
330: if (handler != null) {
331: expression = expression.substring(expression
332: .indexOf(TAG_HANDLER_SUFFIX) + 1);
333:
334: LOG.debug("found handler for prefix \""
335: + handlerName
336: + "\" type: "
337: + (handler != null ? handler.getClass().getName()
338: : null) + "\n\t" + "key: \"" + key
339: + "\" expr: \"" + expression + "\"");
340:
341: ExpressionUpdateNode node = new ExpressionUpdateNode();
342: node.expression = expression;
343: node.values = values;
344:
345: /*
346: Call the haneler to process the request's parameter
347: */
348: handler.process(request, key, expression, node);
349:
350: return node;
351: } else
352: throw new IllegalStateException(
353: "Request parameter references a tag handler prefix \""
354: + handlerName
355: + "\" that is not registered for expression \""
356: + key + "\"");
357: }
358:
359: /**
360: * This code originated from the Struts class
361: * {@link org.apache.struts.util.RequestUtils#populate(Object, javax.servlet.http.HttpServletRequest)}
362: * and just defers to Struts for Struts's usual request parameter population.
363: *
364: * @param strutsProperties a Map of properties that should be applied to the form using Strus's algorithm
365: * for doing so
366: * @param form the form to which properties should be applied
367: */
368: private static void handleStrutsProperties(Map strutsProperties,
369: ActionForm form) {
370: if (strutsProperties != null) {
371:
372: LOG.debug("Handle Struts request parameters.");
373: Object bean = form;
374: if (form instanceof AnyBeanActionForm) {
375: bean = ((AnyBeanActionForm) form).getBean();
376: }
377:
378: /* defer as Struts does to BeanUtils for non-NetUI expression keys */
379: try {
380: BeanUtils.populate(bean, strutsProperties);
381: } catch (Exception e) {
382: throw new RuntimeException(
383: "Exception processing bean and request parameters: ",
384: e);
385: }
386: }
387: }
388: }
|