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