1
2
3
4
5
6
7
8
9 package org.kuali.student.common.assembly.dictionary;
10
11 import java.text.DateFormat;
12 import java.text.ParseException;
13 import java.text.SimpleDateFormat;
14 import java.util.ArrayList;
15 import java.util.HashMap;
16 import java.util.List;
17 import java.util.Map;
18
19 import org.apache.log4j.Logger;
20 import org.kuali.student.common.assembly.data.ConstraintMetadata;
21 import org.kuali.student.common.assembly.data.Data;
22 import org.kuali.student.common.assembly.data.LookupMetadata;
23 import org.kuali.student.common.assembly.data.LookupParamMetadata;
24 import org.kuali.student.common.assembly.data.Metadata;
25 import org.kuali.student.common.assembly.data.UILookupConfig;
26 import org.kuali.student.common.assembly.data.UILookupData;
27 import org.kuali.student.common.assembly.data.Data.DataType;
28 import org.kuali.student.common.assembly.data.Data.Value;
29 import org.kuali.student.common.assembly.data.Metadata.WriteAccess;
30 import org.kuali.student.common.dictionary.dto.CaseConstraint;
31 import org.kuali.student.common.dictionary.dto.CommonLookupParam;
32 import org.kuali.student.common.dictionary.dto.Constraint;
33 import org.kuali.student.common.dictionary.dto.FieldDefinition;
34 import org.kuali.student.common.dictionary.dto.ObjectStructureDefinition;
35 import org.kuali.student.common.dictionary.dto.WhenConstraint;
36 import org.kuali.student.common.dictionary.service.DictionaryService;
37 import org.kuali.student.common.dto.DtoConstants.DtoState;
38 import org.springframework.beans.BeanUtils;
39 import org.springframework.context.ApplicationContext;
40 import org.springframework.context.support.ClassPathXmlApplicationContext;
41
42
43
44
45
46
47 public class MetadataServiceImpl {
48 final Logger LOG = Logger.getLogger(MetadataServiceImpl.class);
49
50 private Map<String, DictionaryService> dictionaryServiceMap;
51 private List<UILookupConfig> lookupObjectStructures;
52 private String uiLookupContext;
53
54 private static class RecursionCounter {
55 public static final int MAX_DEPTH = 4;
56
57 private Map<String, Integer> recursions = new HashMap<String, Integer>();
58
59 public int increment(String objectName) {
60 Integer hits = recursions.get(objectName);
61
62 if (hits == null) {
63 hits = new Integer(1);
64 } else {
65 hits++;
66 }
67 recursions.put(objectName, hits);
68 return hits;
69 }
70
71 public int decrement(String objectName) {
72 Integer hits = recursions.get(objectName);
73 if (hits >= 1) {
74 hits--;
75 }
76
77 recursions.put(objectName, hits);
78 return hits;
79 }
80 }
81
82
83
84
85
86
87 public MetadataServiceImpl(DictionaryService... dictionaryServices) {
88 if (dictionaryServices != null) {
89 this.dictionaryServiceMap = new HashMap<String, DictionaryService>();
90 for (DictionaryService d : dictionaryServices) {
91 List<String> objectTypes = d.getObjectTypes();
92 for (String objectType : objectTypes) {
93 dictionaryServiceMap.put(objectType, d);
94 }
95 }
96 }
97 }
98
99
100
101
102
103
104
105
106
107
108 public Metadata getMetadata(String objectKey, String type, String state, String nextState) {
109 return getMetadataFromDictionaryService(objectKey, type, state, nextState);
110 }
111
112
113
114
115
116
117
118
119
120 public Metadata getMetadata(String objectKey, String type, String state) {
121 return getMetadata(objectKey, type, state, null);
122 }
123
124
125
126
127
128
129
130
131 public Metadata getMetadata(String objectKey, String state) {
132 return getMetadata(objectKey, null, state);
133 }
134
135
136
137
138
139
140
141
142 public Metadata getMetadata(String objectKey) {
143 return getMetadata(objectKey, null, null);
144 }
145
146
147
148
149
150
151
152
153
154
155 protected Metadata getMetadataFromDictionaryService(String objectKey, String type, String state, String nextState) {
156 Metadata metadata = new Metadata();
157
158 ObjectStructureDefinition objectStructure = getObjectStructure(objectKey);
159
160 metadata.setProperties(getProperties(objectStructure, type, state, nextState, new RecursionCounter()));
161
162 metadata.setWriteAccess(WriteAccess.ALWAYS);
163 metadata.setDataType(DataType.DATA);
164 addLookupstoMetadata(objectKey, metadata, type);
165 return metadata;
166 }
167
168
169
170
171
172
173
174
175
176 private Map<String, Metadata> getProperties(ObjectStructureDefinition objectStructure, String type, String state, String nextState, RecursionCounter counter) {
177 String objectId = objectStructure.getName();
178 int hits = counter.increment(objectId);
179
180 Map<String, Metadata> properties = null;
181
182 if (hits < RecursionCounter.MAX_DEPTH) {
183 properties = new HashMap<String, Metadata>();
184
185 List<FieldDefinition> attributes = objectStructure.getAttributes();
186 for (FieldDefinition fd : attributes) {
187
188 Metadata metadata = new Metadata();
189
190
191 metadata.setWriteAccess(WriteAccess.ALWAYS);
192 metadata.setDataType(convertDictionaryDataType(fd.getDataType()));
193 metadata.setConstraints(getConstraints(fd, type, state, nextState));
194 metadata.setCanEdit(!fd.isReadOnly());
195 metadata.setCanUnmask(!fd.isMask());
196 metadata.setCanView(!fd.isHide());
197 metadata.setDynamic(fd.isDynamic());
198 metadata.setLabelKey(fd.getLabelKey());
199 metadata.setDefaultValue(convertDefaultValue(metadata.getDataType(), fd.getDefaultValue()));
200 metadata.setDefaultValuePath(fd.getDefaultValuePath());
201
202 if (fd.isPartialMask()){
203 metadata.setPartialMaskFormatter(fd.getPartialMaskFormatter());
204 }
205
206 if (fd.isMask()){
207 metadata.setMaskFormatter(fd.getMaskFormatter());
208 }
209
210
211 Map<String, Metadata> nestedProperties = null;
212 if (fd.getDataType() == org.kuali.student.common.dictionary.dto.DataType.COMPLEX && fd.getDataObjectStructure() != null) {
213 nestedProperties = getProperties(fd.getDataObjectStructure(), type, state, nextState, counter);
214 }
215
216
217 if (isRepeating(fd)) {
218 Metadata repeatingMetadata = new Metadata();
219 metadata.setDataType(DataType.LIST);
220
221 repeatingMetadata.setWriteAccess(WriteAccess.ALWAYS);
222 repeatingMetadata.setOnChangeRefreshMetadata(false);
223 repeatingMetadata.setDataType(convertDictionaryDataType(fd.getDataType()));
224
225 if (nestedProperties != null) {
226 repeatingMetadata.setProperties(nestedProperties);
227 }
228
229 Map<String, Metadata> repeatingProperty = new HashMap<String, Metadata>();
230 repeatingProperty.put("*", repeatingMetadata);
231 metadata.setProperties(repeatingProperty);
232 } else if (nestedProperties != null) {
233 metadata.setProperties(nestedProperties);
234 }
235
236 properties.put(fd.getName(), metadata);
237
238 }
239 }
240
241 counter.decrement(objectId);
242 return properties;
243 }
244
245
246
247
248
249
250
251 protected boolean isRepeating(FieldDefinition fd) {
252 boolean isRepeating = false;
253 try {
254 int maxOccurs = Integer.parseInt(fd.getMaxOccurs());
255 isRepeating = maxOccurs > 1;
256 } catch (NumberFormatException nfe) {
257 isRepeating = FieldDefinition.UNBOUNDED.equals(fd.getMaxOccurs());
258 }
259
260 return isRepeating;
261 }
262
263
264
265
266
267
268
269 protected ObjectStructureDefinition getObjectStructure(String objectKey) {
270 DictionaryService dictionaryService = dictionaryServiceMap.get(objectKey);
271
272 if (dictionaryService == null) {
273 throw new RuntimeException("Dictionary service not provided for objectKey=[" + objectKey + "].");
274 }
275
276 return dictionaryService.getObjectStructure(objectKey);
277 }
278
279 protected List<ConstraintMetadata> getConstraints(FieldDefinition fd, String type, String state, String nextState) {
280 List<ConstraintMetadata> constraints = new ArrayList<ConstraintMetadata>();
281
282 ConstraintMetadata constraintMetadata = new ConstraintMetadata();
283
284 updateConstraintMetadata(constraintMetadata, (Constraint) fd, type, state, nextState);
285 constraints.add(constraintMetadata);
286
287 return constraints;
288 }
289
290
291
292
293
294
295
296 protected void updateConstraintMetadata(ConstraintMetadata constraintMetadata, Constraint constraint, String type, String state, String nextState) {
297
298
299
300
301 if (constraint.getMinLength() != null) {
302 constraintMetadata.setMinLength(constraint.getMinLength());
303 }
304
305
306 try {
307 if (constraint.getMaxLength() != null) {
308 constraintMetadata.setMaxLength(Integer.parseInt(constraint.getMaxLength()));
309 }
310
311 } catch (NumberFormatException nfe) {
312
313
314 constraintMetadata.setMaxLength(9999);
315 }
316
317
318 if (constraint.getMinOccurs() != null) {
319 constraintMetadata.setMinOccurs(constraint.getMinOccurs());
320 }
321
322
323 String maxOccurs = constraint.getMaxOccurs();
324 if (maxOccurs != null) {
325 try {
326 constraintMetadata.setMaxOccurs(Integer.parseInt(maxOccurs));
327 if (!FieldDefinition.SINGLE.equals(maxOccurs)) {
328 constraintMetadata.setId("repeating");
329 }
330 } catch (NumberFormatException nfe) {
331
332 if (FieldDefinition.UNBOUNDED.equals(maxOccurs)) {
333 constraintMetadata.setId("repeating");
334 constraintMetadata.setMaxOccurs(9999);
335 }
336 }
337 }
338
339
340 if (constraint.getExclusiveMin() != null) {
341 constraintMetadata.setMinValue(constraint.getExclusiveMin());
342 }
343
344
345 if (constraint.getInclusiveMax() != null) {
346 constraintMetadata.setMaxValue(constraint.getInclusiveMax());
347 }
348
349 if (constraint.getValidChars() != null) {
350 constraintMetadata.setValidChars(constraint.getValidChars().getValue());
351 constraintMetadata.setValidCharsMessageId(constraint.getValidChars().getLabelKey());
352 }
353
354
355 if (constraint.getCaseConstraint() != null) {
356 processCaseConstraint(constraintMetadata, constraint.getCaseConstraint(), type, state, nextState);
357 }
358 }
359
360 protected void processCaseConstraint(ConstraintMetadata constraintMetadata, CaseConstraint caseConstraint, String type, String state, String nextState) {
361 String fieldPath = caseConstraint.getFieldPath();
362 List<WhenConstraint> whenConstraints = caseConstraint.getWhenConstraint();
363
364 fieldPath = (fieldPath != null ? fieldPath.toUpperCase() : fieldPath);
365 if ("STATE".equals(fieldPath)) {
366
367
368
369 state = (state == null ? DtoState.DRAFT.toString():state);
370 nextState = (nextState == null ? DtoState.getNextStateAsString(state):nextState);
371
372 if ("EQUALS".equals(caseConstraint.getOperator()) && whenConstraints != null) {
373 for (WhenConstraint whenConstraint : whenConstraints) {
374 List<Object> values = whenConstraint.getValues();
375 if (values != null) {
376 Constraint constraint = whenConstraint.getConstraint();
377
378
379 if (values.contains(nextState)) {
380 if (constraint.getMinOccurs() > 0) {
381 constraintMetadata.setRequiredForNextState(true);
382 constraintMetadata.setNextState(nextState);
383 }
384 }
385
386
387 if (values.contains(state.toUpperCase())) {
388 updateConstraintMetadata(constraintMetadata, constraint, type, state, nextState);
389 }
390 }
391 }
392 }
393 } else if ("TYPE".equals(fieldPath)) {
394
395
396 if ("EQUALS".equals(caseConstraint.getOperator()) && whenConstraints != null) {
397 for (WhenConstraint whenConstraint : whenConstraints) {
398 List<Object> values = whenConstraint.getValues();
399 if (values != null && values.contains(type)) {
400 Constraint constraint = whenConstraint.getConstraint();
401 updateConstraintMetadata(constraintMetadata, constraint, type, state, nextState);
402 }
403 }
404 }
405 }
406 }
407
408
409
410
411
412
413
414
415 protected Value convertDefaultValue(DataType dataType, Object value) {
416 Value v = null;
417 if (value instanceof String) {
418 String s = (String) value;
419 switch (dataType) {
420 case STRING:
421 v = new Data.StringValue(s);
422 break;
423 case BOOLEAN:
424 v = new Data.BooleanValue(Boolean.valueOf(s));
425 break;
426 case FLOAT:
427 v = new Data.FloatValue(Float.valueOf(s));
428 break;
429 case DATE:
430 DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
431 try {
432 v = new Data.DateValue(format.parse(s));
433 } catch (ParseException e) {
434 LOG.error("Unable to get default date value from metadata definition");
435 }
436 break;
437 case LONG:
438 if (!s.isEmpty()) {
439 v = new Data.LongValue(Long.valueOf(s));
440 }
441 break;
442 case DOUBLE:
443 v = new Data.DoubleValue(Double.valueOf(s));
444 break;
445 case INTEGER:
446 v = new Data.IntegerValue(Integer.valueOf(s));
447 break;
448 }
449 } else {
450 v = convertDefaultValue(value);
451 }
452
453 return v;
454 }
455
456 protected Value convertDefaultValue(Object value) {
457 Value v = null;
458
459 if (value instanceof String) {
460 v = new Data.StringValue((String) value);
461 } else if (value instanceof Boolean) {
462 v = new Data.BooleanValue((Boolean) value);
463 } else if (value instanceof Integer) {
464 v = new Data.IntegerValue((Integer) value);
465 } else if (value instanceof Double) {
466 v = new Data.DoubleValue((Double) value);
467 } else if (value instanceof Long) {
468 v = new Data.LongValue((Long) value);
469 } else if (value instanceof Short) {
470 v = new Data.ShortValue((Short) value);
471 } else if (value instanceof Float) {
472 v = new Data.FloatValue((Float) value);
473 }
474
475 return v;
476 }
477
478 protected DataType convertDictionaryDataType(org.kuali.student.common.dictionary.dto.DataType dataType) {
479 switch (dataType) {
480 case STRING:
481 return DataType.STRING;
482 case BOOLEAN:
483 return DataType.BOOLEAN;
484 case INTEGER:
485 return DataType.INTEGER;
486 case FLOAT:
487 return DataType.FLOAT;
488 case COMPLEX:
489 return DataType.DATA;
490 case DATE:
491 return DataType.DATE;
492 case DOUBLE:
493 return DataType.DOUBLE;
494 case LONG:
495 return DataType.LONG;
496 }
497
498 return null;
499 }
500
501 public void setUiLookupContext(String uiLookupContext) {
502 this.uiLookupContext = uiLookupContext;
503 init();
504
505 }
506
507 @SuppressWarnings("unchecked")
508 private void init() {
509 ApplicationContext ac = new ClassPathXmlApplicationContext(uiLookupContext);
510
511 Map<String, UILookupConfig> beansOfType = (Map<String, UILookupConfig>) ac.getBeansOfType(UILookupConfig.class);
512 lookupObjectStructures = new ArrayList<UILookupConfig>();
513 for (UILookupConfig objStr : beansOfType.values()) {
514 lookupObjectStructures.add(objStr);
515 }
516 System.out.println("UILookup loaded");
517 }
518
519 private String calcSimpleName(String objectKey) {
520 int lastDot = objectKey.lastIndexOf(".");
521 if (lastDot == -1) {
522 return objectKey;
523 }
524 return objectKey.substring(lastDot + 1);
525
526 }
527
528 private boolean matchesObjectKey(String objectKey, String path) {
529 String simpleName = calcSimpleName(objectKey);
530 if (path.toLowerCase().startsWith(simpleName.toLowerCase())) {
531
532 return true;
533 }
534
535 return false;
536 }
537
538 private boolean matchesType(String paramType, String lookupType) {
539
540 if (paramType == null && lookupType == null) {
541 return true;
542 }
543
544
545 if (paramType == null && lookupType != null) {
546 return false;
547 }
548
549
550
551
552
553 if (paramType != null && lookupType == null) {
554 return true;
555 }
556 if (paramType.equalsIgnoreCase(lookupType)) {
557
558 return true;
559 }
560
561 return false;
562 }
563
564 private void addLookupstoMetadata(String objectKey, Metadata metadata, String type) {
565 if (lookupObjectStructures != null) {
566 for (UILookupConfig lookup : lookupObjectStructures) {
567 if (!matchesObjectKey(objectKey, lookup.getPath())) {
568 continue;
569 }
570 if (!matchesType(type, lookup.getType())) {
571 continue;
572 }
573
574
575 Map<String, Metadata> parsedMetadataMap = metadata.getProperties();
576 Metadata parsedMetadata = null;
577 String parsedMetadataKey = "";
578 String lookupFieldPath = lookup.getPath();
579 String[] lookupPathTokens = getPathTokens(lookupFieldPath);
580 for (int i = 1; i < lookupPathTokens.length; i++) {
581 if (parsedMetadataMap == null) {
582 break;
583 }
584 if (i == lookupPathTokens.length - 1) {
585
586 parsedMetadata = parsedMetadataMap.get(lookupPathTokens[i]);
587 parsedMetadataKey = parsedMetadataKey + "." + lookupPathTokens[i];
588 }
589 if (parsedMetadataMap.get(lookupPathTokens[i]) != null) {
590 parsedMetadataMap = parsedMetadataMap.get(lookupPathTokens[i]).getProperties();
591 } else if (parsedMetadataMap.get("*") != null) {
592
593 parsedMetadataMap = parsedMetadataMap.get("*").getProperties();
594 i--;
595 }
596
597 }
598 if (parsedMetadata != null) {
599
600
601 UILookupData initialLookup = lookup.getInitialLookup();
602 if (initialLookup != null) {
603 mapLookupDatatoMeta(initialLookup);
604 parsedMetadata.setInitialLookup(mapLookupDatatoMeta(lookup.getInitialLookup()));
605 }
606 List<LookupMetadata> additionalLookupMetadata = null;
607 if (lookup.getAdditionalLookups() != null) {
608 additionalLookupMetadata = new ArrayList<LookupMetadata>();
609 for (UILookupData additionallookup : lookup.getAdditionalLookups()) {
610 additionalLookupMetadata.add(mapLookupDatatoMeta(additionallookup));
611 }
612 parsedMetadata.setAdditionalLookups(additionalLookupMetadata);
613 }
614 }
615 }
616 }
617 }
618
619 private LookupMetadata mapLookupDatatoMeta(UILookupData lookupData) {
620 LookupMetadata lookupMetadata = new LookupMetadata();
621 List<LookupParamMetadata> paramsMetadata;
622 BeanUtils.copyProperties(lookupData, lookupMetadata, new String[]{"widget", "usage", "widgetOptions", "params"});
623 if (lookupData.getWidget() != null) {
624 lookupMetadata.setWidget(org.kuali.student.common.assembly.data.LookupMetadata.Widget.valueOf(lookupData.getWidget().toString()));
625 }
626 if (lookupData.getUsage() != null) {
627 lookupMetadata.setUsage(org.kuali.student.common.assembly.data.LookupMetadata.Usage.valueOf(lookupData.getUsage().toString()));
628 }
629 if (lookupData.getWidgetOptions () != null) {
630 lookupMetadata.setWidgetOptions (new HashMap ());
631 for (UILookupData.WidgetOption wo: lookupData.getWidgetOptions ().keySet ()) {
632 String value = lookupData.getWidgetOptions ().get (wo);
633 LookupMetadata.WidgetOption key = LookupMetadata.WidgetOption.valueOf(wo.toString());
634 lookupMetadata.getWidgetOptions ().put (key, value);
635 }
636 }
637 if (lookupData.getParams() != null) {
638 paramsMetadata = new ArrayList<LookupParamMetadata>();
639 for (CommonLookupParam param : lookupData.getParams()) {
640 paramsMetadata.add(mapLookupParamMetadata(param));
641 }
642 lookupMetadata.setParams(paramsMetadata);
643 }
644
645 return lookupMetadata;
646 }
647
648 private LookupParamMetadata mapLookupParamMetadata(CommonLookupParam param) {
649 LookupParamMetadata paramMetadata = new LookupParamMetadata();
650 BeanUtils.copyProperties(param, paramMetadata, new String[]{"childLookup", "dataType", "writeAccess", "usage", "widget"});
651 if (param.getChildLookup() != null) {
652 paramMetadata.setChildLookup(mapLookupDatatoMeta((UILookupData) param.getChildLookup()));
653 }
654 if (param.getDataType() != null) {
655 paramMetadata.setDataType(org.kuali.student.common.assembly.data.Data.DataType.valueOf(param.getDataType().toString()));
656 }
657 if (param.getWriteAccess() != null) {
658 paramMetadata.setWriteAccess(org.kuali.student.common.assembly.data.Metadata.WriteAccess.valueOf(param.getWriteAccess().toString()));
659 }
660 if (param.getUsage() != null) {
661 paramMetadata.setUsage(org.kuali.student.common.assembly.data.LookupMetadata.Usage.valueOf(param.getUsage().toString()));
662 }
663 if (param.getWidget() != null) {
664 paramMetadata.setWidget(org.kuali.student.common.assembly.data.LookupParamMetadata.Widget.valueOf(param.getWidget().toString()));
665 }
666
667 return paramMetadata;
668 }
669
670 private static String[] getPathTokens(String fieldPath) {
671 return (fieldPath != null && fieldPath.contains(".") ? fieldPath.split("\\.") : new String[]{fieldPath});
672 }
673 }