1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  package org.kuali.rice.krad.web.bind;
17  
18  import org.apache.commons.lang.ArrayUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.apache.commons.lang3.reflect.FieldUtils;
21  import org.kuali.rice.krad.data.DataObjectService;
22  import org.kuali.rice.krad.data.DataObjectWrapper;
23  import org.kuali.rice.krad.data.KradDataServiceLocator;
24  import org.kuali.rice.krad.data.util.Link;
25  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
26  import org.kuali.rice.krad.uif.UifConstants;
27  import org.kuali.rice.krad.uif.UifConstants.ViewType;
28  import org.kuali.rice.krad.uif.UifParameters;
29  import org.kuali.rice.krad.uif.service.ViewService;
30  import org.kuali.rice.krad.uif.view.View;
31  import org.kuali.rice.krad.util.KRADUtils;
32  import org.kuali.rice.krad.web.form.UifFormBase;
33  import org.springframework.core.annotation.AnnotationUtils;
34  import org.springframework.core.convert.ConversionService;
35  import org.springframework.util.Assert;
36  import org.springframework.validation.AbstractPropertyBindingResult;
37  import org.springframework.web.bind.ServletRequestDataBinder;
38  
39  import javax.servlet.ServletRequest;
40  import javax.servlet.http.HttpServletRequest;
41  import java.lang.reflect.Field;
42  import java.util.ArrayList;
43  import java.util.Collections;
44  import java.util.HashSet;
45  import java.util.List;
46  import java.util.Map;
47  import java.util.Set;
48  
49  
50  
51  
52  
53  
54  
55  public class UifServletRequestDataBinder extends ServletRequestDataBinder {
56      protected static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(
57              UifServletRequestDataBinder.class);
58  
59      private UifBeanPropertyBindingResult bindingResult;
60      private ConversionService conversionService;
61      private DataObjectService dataObjectService;
62      private boolean changeTracking = false;
63      private boolean autoLinking = true;
64  
65      public UifServletRequestDataBinder(Object target) {
66          super(target);
67          this.changeTracking = determineChangeTracking(target);
68          setBindingErrorProcessor(new UifBindingErrorProcessor());
69      }
70  
71      public UifServletRequestDataBinder(Object target, String name) {
72          super(target, name);
73          this.changeTracking = determineChangeTracking(target);
74          setBindingErrorProcessor(new UifBindingErrorProcessor());
75      }
76  
77      
78  
79  
80      private static boolean determineChangeTracking(Object target) {
81          ChangeTracking changeTracking = AnnotationUtils.findAnnotation(target.getClass(), ChangeTracking.class);
82          if (changeTracking != null && changeTracking.enabled()) {
83              return true;
84          }
85          return false;
86      }
87  
88      
89  
90  
91  
92  
93      @Override
94      public void initBeanPropertyAccess() {
95          Assert.state(this.bindingResult == null,
96                  "DataBinder is already initialized - call initBeanPropertyAccess before other configuration methods");
97  
98          this.bindingResult = new UifBeanPropertyBindingResult(getTarget(), getObjectName(), isAutoGrowNestedPaths(),
99                  getAutoGrowCollectionLimit());
100         this.bindingResult.setChangeTracking(this.changeTracking);
101 
102         if (this.conversionService != null) {
103             this.bindingResult.initConversion(this.conversionService);
104         }
105 
106         if (this.dataObjectService == null) {
107             this.dataObjectService = KradDataServiceLocator.getDataObjectService();
108         }
109     }
110 
111     
112 
113 
114 
115 
116     @Override
117     protected AbstractPropertyBindingResult getInternalBindingResult() {
118         if (this.bindingResult == null) {
119             initBeanPropertyAccess();
120         }
121 
122         return this.bindingResult;
123     }
124 
125     
126 
127 
128 
129 
130     @Override
131     public void initDirectFieldAccess() {
132         LOG.error("Direct Field access is not allowed in UifServletRequestDataBinder.");
133         throw new RuntimeException("Direct Field access is not allowed in Kuali");
134     }
135 
136     
137 
138 
139     private void _bind(ServletRequest request) {
140         super.bind(request);
141     }
142 
143     
144 
145 
146 
147 
148 
149 
150 
151 
152 
153 
154 
155 
156 
157 
158     @Override
159     public void bind(ServletRequest request) {
160         UifFormBase form = (UifFormBase) UifServletRequestDataBinder.this.getTarget();
161 
162         request.setAttribute(UifConstants.REQUEST_FORM, form);
163 
164         form.preBind((HttpServletRequest) request);
165 
166         _bind(request);
167 
168         request.setAttribute(UifConstants.PROPERTY_EDITOR_REGISTRY, this.bindingResult.getPropertyEditorRegistry());
169 
170         executeAutomaticLinking(request, form);
171 
172         if (!form.isUpdateNoneRequest()) {
173             
174             String viewId = (String) request.getAttribute(UifParameters.VIEW_ID);
175             if (StringUtils.isBlank(viewId)) {
176                 viewId = request.getParameter(UifParameters.VIEW_ID);
177             }
178 
179             View view = null;
180             if (StringUtils.isNotBlank(viewId)) {
181                 view = getViewService().getViewById(viewId);
182             }
183 
184             
185             if (view == null) {
186                 view = getViewByType(request, form);
187             }
188 
189             
190             if (view == null) {
191                 view = getViewFromPreviousModel(form);
192 
193                 if (view != null) {
194                     LOG.warn("Obtained viewId from cached form, this may not be safe!");
195                 }
196             }
197 
198             if (view != null) {
199                 form.setViewId(view.getId());
200 
201             } else {
202                 form.setViewId(null);
203             }
204 
205             form.setView(view);
206         }
207 
208         
209         form.postBind((HttpServletRequest) request);
210     }
211 
212     
213 
214 
215 
216 
217 
218 
219 
220 
221     protected void executeAutomaticLinking(ServletRequest request, UifFormBase form) {
222         if (!changeTracking) {
223             LOG.info("Skip automatic linking because change tracking not enabled for this form.");
224             return;
225         }
226 
227         if (!autoLinking) {
228             LOG.info("Skip automatic linking because it has been disabled for this form");
229             return;
230         }
231 
232         Set<String> autoLinkingPaths = determineRootAutoLinkingPaths(form.getClass(), null, new HashSet<Class<?>>());
233         List<AutoLinkTarget> targets = extractAutoLinkTargets(autoLinkingPaths);
234 
235         
236         for (AutoLinkTarget target : targets) {
237             if (!dataObjectService.supports(target.getTarget().getClass())) {
238                 LOG.warn("Encountered an auto linking target that is not a valid data object: " + target.getTarget()
239                         .getClass());
240             } else {
241                 DataObjectWrapper<?> wrapped = dataObjectService.wrap(target.getTarget());
242                 wrapped.linkChanges(target.getModifiedPropertyPaths());
243             }
244         }
245     }
246 
247     
248 
249 
250 
251 
252 
253 
254 
255 
256 
257 
258 
259 
260 
261 
262 
263 
264     protected Set<String> determineRootAutoLinkingPaths(Class<?> rootObjectType, String path, Set<Class<?>> scanned) {
265         Set<String> autoLinkingPaths = new HashSet<String>();
266         if (scanned.contains(rootObjectType)) {
267             return autoLinkingPaths;
268         } else {
269             scanned.add(rootObjectType);
270         }
271         Link autoLink = AnnotationUtils.findAnnotation(rootObjectType, Link.class);
272         if (autoLink != null && autoLink.cascade()) {
273             autoLinkingPaths.addAll(assembleAutoLinkingPaths(path, autoLink));
274         } else if (autoLink == null) {
275             Field[] fields = FieldUtils.getAllFields(rootObjectType);
276             for (Field field : fields) {
277                 autoLink = field.getAnnotation(Link.class);
278                 if (autoLink != null) {
279                     if (autoLink.cascade()) {
280                         String fieldPath = appendToPath(path, field.getName());
281                         autoLinkingPaths.addAll(assembleAutoLinkingPaths(fieldPath, autoLink));
282                     }
283                 } else {
284                     autoLinkingPaths.addAll(determineRootAutoLinkingPaths(field.getType(), appendToPath(path,
285                             field.getName()), scanned));
286                 }
287             }
288         }
289         return autoLinkingPaths;
290     }
291 
292     
293 
294 
295 
296 
297 
298 
299 
300 
301 
302 
303     protected Set<String> assembleAutoLinkingPaths(String path, Link autoLink) {
304         Set<String> autoLinkingPaths = new HashSet<String>();
305         if (ArrayUtils.isEmpty(autoLink.path())) {
306             autoLinkingPaths.add(path);
307         } else {
308             for (String autoLinkingPath : autoLink.path()) {
309                 autoLinkingPaths.add(appendToPath(path, autoLinkingPath));
310             }
311         }
312         return autoLinkingPaths;
313     }
314 
315     
316 
317 
318 
319 
320 
321 
322 
323 
324 
325 
326     protected List<AutoLinkTarget> extractAutoLinkTargets(Set<String> autoLinkingPaths) {
327         List<AutoLinkTarget> targets = new ArrayList<AutoLinkTarget>();
328 
329         for (String autoLinkingPath : autoLinkingPaths) {
330             Object targetObject = getInternalBindingResult().getPropertyAccessor().getPropertyValue(autoLinkingPath);
331             if (targetObject == null) {
332                 continue;
333             }
334 
335             if (targetObject instanceof Map) {
336                 targets.addAll(extractAutoLinkMapTargets(autoLinkingPath, (Map<?, ?>) targetObject));
337 
338                 continue;
339             }
340 
341             if (targetObject instanceof List) {
342                 targets.addAll(extractAutoLinkListTargets(autoLinkingPath, (List<?>) targetObject));
343 
344                 continue;
345             }
346 
347             Set<String> modifiedAutoLinkingPaths = new HashSet<String>();
348 
349             Set<String> modifiedPaths = ((UifBeanPropertyBindingResult) getInternalBindingResult()).getModifiedPaths();
350             for (String modifiedPath : modifiedPaths) {
351                 if (modifiedPath.startsWith(autoLinkingPath)) {
352                     modifiedAutoLinkingPaths.add(modifiedPath.substring(autoLinkingPath.length() + 1));
353                 }
354             }
355 
356             targets.add(new AutoLinkTarget(targetObject, modifiedAutoLinkingPaths));
357         }
358 
359         return targets;
360     }
361 
362     
363 
364 
365 
366 
367 
368 
369 
370     protected List<AutoLinkTarget> extractAutoLinkMapTargets(String autoLinkingPath, Map<?, ?> targetMap) {
371         List<AutoLinkTarget> targets = new ArrayList<AutoLinkTarget>();
372 
373         Set<String> modifiedPaths = ((UifBeanPropertyBindingResult) getInternalBindingResult()).getModifiedPaths();
374 
375         for (Map.Entry<?, ?> targetMapEntry : targetMap.entrySet()) {
376             Set<String> modifiedAutoLinkingPaths = new HashSet<String>();
377 
378             for (String modifiedPath : modifiedPaths) {
379                 String targetPathMatch = autoLinkingPath + "['" + targetMapEntry.getKey() + "']";
380 
381                 if (modifiedPath.startsWith(targetPathMatch)) {
382                     modifiedAutoLinkingPaths.add(modifiedPath.substring(targetPathMatch.length() + 1));
383                 }
384             }
385 
386             if (!modifiedAutoLinkingPaths.isEmpty()) {
387                 targets.add(new AutoLinkTarget(targetMapEntry.getValue(), modifiedAutoLinkingPaths));
388             }
389         }
390 
391         return targets;
392     }
393 
394     
395 
396 
397 
398 
399 
400 
401 
402     protected List<AutoLinkTarget> extractAutoLinkListTargets(String autoLinkingPath, List<?> targetList) {
403         List<AutoLinkTarget> targets = new ArrayList<AutoLinkTarget>();
404 
405         Set<String> modifiedPaths = ((UifBeanPropertyBindingResult) getInternalBindingResult()).getModifiedPaths();
406 
407         for (int i = 0; i < targetList.size(); i++) {
408             Set<String> modifiedAutoLinkingPaths = new HashSet<String>();
409 
410             for (String modifiedPath : modifiedPaths) {
411                 String targetPathMatch = autoLinkingPath + "[" + i + "]";
412 
413                 if (modifiedPath.startsWith(targetPathMatch)) {
414                     modifiedAutoLinkingPaths.add(modifiedPath.substring(targetPathMatch.length() + 1));
415                 }
416             }
417 
418             if (!modifiedAutoLinkingPaths.isEmpty()) {
419                 targets.add(new AutoLinkTarget(targetList.get(i), modifiedAutoLinkingPaths));
420             }
421         }
422 
423         return targets;
424     }
425 
426     
427 
428 
429 
430 
431 
432 
433 
434 
435 
436 
437 
438     private String appendToPath(String path, String pathElement) {
439         if (StringUtils.isEmpty(path)) {
440             return pathElement;
441         } else if (StringUtils.isEmpty(pathElement)) {
442             return path;
443         }
444         return path + "." + pathElement;
445     }
446 
447     
448 
449 
450 
451 
452 
453 
454 
455     protected View getViewByType(ServletRequest request, UifFormBase form) {
456         View view = null;
457 
458         String viewTypeName = request.getParameter(UifParameters.VIEW_TYPE_NAME);
459         ViewType viewType = StringUtils.isBlank(viewTypeName) ? form.getViewTypeName() : ViewType.valueOf(viewTypeName);
460 
461         if (viewType != null) {
462             Map<String, String> parameterMap = KRADUtils.translateRequestParameterMap(request.getParameterMap());
463             view = getViewService().getViewByType(viewType, parameterMap);
464         }
465 
466         return view;
467     }
468 
469     
470 
471 
472 
473 
474 
475 
476     protected View getViewFromPreviousModel(UifFormBase form) {
477         
478         if (form.getViewId() != null) {
479             return getViewService().getViewById(form.getViewId());
480         }
481 
482         return null;
483     }
484 
485     public boolean isChangeTracking() {
486         return changeTracking;
487     }
488 
489     public boolean isAutoLinking() {
490         return autoLinking;
491     }
492 
493     public void setAutoLinking(boolean autoLinking) {
494         this.autoLinking = autoLinking;
495     }
496 
497     public ViewService getViewService() {
498         return KRADServiceLocatorWeb.getViewService();
499     }
500 
501     public DataObjectService getDataObjectService() {
502         return this.dataObjectService;
503     }
504 
505     public void setDataObjectService(DataObjectService dataObjectService) {
506         this.dataObjectService = dataObjectService;
507     }
508 
509     
510 
511 
512 
513 
514 
515     private static final class AutoLinkTarget {
516         private final Object target;
517         private final Set<String> modifiedPropertyPaths;
518 
519         AutoLinkTarget(Object target, Set<String> modifiedPropertyPaths) {
520             this.target = target;
521             this.modifiedPropertyPaths = modifiedPropertyPaths;
522         }
523 
524         Object getTarget() {
525             return target;
526         }
527 
528         Set<String> getModifiedPropertyPaths() {
529             return Collections.unmodifiableSet(modifiedPropertyPaths);
530         }
531     }
532 
533 }
534 
535