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 org.apache.commons.logging.Log;
25  import org.apache.commons.logging.LogFactory;
26  import org.nuiton.util.StringUtil;
27  import org.nuiton.validator.AbstractNuitonValidatorProvider;
28  import org.nuiton.validator.NuitonValidator;
29  import org.nuiton.validator.NuitonValidatorModel;
30  import org.nuiton.validator.NuitonValidatorScope;
31  
32  import java.io.File;
33  import java.io.FilenameFilter;
34  import java.util.ArrayList;
35  import java.util.Arrays;
36  import java.util.Comparator;
37  import java.util.EnumSet;
38  import java.util.List;
39  import java.util.Map;
40  import java.util.Set;
41  import java.util.SortedSet;
42  import java.util.TreeSet;
43  import java.util.regex.Matcher;
44  import java.util.regex.Pattern;
45  
46  /**
47   * Provider of validator for the xworks nuiton validator.
48   *
49   * @author Tony Chemit - chemit@codelutin.com
50   * @since 2.0
51   */
52  public class XWork2NuitonValidatorProvider extends AbstractNuitonValidatorProvider {
53  
54      public static final String PROVIDER_NAME = "xwork2";
55  
56      /** Logger. */
57      private static final Log log =
58              LogFactory.getLog(XWork2NuitonValidatorProvider.class);
59  
60      public XWork2NuitonValidatorProvider() {
61          super(PROVIDER_NAME);
62      }
63  
64      @Override
65      public <O> NuitonValidatorModel<O> newModel(Class<O> type,
66                                                  String context,
67                                                  NuitonValidatorScope... scopes) {
68  
69          if (scopes.length == 0) {
70              // use all scopes
71              scopes = NuitonValidatorScope.values();
72          }
73  
74          Map<NuitonValidatorScope, String[]> fields =
75                  XWork2ValidatorUtil.detectFields(type, context, scopes);
76  
77          Set<NuitonValidatorScope> scopeSet =
78                  EnumSet.noneOf(NuitonValidatorScope.class);
79          scopeSet.addAll(Arrays.asList(scopes));
80  
81          return new NuitonValidatorModel<O>(type, context, scopeSet, fields);
82      }
83  
84      @Override
85      public <O> XWork2NuitonValidator<O> newValidator(NuitonValidatorModel<O> model) {
86          return new XWork2NuitonValidator<O>(model);
87      }
88  
89      @Override
90      public SortedSet<NuitonValidator<?>> detectValidators(File sourceRoot,
91                                                            Pattern contextFilter,
92                                                            NuitonValidatorScope[] scopes,
93                                                            Class<?>... types) {
94  
95          if (scopes == null) {
96  
97              // use all scopes
98              scopes = NuitonValidatorScope.values();
99          }
100 
101         SortedSet<NuitonValidator<?>> result =
102                 new TreeSet<NuitonValidator<?>>(new ValidatorComparator());
103 
104         for (Class<?> c : types) {
105             File dir = getClassDir(sourceRoot, c);
106             if (!dir.exists()) {
107 
108                 // pas de repertoire adequate
109                 if (log.isDebugEnabled()) {
110                     log.debug("skip none existing directory " + dir);
111                 }
112                 continue;
113             }
114             String[] contexts = getContexts(c, dir);
115             if (log.isDebugEnabled()) {
116                 log.debug("contexts : " + Arrays.toString(contexts));
117             }
118 
119             if (contexts.length > 0) {
120                 String[] realContexts = getContextsWithoutScopes(contexts);
121 
122                 if (log.isDebugEnabled()) {
123                     log.debug("realContexts : " +
124                               Arrays.toString(realContexts));
125                 }
126 
127                 if (contextFilter != null) {
128 
129                     // filter contexts
130                     realContexts = getFilterContexts(contextFilter,
131                                                      realContexts
132                     );
133                     if (log.isDebugEnabled()) {
134                         log.debug("filterContexts : " +
135                                   Arrays.toString(realContexts));
136                     }
137                 }
138 
139                 for (String context : realContexts) {
140 
141                     // on cherche le validateur
142                     NuitonValidator<?> validator = getValidator(
143                             c,
144                             context.isEmpty() ? null : context,
145                             scopes
146                     );
147                     if (validator != null) {
148                         // on enregistre le validateur
149                         result.add(validator);
150                     }
151                 }
152             }
153         }
154         return result;
155     }
156 
157     /**
158      * Pour un context et un type d'entité donné, instancie un validateur et
159      * test si ce validateur est utilisable (i.e qu'il admet des champs à
160      * valider).
161      *
162      * Si aucun champ n'est trouvé dans le validateur, alors on retourne null.
163      *
164      * @param <O>     le type du bean
165      * @param klass   le type du bean
166      * @param context le context du validateur
167      * @param scopes  les scopes a utiliser (si {@code null} alors pas de
168      *                filtre sur les scopes)
169      * @return le validateur initialisé, ou <code>null</code> si aucun scope
170      * détecté dans le validateur.
171      */
172     protected <O> NuitonValidator<O> getValidator(Class<O> klass,
173                                                   String context,
174                                                   NuitonValidatorScope... scopes) {
175 
176         NuitonValidatorModel<O> model = newModel(klass, context, scopes);
177 
178         XWork2NuitonValidator<O> valitator = newValidator(model);
179 
180         Set<NuitonValidatorScope> realScopes = valitator.getEffectiveScopes();
181         if (realScopes.isEmpty()) {
182             valitator = null;
183             if (log.isDebugEnabled()) {
184                 log.debug(klass + " : validator skip (no scopes detected)");
185             }
186         } else {
187             if (log.isDebugEnabled()) {
188                 log.debug(klass + " : keep validator " + valitator);
189             }
190         }
191         return valitator;
192     }
193 
194     protected File getClassDir(File sourceRoot, Class<?> clazz) {
195         String path = clazz.getPackage().getName();
196         path = path.replaceAll("\\.", StringUtil.getFileSeparatorRegex());
197         File dir = new File(sourceRoot, path);
198         return dir;
199     }
200 
201     protected String[] getContexts(Class<?> clazz, File dir) {
202         Set<String> result = new TreeSet<String>();
203         ValidatorFilenameFilter filter = new ValidatorFilenameFilter(clazz);
204         if (log.isDebugEnabled()) {
205             log.debug("dir : " + dir);
206         }
207         String[] files = dir.list(filter);
208         for (String file : files) {
209             if (log.isDebugEnabled()) {
210                 log.debug("file " + file);
211             }
212             String context = file.substring(
213                     filter.prefix.length(),
214                     file.length() - ValidatorFilenameFilter.SUFFIX.length()
215             );
216             if (log.isDebugEnabled()) {
217                 log.debug("detect " + clazz.getSimpleName() +
218                           " context [" + context + "]");
219             }
220             result.add(context);
221         }
222         return result.toArray(new String[result.size()]);
223     }
224 
225     protected String[] getContextsWithoutScopes(String[] contexts) {
226         Set<String> result = new TreeSet<String>();
227         NuitonValidatorScope[] scopes = NuitonValidatorScope.values();
228         for (String context : contexts) {
229             for (NuitonValidatorScope scope : scopes) {
230                 String scopeName = scope.name().toLowerCase();
231                 if (!context.endsWith(scopeName)) {
232                     // pas concerne par ce scope
233                     continue;
234                 }
235                 if (log.isDebugEnabled()) {
236                     log.debug("detect context : " + context);
237                 }
238                 String realContext = context.substring(
239                         0,
240                         context.length() - scopeName.length()
241                 );
242                 if (realContext.endsWith("-")) {
243                     realContext = realContext.substring(
244                             0,
245                             realContext.length() - 1
246                     );
247                 }
248                 result.add(realContext);
249             }
250         }
251         return result.toArray(new String[result.size()]);
252     }
253 
254     protected String[] getFilterContexts(Pattern contextFilter,
255                                          String[] realContexts) {
256         List<String> result = new ArrayList<String>();
257         for (String c : realContexts) {
258             Matcher m = contextFilter.matcher(c);
259             if (m.matches()) {
260                 result.add(c);
261             }
262         }
263         return result.toArray(new String[result.size()]);
264     }
265 
266     protected static class ValidatorFilenameFilter implements FilenameFilter {
267 
268         protected static final String SUFFIX = "-validation.xml";
269 
270         protected Class<?> clazz;
271 
272         protected String prefix;
273 
274         public ValidatorFilenameFilter(Class<?> clazz) {
275             this.clazz = clazz;
276             prefix = clazz.getSimpleName() + "-";
277         }
278 
279         @Override
280         public boolean accept(File dir, String name) {
281             boolean result = name.endsWith(SUFFIX);
282             if (result) {
283                 result = name.startsWith(prefix);
284             }
285             return result;
286         }
287     }
288 
289     protected static class ValidatorComparator implements Comparator<NuitonValidator<?>> {
290 
291         @Override
292         public int compare(NuitonValidator<?> o1, NuitonValidator<?> o2) {
293 
294             NuitonValidatorModel<?> model1 = ((XWork2NuitonValidator<?>) o1).getModel();
295             NuitonValidatorModel<?> model2 = ((XWork2NuitonValidator<?>) o2).getModel();
296 
297             String contextName1 =
298                     model1.getType().getSimpleName() + "-" +
299                     (model1.getContext() == null ? "" : model1.getContext());
300             String contextName2 =
301                     model2.getType().getSimpleName() + "-" +
302                     (model2.getContext() == null ? "" : model2.getContext());
303             return contextName1.compareTo(contextName2);
304         }
305     }
306 
307 }