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.ViewType;
27 import org.kuali.rice.krad.uif.UifParameters;
28 import org.kuali.rice.krad.uif.service.ViewService;
29 import org.kuali.rice.krad.uif.view.View;
30 import org.kuali.rice.krad.util.KRADUtils;
31 import org.kuali.rice.krad.web.form.UifFormBase;
32 import org.springframework.core.annotation.AnnotationUtils;
33 import org.springframework.core.convert.ConversionService;
34 import org.springframework.util.Assert;
35 import org.springframework.validation.AbstractPropertyBindingResult;
36 import org.springframework.web.bind.ServletRequestDataBinder;
37
38 import javax.servlet.ServletRequest;
39 import javax.servlet.http.HttpServletRequest;
40 import java.lang.reflect.Field;
41 import java.util.ArrayList;
42 import java.util.Collections;
43 import java.util.HashSet;
44 import java.util.List;
45 import java.util.Map;
46 import java.util.Set;
47
48
49
50
51
52
53
54 public class UifServletRequestDataBinder extends ServletRequestDataBinder {
55
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 form.preBind((HttpServletRequest) request);
163
164 _bind(request);
165
166 executeAutomaticLinking(request, form);
167
168 if (!form.isUpdateNoneRequest()) {
169
170 String viewId = (String) request.getAttribute(UifParameters.VIEW_ID);
171 if (StringUtils.isBlank(viewId)) {
172 viewId = request.getParameter(UifParameters.VIEW_ID);
173 }
174
175 View view = null;
176 if (StringUtils.isNotBlank(viewId)) {
177 view = getViewService().getViewById(viewId);
178 }
179
180
181 if (view == null) {
182 view = getViewByType(request, form);
183 }
184
185
186 if (view == null) {
187 view = getViewFromPreviousModel(form);
188
189 if (view != null) {
190 LOG.warn("Obtained viewId from cached form, this may not be safe!");
191 }
192 }
193
194 if (view != null) {
195 form.setViewId(view.getId());
196
197 } else {
198 form.setViewId(null);
199 }
200
201 form.setView(view);
202 }
203
204
205 form.postBind((HttpServletRequest) request);
206 }
207
208
209
210
211
212
213
214
215
216
217 protected void executeAutomaticLinking(ServletRequest request, UifFormBase form) {
218 if (!changeTracking) {
219 LOG.info("Skip automatic linking because change tracking not enabled for this form.");
220 return;
221 }
222 if (!autoLinking) {
223 LOG.info("Skip automatic linking because it has been disabled for this form");
224 return;
225 }
226 Set<String> autoLinkingPaths = determineRootAutoLinkingPaths(form.getClass(), null, new HashSet<Class<?>>());
227 List<AutoLinkTarget> targets = extractAutoLinkTargets(autoLinkingPaths);
228
229 for (AutoLinkTarget target : targets) {
230 if (!dataObjectService.supports(target.getTarget().getClass())) {
231 LOG.warn("Encountered an auto linking target that is not a valid data object: "
232 + target.getTarget().getClass());
233 } else {
234 DataObjectWrapper<?> wrapped = dataObjectService.wrap(target.getTarget());
235 wrapped.linkChanges(target.getModifiedPropertyPaths());
236 }
237 }
238 }
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257 protected Set<String> determineRootAutoLinkingPaths(Class<?> rootObjectType, String path, Set<Class<?>> scanned) {
258 Set<String> autoLinkingPaths = new HashSet<String>();
259 if (scanned.contains(rootObjectType)) {
260 return autoLinkingPaths;
261 } else {
262 scanned.add(rootObjectType);
263 }
264 Link autoLink = AnnotationUtils.findAnnotation(rootObjectType, Link.class);
265 if (autoLink != null && autoLink.cascade()) {
266 autoLinkingPaths.addAll(assembleAutoLinkingPaths(path, autoLink));
267 } else if (autoLink == null) {
268 Field[] fields = FieldUtils.getAllFields(rootObjectType);
269 for (Field field : fields) {
270 autoLink = field.getAnnotation(Link.class);
271 if (autoLink != null) {
272 if (autoLink.cascade()) {
273 String fieldPath = appendToPath(path, field.getName());
274 autoLinkingPaths.addAll(assembleAutoLinkingPaths(fieldPath, autoLink));
275 }
276 } else {
277 autoLinkingPaths.addAll(determineRootAutoLinkingPaths(field.getType(), appendToPath(path,
278 field.getName()), scanned));
279 }
280 }
281 }
282 return autoLinkingPaths;
283 }
284
285
286
287
288
289
290
291
292
293
294
295 protected Set<String> assembleAutoLinkingPaths(String path, Link autoLink) {
296 Set<String> autoLinkingPaths = new HashSet<String>();
297 if (ArrayUtils.isEmpty(autoLink.path())) {
298 autoLinkingPaths.add(path);
299 } else {
300 for (String autoLinkingPath : autoLink.path()) {
301 autoLinkingPaths.add(appendToPath(path, autoLinkingPath));
302 }
303 }
304 return autoLinkingPaths;
305 }
306
307
308
309
310
311
312
313
314
315
316
317
318
319 protected List<AutoLinkTarget> extractAutoLinkTargets(Set<String> autoLinkingPaths) {
320 List<AutoLinkTarget> targets = new ArrayList<AutoLinkTarget>();
321 for (String autoLinkingPath : autoLinkingPaths) {
322 Set<String> modifiedAutoLinkingPaths = new HashSet<String>();
323 Set<String> modifiedPaths = ((UifBeanPropertyBindingResult)getInternalBindingResult()).getModifiedPaths();
324 for (String modifiedPath : modifiedPaths) {
325 if (modifiedPath.startsWith(autoLinkingPath)) {
326 modifiedAutoLinkingPaths.add(modifiedPath.substring(autoLinkingPath.length() + 1));
327 }
328 }
329 Object targetObject = getInternalBindingResult().getPropertyAccessor().getPropertyValue(autoLinkingPath);
330 if (targetObject != null) {
331 targets.add(new AutoLinkTarget(targetObject, modifiedAutoLinkingPaths));
332 }
333 }
334 return targets;
335 }
336
337
338
339
340
341
342
343
344
345
346
347
348
349 private String appendToPath(String path, String pathElement) {
350 if (StringUtils.isEmpty(path)) {
351 return pathElement;
352 } else if (StringUtils.isEmpty(pathElement)) {
353 return path;
354 }
355 return path + "." + pathElement;
356 }
357
358
359
360
361
362
363
364
365
366 protected View getViewByType(ServletRequest request, UifFormBase form) {
367 View view = null;
368
369 String viewTypeName = request.getParameter(UifParameters.VIEW_TYPE_NAME);
370 ViewType viewType = StringUtils.isBlank(viewTypeName) ? form.getViewTypeName() : ViewType.valueOf(viewTypeName);
371
372 if (viewType != null) {
373 Map<String, String> parameterMap = KRADUtils.translateRequestParameterMap(request.getParameterMap());
374 view = getViewService().getViewByType(viewType, parameterMap);
375 }
376
377 return view;
378 }
379
380
381
382
383
384
385
386
387 protected View getViewFromPreviousModel(UifFormBase form) {
388
389 if (form.getViewId() != null) {
390 return getViewService().getViewById(form.getViewId());
391 }
392
393 return null;
394 }
395
396 public boolean isChangeTracking() {
397 return changeTracking;
398 }
399
400 public boolean isAutoLinking() {
401 return autoLinking;
402 }
403
404 public void setAutoLinking(boolean autoLinking) {
405 this.autoLinking = autoLinking;
406 }
407
408 public ViewService getViewService() {
409 return KRADServiceLocatorWeb.getViewService();
410 }
411
412 public DataObjectService getDataObjectService() {
413 return this.dataObjectService;
414 }
415
416 public void setDataObjectService(DataObjectService dataObjectService) {
417 this.dataObjectService = dataObjectService;
418 }
419
420
421
422
423
424
425
426 private static final class AutoLinkTarget {
427
428 private final Object target;
429 private final Set<String> modifiedPropertyPaths;
430
431 AutoLinkTarget(Object target, Set<String> modifiedPropertyPaths) {
432 this.target = target;
433 this.modifiedPropertyPaths = modifiedPropertyPaths;
434 }
435
436 Object getTarget() {
437 return target;
438 }
439
440 Set<String> getModifiedPropertyPaths() {
441 return Collections.unmodifiableSet(modifiedPropertyPaths);
442 }
443
444 }
445
446 }
447
448