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