View Javadoc
1   /*
2    * #%L
3    * Nuiton Validator
4    * %%
5    * Copyright (C) 2013 - 2014 Code Lutin, Tony Chemit
6    * %%
7    * This program is free software: you can redistribute it and/or modify
8    * it under the terms of the GNU Lesser General Public License as 
9    * published by the Free Software Foundation, either version 3 of the 
10   * License, or (at your option) any later version.
11   * 
12   * This program is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   * GNU General Lesser Public License for more details.
16   * 
17   * You should have received a copy of the GNU General Lesser Public 
18   * License along with this program.  If not, see
19   * <http://www.gnu.org/licenses/lgpl-3.0.html>.
20   * #L%
21   */
22  package org.nuiton.validator.xwork2;
23  
24  import com.google.common.collect.Maps;
25  import com.opensymphony.xwork2.ActionContext;
26  import com.opensymphony.xwork2.ActionProxy;
27  import com.opensymphony.xwork2.ActionProxyFactory;
28  import com.opensymphony.xwork2.DefaultActionInvocation;
29  import com.opensymphony.xwork2.ObjectFactory;
30  import com.opensymphony.xwork2.Result;
31  import com.opensymphony.xwork2.UnknownHandler;
32  import com.opensymphony.xwork2.config.Configuration;
33  import com.opensymphony.xwork2.config.ConfigurationManager;
34  import com.opensymphony.xwork2.config.entities.ActionConfig;
35  import com.opensymphony.xwork2.inject.Container;
36  import com.opensymphony.xwork2.inject.Inject;
37  import com.opensymphony.xwork2.util.ValueStack;
38  import com.opensymphony.xwork2.util.ValueStackFactory;
39  import com.opensymphony.xwork2.validator.ActionValidatorManager;
40  import com.opensymphony.xwork2.validator.FieldValidator;
41  import com.opensymphony.xwork2.validator.Validator;
42  import org.apache.commons.logging.Log;
43  import org.apache.commons.logging.LogFactory;
44  import org.nuiton.util.beans.BeanUtil;
45  import org.nuiton.validator.NuitonValidatorScope;
46  
47  import java.util.HashSet;
48  import java.util.Map;
49  import java.util.Set;
50  import java.util.TreeMap;
51  
52  /**
53   * Usefull methods to works with work2 validator api.
54   *
55   * @author Tony Chemit - chemit@codelutin.com
56   * @since 2.0
57   */
58  public class XWork2ValidatorUtil {
59  
60      /** Logger. */
61      private static final Log log = LogFactory.getLog(XWork2ValidatorUtil.class);
62  
63      /**
64       * A shared value stack to allow external operations on it (for example add
65       * some datas in stack to be used by validators.
66       */
67      static private ValueStack sharedValueStack;
68  
69      public static ValueStack getSharedValueStack() {
70          if (sharedValueStack == null) {
71  
72              // init context
73              sharedValueStack = createValuestack();
74              if (log.isDebugEnabled()) {
75                  log.debug("init shared value stack " + sharedValueStack);
76              }
77          }
78          return sharedValueStack;
79      }
80  
81      /**
82       * Sets the given value stack as shared (can be null).
83       *
84       * @param sharedValueStack the new shared value stack to use (can be null).
85       * @since 3.0
86       */
87      public static void setSharedValueStack(ValueStack sharedValueStack) {
88          if (log.isDebugEnabled()) {
89              log.debug("set shared value stack " + sharedValueStack);
90          }
91          XWork2ValidatorUtil.sharedValueStack = sharedValueStack;
92      }
93  
94      public static ValueStack createValuestack() {
95  
96          ValueStack result;
97  
98          ActionContext context = ActionContext.getContext();
99          if (context == null) {
100 
101             // no action context, create a value stack from scratch
102             ConfigurationManager confManager = new ConfigurationManager("xwork");
103             Configuration conf = confManager.getConfiguration();
104             Container container = conf.getContainer();
105             ValueStackFactory stackFactory = container.getInstance(ValueStackFactory.class);
106             result = stackFactory.createValueStack();
107         } else {
108 
109             // there is an action context, try to use his value stack
110             result = context.getValueStack();
111 
112             if (result == null) {
113 
114                 // no value stack, create then a new one
115                 ValueStackFactory stackFactory = context.getInstance(ValueStackFactory.class);
116                 result = stackFactory.createValueStack();
117             }
118         }
119         return result;
120     }
121 
122     public static <O> XWork2ScopeValidator<O> newXWorkScopeValidator(Class<O> beanClass, String contextName,
123                                                                      Set<String> fields) {
124         return newXWorkScopeValidator(beanClass, contextName, fields, getSharedValueStack());
125     }
126 
127     public static <O> XWork2ScopeValidator<O> newXWorkScopeValidator(Class<O> beanClass, String contextName,
128                                                                      Set<String> fields, ValueStack vs) {
129 
130         return new XWork2ScopeValidator<O>(beanClass, contextName, fields, vs);
131     }
132 
133 
134     public static String getContextForScope(String context, NuitonValidatorScope scope) {
135         return (context == null ? "" : context + "-") + scope.name().toLowerCase();
136     }
137 
138     protected static ActionValidatorManager newValidationManager(ValueStack vs) {
139 
140         if (vs == null) {
141             vs = createValuestack();
142 
143             if (log.isDebugEnabled()) {
144                 log.debug("create a standalone value stack " + vs);
145             }
146         } else {
147             if (log.isDebugEnabled()) {
148                 log.debug("use given value stack " + vs);
149             }
150         }
151 
152         ActionContext context = ActionContext.getContext();
153 
154         if (context == null) {
155             context = new ActionContext(vs.getContext());
156 
157             // must set the action context otherwise can't obtain after validators
158             // with the method validator.getValidators(XXX)
159             // Later in the code, we could not having reference to the ValueStack
160             // before using a validator... Must be cleaned...
161             ActionContext.setContext(context);
162 
163         }
164         Container container = context.getContainer();
165 
166         if (context.getActionInvocation() == null) {
167 
168             // We need sometimes a ActionInvocation (see http://nuiton.org/issues/2837)
169 
170             // ----
171             // tchemit-2015-01-06 Don't need this anymore (see http://forge.nuiton.org/issues/3612)
172 
173 //            Configuration configuration = container.getInstance(Configuration.class);
174 //
175 //            UnknownHandlerConfig unknownHandlerStack = new UnknownHandlerConfig("nuiton");
176 //            List<UnknownHandlerConfig> unknownHandlerStackList = configuration.getUnknownHandlerStack();
177 //
178 //            if (unknownHandlerStackList == null) {
179 //                unknownHandlerStackList = Lists.newArrayList();
180 //                configuration.setUnknownHandlerStack(unknownHandlerStackList);
181 //            }
182 //            unknownHandlerStackList.add(0, unknownHandlerStack);
183             // tchemit-2015-01-06 Don't need this anymore (see http://forge.nuiton.org/issues/3612)
184             // ----
185 
186             Map<String, Object> extraContext = Maps.newHashMap();
187             extraContext.put(ActionContext.VALUE_STACK, vs);
188             DefaultActionInvocation invocation = new DefaultActionInvocation(extraContext, false);
189             invocation.setObjectFactory(container.getInstance(ObjectFactory.class));
190             invocation.setContainer(container);
191 
192             ActionProxyFactory actionProxyFactory = context.getInstance(ActionProxyFactory.class);
193             ActionProxy actionProxy = actionProxyFactory.createActionProxy(invocation, "java.lang", "java.lang.Object", null, false, false);
194             invocation.init(actionProxy);
195             context.setActionInvocation(invocation);
196 
197         }
198         // init validator
199 
200         ActionValidatorManager validatorManager =
201                 container.getInstance(ActionValidatorManager.class, "no-annotations");
202 
203         //FIXME-tchemit 2012-07-17 work-around to fix http://nuiton.org/issues/2191
204         //FIXME-tchemit 2012-07-17 will remove this when using xworks-core which fixes https://issues.apache.org/jira/browse/WW-3850
205 //        if (validatorManager instanceof DefaultActionValidatorManager) {
206 //
207 //            FileManagerFactory fileManagerFactory = container.getInstance(FileManagerFactory.class);
208 //            fileManagerFactory.setReloadingConfigs(String.valueOf(Boolean.FALSE));
209 //            ((DefaultActionValidatorManager) validatorManager).setFileManagerFactory(fileManagerFactory);
210 //        }
211 
212         return validatorManager;
213     }
214 
215     public static <O> Map<NuitonValidatorScope, String[]> detectFields(Class<O> type, String context,
216                                                                        NuitonValidatorScope[] scopeUniverse) {
217 
218         Set<String> availableFields = BeanUtil.getReadableProperties(type);
219 
220         ActionValidatorManager validatorManager = newValidationManager(getSharedValueStack());
221 
222         Map<NuitonValidatorScope, String[]> fields = new TreeMap<NuitonValidatorScope, String[]>();
223 
224         for (NuitonValidatorScope scope : scopeUniverse) {
225 
226             Set<String> fieldNames =
227                     detectFieldsForScope(validatorManager, type, scope, context, availableFields, false);
228 
229             if (log.isDebugEnabled()) {
230                 log.debug("detected validator fields for scope " + scope +
231                           ":" + context + " : " + fieldNames);
232             }
233 
234             if (!fieldNames.isEmpty()) {
235 
236                 // fields detected in this validator, keep it
237 
238                 fields.put(scope, fieldNames.toArray(new String[fieldNames.size()]));
239             }
240         }
241 
242         return fields;
243 
244     }
245 
246     protected static Set<String> detectFieldsForScope(ActionValidatorManager validator,
247                                                       Class<?> type,
248                                                       NuitonValidatorScope scope,
249                                                       String context,
250                                                       Set<String> availableFields,
251                                                       boolean includeDefaultContext) {
252 
253         String scopeContext = getContextForScope(context, scope);
254 
255         Set<String> fields = new HashSet<String>();
256 
257         int skip = 0;
258         if (scopeContext != null && !includeDefaultContext) {
259             // count the number of validator to skip
260             for (Validator<?> v : validator.getValidators(type, null)) {
261                 // we only work on FieldValidator at the moment
262                 if (v instanceof FieldValidator) {
263                     skip++;
264                 }
265             }
266         }
267 
268         for (Validator<?> v : validator.getValidators(type, scopeContext)) {
269 
270             // we only work on FieldValidator at the moment
271             if (v instanceof FieldValidator) {
272                 if (skip > 0) {
273                     skip--;
274                     continue;
275                 }
276                 FieldValidator fieldValidator = (FieldValidator) v;
277                 if (log.isDebugEnabled()) {
278                     log.debug("context " + context + " - field " +
279                               fieldValidator.getFieldName());
280                 }
281                 String fName = fieldValidator.getFieldName();
282                 if (availableFields.contains(fName)) {
283 
284                     // safe field
285                     fields.add(fName);
286                 } else {
287 
288                     if (BeanUtil.isNestedReadableProperty(type, fName)) {
289 
290                         fields.add(fName);
291 
292                     } else {
293                         // not a readable property, can not add it
294                         String message =
295                                 "Field " + fName + " in scope [" + scopeContext + "] is not a readable property of " +
296                                 type.getName();
297                         if (log.isErrorEnabled()) {
298                             log.error(message);
299                         }
300                         throw new IllegalStateException(message);
301                     }
302                 }
303             }
304         }
305 
306         return fields;
307     }
308 
309     /**
310      * A dummy unknown handler when we want to use for example visitor validators
311      * which need a invocation handler.
312      *
313      * <strong>Note:</strong> Do not use this for any purpose...
314      *
315      * @author Tony Chemit - chemit@codelutin.com
316      * @since 3.0
317      */
318     public static class NuitonDefaultUnknownHandler implements UnknownHandler {
319 
320         protected Configuration configuration;
321 
322         protected ObjectFactory objectFactory;
323 
324         @Inject
325         public void setConfiguration(Configuration configuration) {
326             this.configuration = configuration;
327         }
328 
329         @Inject
330         public void setObjectFactory(ObjectFactory objectFactory) {
331             this.objectFactory = objectFactory;
332         }
333 
334         @Override
335         public ActionConfig handleUnknownAction(String namespace, String actionName) {
336             return new ActionConfig.Builder(namespace, actionName, Object.class.getName()).build();
337         }
338 
339         @Override
340         public Result handleUnknownResult(ActionContext actionContext, String actionName, ActionConfig actionConfig, String resultCode) {
341             return null;
342         }
343 
344         @Override
345         public Object handleUnknownActionMethod(Object action, String methodName) {
346             return null;
347         }
348     }
349 }