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