001/** 002 * Copyright 2005-2016 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.rice.krad.datadictionary; 017 018import java.beans.PropertyDescriptor; 019import java.io.File; 020import java.io.IOException; 021import java.util.ArrayList; 022import java.util.Arrays; 023import java.util.Collection; 024import java.util.HashMap; 025import java.util.List; 026import java.util.Map; 027import java.util.Set; 028import java.util.TreeMap; 029 030import org.apache.commons.beanutils.PropertyUtils; 031import org.apache.commons.collections.ListUtils; 032import org.apache.commons.lang3.ArrayUtils; 033import org.apache.commons.lang.ClassUtils; 034import org.apache.commons.lang.StringUtils; 035import org.kuali.rice.core.api.config.property.ConfigContext; 036import org.kuali.rice.core.api.util.ClassLoaderUtils; 037import org.kuali.rice.krad.data.provider.annotation.UifAutoCreateViewType; 038import org.kuali.rice.krad.datadictionary.exception.AttributeValidationException; 039import org.kuali.rice.krad.datadictionary.exception.CompletionException; 040import org.kuali.rice.krad.datadictionary.parse.StringListConverter; 041import org.kuali.rice.krad.datadictionary.parse.StringMapConverter; 042import org.kuali.rice.krad.datadictionary.uif.ComponentBeanPostProcessor; 043import org.kuali.rice.krad.datadictionary.uif.UifBeanFactoryPostProcessor; 044import org.kuali.rice.krad.datadictionary.uif.UifDictionaryIndex; 045import org.kuali.rice.krad.datadictionary.validator.ErrorReport; 046import org.kuali.rice.krad.datadictionary.validator.ValidationTrace; 047import org.kuali.rice.krad.datadictionary.validator.Validator; 048import org.kuali.rice.krad.lookup.LookupView; 049import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 050import org.kuali.rice.krad.service.LegacyDataAdapter; 051import org.kuali.rice.krad.uif.UifConstants; 052import org.kuali.rice.krad.uif.UifConstants.ViewType; 053import org.kuali.rice.krad.uif.util.ComponentFactory; 054import org.kuali.rice.krad.uif.util.ExpressionFunctions; 055import org.kuali.rice.krad.uif.util.ObjectPropertyUtils; 056import org.kuali.rice.krad.uif.view.InquiryView; 057import org.kuali.rice.krad.uif.view.View; 058import org.kuali.rice.krad.util.KRADConstants; 059import org.slf4j.Logger; 060import org.slf4j.LoggerFactory; 061import org.springframework.beans.PropertyValue; 062import org.springframework.beans.PropertyValues; 063import org.springframework.beans.factory.config.BeanDefinition; 064import org.springframework.beans.factory.config.BeanExpressionContext; 065import org.springframework.beans.factory.config.BeanPostProcessor; 066import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; 067import org.springframework.beans.factory.config.Scope; 068import org.springframework.beans.factory.support.ChildBeanDefinition; 069import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; 070import org.springframework.context.expression.StandardBeanExpressionResolver; 071import org.springframework.core.convert.support.GenericConversionService; 072import org.springframework.core.io.DefaultResourceLoader; 073import org.springframework.core.io.Resource; 074import org.springframework.expression.spel.support.StandardEvaluationContext; 075import org.springframework.util.StopWatch; 076 077/** 078 * Encapsulates a bean factory and indexes to the beans within the factory for providing 079 * framework metadata 080 * 081 * @author Kuali Rice Team (rice.collab@kuali.org) 082 */ 083public class DataDictionary { 084 085 private static final Logger LOG = LoggerFactory.getLogger(DataDictionary.class); 086 087 protected static boolean validateEBOs = true; 088 089 protected DefaultListableBeanFactory ddBeans = new DefaultListableBeanFactory(); 090 protected XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(ddBeans); 091 092 protected DataDictionaryIndex ddIndex = new DataDictionaryIndex(ddBeans); 093 protected UifDictionaryIndex uifIndex = new UifDictionaryIndex(ddBeans); 094 095 protected DataDictionaryMapper ddMapper = new DataDictionaryIndexMapper(); 096 097 protected Map<String, List<String>> moduleDictionaryFiles = new HashMap<String, List<String>>(); 098 protected List<String> moduleLoadOrder = new ArrayList<String>(); 099 100 protected ArrayList<String> beanValidationFiles = new ArrayList<String>(); 101 102 public static LegacyDataAdapter legacyDataAdapter; 103 104 protected transient StopWatch timer; 105 106 /** 107 * Populates and processes the dictionary bean factory based on the configured files and 108 * performs indexing 109 * 110 * @param allowConcurrentValidation - indicates whether the indexing should occur on a different thread 111 * or the same thread 112 */ 113 public void parseDataDictionaryConfigurationFiles(boolean allowConcurrentValidation) { 114 timer = new StopWatch("DD Processing"); 115 setupProcessor(ddBeans); 116 117 loadDictionaryBeans(ddBeans, moduleDictionaryFiles, ddIndex, beanValidationFiles); 118 119 performDictionaryPostProcessing(allowConcurrentValidation); 120 } 121 122 /** 123 * Sets up the bean post processor and conversion service 124 * 125 * @param beans - The bean factory for the the dictionary beans 126 */ 127 public static void setupProcessor(DefaultListableBeanFactory beans) { 128 try { 129 // UIF post processor that sets component ids 130 BeanPostProcessor idPostProcessor = ComponentBeanPostProcessor.class.newInstance(); 131 beans.addBeanPostProcessor(idPostProcessor); 132 beans.setBeanExpressionResolver(new StandardBeanExpressionResolver() { 133 @Override 134 protected void customizeEvaluationContext(StandardEvaluationContext evalContext) { 135 try { 136 evalContext.registerFunction("getService", ExpressionFunctions.class.getDeclaredMethod("getService", new Class[]{String.class})); 137 } catch(NoSuchMethodException me) { 138 LOG.error("Unable to register custom expression to data dictionary bean factory", me); 139 } 140 } 141 }); 142 143 // special converters for shorthand map and list property syntax 144 GenericConversionService conversionService = new GenericConversionService(); 145 conversionService.addConverter(new StringMapConverter()); 146 conversionService.addConverter(new StringListConverter()); 147 148 beans.setConversionService(conversionService); 149 } catch (Exception e1) { 150 throw new DataDictionaryException("Cannot create component decorator post processor: " + e1.getMessage(), 151 e1); 152 } 153 } 154 155 /** 156 * Populates and processes the dictionary bean factory based on the configured files 157 * 158 * @param beans - The bean factory for the dictionary bean 159 * @param moduleDictionaryFiles - List of bean xml files 160 * @param index - Index of the data dictionary beans 161 * @param validationFiles - The List of bean xml files loaded into the bean file 162 */ 163 public void loadDictionaryBeans(DefaultListableBeanFactory beans, 164 Map<String, List<String>> moduleDictionaryFiles, DataDictionaryIndex index, 165 ArrayList<String> validationFiles) { 166 // expand configuration locations into files 167 timer.start("XML File Loading"); 168 LOG.info("Starting DD XML File Load"); 169 170 List<String> allBeanNames = new ArrayList<String>(); 171 for (String namespaceCode : moduleLoadOrder) { 172 LOG.info( "Processing Module: " + namespaceCode); 173 List<String> moduleDictionaryLocations = moduleDictionaryFiles.get(namespaceCode); 174 if ( LOG.isDebugEnabled() ) { 175 LOG.debug("DD Locations in Module: " + moduleDictionaryLocations); 176 } 177 178 if (moduleDictionaryLocations == null) { 179 continue; 180 } 181 182 XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(beans); 183 184 String configFileLocationsArray[] = new String[moduleDictionaryLocations.size()]; 185 configFileLocationsArray = moduleDictionaryLocations.toArray(configFileLocationsArray); 186 for (int i = 0; i < configFileLocationsArray.length; i++) { 187 validationFiles.add(configFileLocationsArray[i]); 188 } 189 190 try { 191 xmlReader.loadBeanDefinitions(configFileLocationsArray); 192 193 // get updated bean names from factory and compare to our previous list to get those that 194 // were added by the last namespace 195 List<String> addedBeanNames = Arrays.asList(beans.getBeanDefinitionNames()); 196 addedBeanNames = ListUtils.removeAll(addedBeanNames, allBeanNames); 197 index.addBeanNamesToNamespace(namespaceCode, addedBeanNames); 198 199 allBeanNames.addAll(addedBeanNames); 200 } catch (Exception e) { 201 throw new DataDictionaryException("Error loading bean definitions: " + e.getLocalizedMessage(),e); 202 } 203 } 204 205 LOG.info("Completed DD XML File Load"); 206 timer.stop(); 207 } 208 209 /** 210 * Invokes post processors and builds indexes for the beans contained in the dictionary 211 * 212 * @param allowConcurrentValidation - indicates whether the indexing should occur on a different thread 213 * or the same thread 214 */ 215 public void performDictionaryPostProcessing(boolean allowConcurrentValidation) { 216 LOG.info("Starting Data Dictionary Post Processing"); 217 218 timer.start("Spring Post Processing"); 219 PropertyPlaceholderConfigurer propertyPlaceholderConfigurer = new PropertyPlaceholderConfigurer(); 220 propertyPlaceholderConfigurer.setProperties(ConfigContext.getCurrentContextConfig().getProperties()); 221 propertyPlaceholderConfigurer.postProcessBeanFactory(ddBeans); 222 223 DictionaryBeanFactoryPostProcessor dictionaryBeanPostProcessor = 224 new DictionaryBeanFactoryPostProcessor(DataDictionary.this, ddBeans); 225 dictionaryBeanPostProcessor.postProcessBeanFactory(); 226 timer.stop(); 227 228 // post processes UIF beans for pulling out expressions within property values 229 timer.start("UIF Post Processing"); 230 UifBeanFactoryPostProcessor factoryPostProcessor = new UifBeanFactoryPostProcessor(); 231 factoryPostProcessor.postProcessBeanFactory(ddBeans); 232 timer.stop(); 233 234 timer.start("Instantiating DD Beans"); 235 ddBeans.preInstantiateSingletons(); 236 timer.stop(); 237 238 // Allow the DD to perform final post processing in a controlled order 239 // Unlike the Spring post processor, we will only call for these operations on the 240 // "top-level" beans and have them call post processing actions on embedded DD objects, if needed 241 timer.start("DD Post Processing"); 242 243 for (DataObjectEntry entry : ddBeans.getBeansOfType(DataObjectEntry.class).values()) { 244 entry.dataDictionaryPostProcessing(); 245 } 246 247 for (DocumentEntry entry : ddBeans.getBeansOfType(DocumentEntry.class).values()) { 248 entry.dataDictionaryPostProcessing(); 249 } 250 251 timer.stop(); 252 253 timer.start("Data Dictionary Indexing"); 254 ddIndex.run(); 255 timer.stop(); 256 257 // the UIF defaulting must be done before the UIF indexing but after the main DD data object indexing 258 if (ConfigContext.getCurrentContextConfig().getBooleanProperty(KRADConstants.Config.ENABLE_VIEW_AUTOGENERATION, false)) { 259 timer.start("UIF Defaulting"); 260 generateMissingInquiryDefinitions(); 261 generateMissingLookupDefinitions(); 262 timer.stop(); 263 } 264 265 timer.start("UIF Indexing"); 266 uifIndex.run(); 267 timer.stop(); 268 269 LOG.info("Completed Data Dictionary Post Processing"); 270 } 271 272 protected void generateMissingInquiryDefinitions() { 273 Collection<InquiryView> inquiryViewBeans = ddBeans.getBeansOfType(InquiryView.class).values(); 274 275 // Index all the inquiry views by the data object class so we can find them easily below 276 Map<Class<?>,InquiryView> defaultViewsByDataObjectClass = new HashMap<Class<?>, InquiryView>(); 277 278 for ( InquiryView view : inquiryViewBeans ) { 279 if ( view.getViewName().equals(UifConstants.DEFAULT_VIEW_NAME) ) { 280 defaultViewsByDataObjectClass.put(view.getDataObjectClassName(), view); 281 } 282 } 283 284 for (DataObjectEntry entry : ddBeans.getBeansOfType(DataObjectEntry.class).values()) { 285 // if an inquiry already exists, just ignore - we only default if none exist 286 if ( defaultViewsByDataObjectClass.containsKey(entry.getDataObjectClass())) { 287 continue; 288 } 289 290 // We only generate the inquiry if the metadata says to 291 if ( entry.getDataObjectMetadata() == null ) { 292 continue; 293 } 294 295 if ( !entry.getDataObjectMetadata().shouldAutoCreateUifViewOfType(UifAutoCreateViewType.INQUIRY)) { 296 continue; 297 } 298 299 // no inquiry exists and we want one to, create one 300 if ( LOG.isInfoEnabled() ) { 301 LOG.info( "Generating Inquiry View for : " + entry.getDataObjectClass() ); 302 } 303 304 String inquiryBeanName = entry.getDataObjectClass().getSimpleName()+"-InquiryView-default"; 305 306 InquiryView inquiryView = KRADServiceLocatorWeb.getUifDefaultingService().deriveInquiryViewFromMetadata(entry); 307 inquiryView.setId(inquiryBeanName); 308 inquiryView.setViewName(UifConstants.DEFAULT_VIEW_NAME); 309 310 ChildBeanDefinition inquiryBean = new ChildBeanDefinition("Uif-InquiryView"); 311 inquiryBean.setScope(BeanDefinition.SCOPE_SINGLETON); 312 inquiryBean.setAttribute("dataObjectClassName", inquiryView.getDataObjectClassName()); 313 inquiryBean.getPropertyValues().add("dataObjectClassName", inquiryView.getDataObjectClassName().getName()); 314 inquiryBean.setResourceDescription("Autogenerated From Metadata"); 315 ddBeans.registerBeanDefinition(inquiryBeanName, inquiryBean); 316 ddBeans.registerSingleton(inquiryBeanName, inquiryView); 317 } 318 } 319 320 protected void generateMissingLookupDefinitions() { 321 Collection<LookupView> lookupViewBeans = ddBeans.getBeansOfType(LookupView.class).values(); 322 // Index all the inquiry views by the data object class so we can find them easily below 323 Map<Class<?>,LookupView> defaultViewsByDataObjectClass = new HashMap<Class<?>, LookupView>(); 324 for ( LookupView view : lookupViewBeans ) { 325 if ( view.getViewName().equals(UifConstants.DEFAULT_VIEW_NAME) ) { 326 defaultViewsByDataObjectClass.put(view.getDataObjectClass(), view); 327 } 328 } 329 for (DataObjectEntry entry : ddBeans.getBeansOfType(DataObjectEntry.class).values()) { 330 // if an inquiry already exists, just ignore - we only default if none exist 331 if ( defaultViewsByDataObjectClass.containsKey(entry.getDataObjectClass())) { 332 continue; 333 } 334 // We only generate the inquiry if the metadata says to 335 if ( entry.getDataObjectMetadata() == null ) { 336 continue; 337 } 338 if ( !entry.getDataObjectMetadata().shouldAutoCreateUifViewOfType(UifAutoCreateViewType.LOOKUP)) { 339 continue; 340 } 341 // no inquiry exists and we want one to, create one 342 if ( LOG.isInfoEnabled() ) { 343 LOG.info( "Generating Lookup View for : " + entry.getDataObjectClass() ); 344 } 345 String lookupBeanName = entry.getDataObjectClass().getSimpleName()+"-LookupView-default"; 346 347 LookupView lookupView = KRADServiceLocatorWeb.getUifDefaultingService().deriveLookupViewFromMetadata(entry); 348 lookupView.setId(lookupBeanName); 349 lookupView.setViewName(UifConstants.DEFAULT_VIEW_NAME); 350 351 ChildBeanDefinition lookupBean = new ChildBeanDefinition(ComponentFactory.LOOKUP_VIEW); 352 lookupBean.setScope(BeanDefinition.SCOPE_SINGLETON); 353 lookupBean.setAttribute("dataObjectClassName", lookupView.getDataObjectClass()); 354 lookupBean.getPropertyValues().add("dataObjectClassName", lookupView.getDataObjectClass().getName()); 355 lookupBean.setResourceDescription("Autogenerated From Metadata"); 356 ddBeans.registerBeanDefinition(lookupBeanName, lookupBean); 357 ddBeans.registerSingleton(lookupBeanName, lookupView); 358 } 359 } 360 361 public void validateDD(boolean validateEbos) { 362 timer.start("Validation"); 363 DataDictionary.validateEBOs = validateEbos; 364 365 Validator.resetErrorReport(); 366 367 Map<String, DataObjectEntry> doBeans = ddBeans.getBeansOfType(DataObjectEntry.class); 368 for (DataObjectEntry entry : doBeans.values()) { 369 entry.completeValidation(new ValidationTrace()); 370 } 371 372 Map<String, DocumentEntry> docBeans = ddBeans.getBeansOfType(DocumentEntry.class); 373 for (DocumentEntry entry : docBeans.values()) { 374 entry.completeValidation(new ValidationTrace()); 375 } 376 377 List<ErrorReport> errorReports = Validator.getErrorReports(); 378 if (!errorReports.isEmpty()) { 379 boolean hasErrors = hasErrors(errorReports); 380 String errorReport = produceErrorReport(errorReports, hasErrors); 381 if (hasErrors) { 382 String message = "Errors during DD validation, failing validation.\n" + errorReport; 383 throw new DataDictionaryException(message); 384 } else { 385 String message = "Warnings during DD validation.\n" + errorReport; 386 LOG.warn(message); 387 } 388 } 389 390 timer.stop(); 391 } 392 393 private boolean hasErrors(List<ErrorReport> errorReports) { 394 for (ErrorReport err : errorReports) { 395 if (err.isError()) { 396 return true; 397 } 398 } 399 return false; 400 } 401 402 protected String produceErrorReport(List<ErrorReport> errorReports, boolean hasErrors) { 403 StringBuilder builder = new StringBuilder(); 404 builder.append("***********************************************************\n"); 405 if (hasErrors) { 406 builder.append("ERRORS REPORTED UPON DATA DICTIONARY VALIDATION\n"); 407 } else { 408 builder.append("WARNINGS REPORTED UPON DATA DICTIONARY VALIDATION\n"); 409 } 410 builder.append("***********************************************************\n"); 411 for (ErrorReport report : errorReports) { 412 builder.append(report.errorMessage()).append("\n"); 413 } 414 return builder.toString(); 415 } 416 417 public void validateDD() { 418 validateDD(true); 419 } 420 421 /** 422 * Adds a location of files or a individual resource to the data dictionary 423 * 424 * <p> 425 * The location can either be an XML file on the classpath or a file or folder location within the 426 * file system. If a folder location is given, the folder and all sub-folders will be traversed and any 427 * XML files will be added to the dictionary 428 * </p> 429 * 430 * @param namespaceCode - namespace the beans loaded from the location should be associated with 431 * @param location - classpath resource or file system location 432 * @throws IOException 433 */ 434 public void addConfigFileLocation(String namespaceCode, String location) throws IOException { 435 // add module to load order so we load in the order modules were configured 436 if (!moduleLoadOrder.contains(namespaceCode)) { 437 moduleLoadOrder.add(namespaceCode); 438 } 439 440 indexSource(namespaceCode, location); 441 } 442 443 /** 444 * Processes a given source for XML files to populate the dictionary with 445 * 446 * @param namespaceCode - namespace the beans loaded from the location should be associated with 447 * @param sourceName - a file system or classpath resource locator 448 * @throws IOException 449 */ 450 protected void indexSource(String namespaceCode, String sourceName) throws IOException { 451 if (sourceName == null) { 452 throw new DataDictionaryException("Source Name given is null"); 453 } 454 455 if (!sourceName.endsWith(".xml")) { 456 Resource resource = getFileResource(sourceName); 457 if (resource.exists()) { 458 try { 459 indexSource(namespaceCode, resource.getFile()); 460 } catch (IOException e) { 461 // ignore resources that exist and cause an error here 462 // they may be directories resident in jar files 463 LOG.debug("Skipped existing resource without absolute file path"); 464 } 465 } else { 466 LOG.warn("Could not find " + sourceName); 467 throw new DataDictionaryException("DD Resource " + sourceName + " not found"); 468 } 469 } else { 470 if (LOG.isDebugEnabled()) { 471 LOG.debug("adding sourceName " + sourceName + " "); 472 } 473 474 Resource resource = getFileResource(sourceName); 475 if (!resource.exists()) { 476 throw new DataDictionaryException("DD Resource " + sourceName + " not found"); 477 } 478 479 addModuleDictionaryFile(namespaceCode, sourceName); 480 } 481 } 482 483 protected Resource getFileResource(String sourceName) { 484 DefaultResourceLoader resourceLoader = new DefaultResourceLoader(ClassLoaderUtils.getDefaultClassLoader()); 485 486 return resourceLoader.getResource(sourceName); 487 } 488 489 protected void indexSource(String namespaceCode, File dir) { 490 for (File file : dir.listFiles()) { 491 if (file.isDirectory()) { 492 indexSource(namespaceCode, file); 493 } else if (file.getName().endsWith(".xml")) { 494 addModuleDictionaryFile(namespaceCode, "file:" + file.getAbsolutePath()); 495 } else { 496 if (LOG.isDebugEnabled()) { 497 LOG.debug("Skipping non xml file " + file.getAbsolutePath() + " in DD load"); 498 } 499 } 500 } 501 } 502 503 /** 504 * Adds a file location to the list of dictionary files for the given namespace code 505 * 506 * @param namespaceCode - namespace to add location for 507 * @param location - file or resource location to add 508 */ 509 protected void addModuleDictionaryFile(String namespaceCode, String location) { 510 List<String> moduleFileLocations = new ArrayList<String>(); 511 if (moduleDictionaryFiles.containsKey(namespaceCode)) { 512 moduleFileLocations = moduleDictionaryFiles.get(namespaceCode); 513 } 514 moduleFileLocations.add(location); 515 516 moduleDictionaryFiles.put(namespaceCode, moduleFileLocations); 517 } 518 519 /** 520 * Mapping of namespace codes to dictionary files that are associated with 521 * that namespace 522 * 523 * @return Map<String, List<String>> where map key is namespace code, and value is list of dictionary 524 * file locations 525 */ 526 public Map<String, List<String>> getModuleDictionaryFiles() { 527 return moduleDictionaryFiles; 528 } 529 530 /** 531 * Setter for the map of module dictionary files 532 * 533 * @param moduleDictionaryFiles 534 */ 535 public void setModuleDictionaryFiles(Map<String, List<String>> moduleDictionaryFiles) { 536 this.moduleDictionaryFiles = moduleDictionaryFiles; 537 } 538 539 /** 540 * Order modules should be loaded into the dictionary 541 * 542 * <p> 543 * Modules are loaded in the order they are found in this list. If not explicity set, they will be loaded in 544 * the order their dictionary file locations are added 545 * </p> 546 * 547 * @return List<String> list of namespace codes indicating the module load order 548 */ 549 public List<String> getModuleLoadOrder() { 550 return moduleLoadOrder; 551 } 552 553 /** 554 * Setter for the list of namespace codes indicating the module load order 555 * 556 * @param moduleLoadOrder 557 */ 558 public void setModuleLoadOrder(List<String> moduleLoadOrder) { 559 this.moduleLoadOrder = moduleLoadOrder; 560 } 561 562 /** 563 * Sets the DataDictionaryMapper 564 * 565 * @param mapper the datadictionary mapper 566 */ 567 public void setDataDictionaryMapper(DataDictionaryMapper mapper) { 568 this.ddMapper = mapper; 569 } 570 571 /** 572 * @param className 573 * @return BusinessObjectEntry for the named class, or null if none exists 574 */ 575 @Deprecated 576 public BusinessObjectEntry getBusinessObjectEntry(String className) { 577 return ddMapper.getBusinessObjectEntry(ddIndex, className); 578 } 579 580 /** 581 * @param className 582 * @return BusinessObjectEntry for the named class, or null if none exists 583 */ 584 public DataObjectEntry getDataObjectEntry(String className) { 585 return ddMapper.getDataObjectEntry(ddIndex, className); 586 } 587 588 /** 589 * This method gets the business object entry for a concrete class 590 * 591 * @param className 592 * @return business object entry 593 */ 594 public BusinessObjectEntry getBusinessObjectEntryForConcreteClass(String className) { 595 return ddMapper.getBusinessObjectEntryForConcreteClass(ddIndex, className); 596 } 597 598 /** 599 * @return List of businessObject classnames 600 */ 601 public List<String> getBusinessObjectClassNames() { 602 return ddMapper.getBusinessObjectClassNames(ddIndex); 603 } 604 605 /** 606 * @return Map of (classname, BusinessObjectEntry) pairs 607 */ 608 public Map<String, BusinessObjectEntry> getBusinessObjectEntries() { 609 return ddMapper.getBusinessObjectEntries(ddIndex); 610 } 611 612 public Map<String, DataObjectEntry> getDataObjectEntries() { 613 return ddMapper.getDataObjectEntries(ddIndex); 614 } 615 616 /** 617 * @param className 618 * @return DataDictionaryEntryBase for the named class, or null if none 619 * exists 620 */ 621 public DataDictionaryEntry getDictionaryObjectEntry(String className) { 622 return ddMapper.getDictionaryObjectEntry(ddIndex, className); 623 } 624 625 /** 626 * Returns the KNS document entry for the given lookup key. The documentTypeDDKey is interpreted 627 * successively in the following ways until a mapping is found (or none if found): 628 * <ol> 629 * <li>KEW/workflow document type</li> 630 * <li>business object class name</li> 631 * <li>maintainable class name</li> 632 * </ol> 633 * This mapping is compiled when DataDictionary files are parsed on startup (or demand). Currently this 634 * means the mapping is static, and one-to-one (one KNS document maps directly to one and only 635 * one key). 636 * 637 * @param documentTypeDDKey the KEW/workflow document type name 638 * @return the KNS DocumentEntry if it exists 639 */ 640 public DocumentEntry getDocumentEntry(String documentTypeDDKey) { 641 return ddMapper.getDocumentEntry(ddIndex, documentTypeDDKey); 642 } 643 644 /** 645 * Note: only MaintenanceDocuments are indexed by businessObject Class 646 * 647 * This is a special case that is referenced in one location. Do we need 648 * another map for this stuff?? 649 * 650 * @param businessObjectClass 651 * @return DocumentEntry associated with the given Class, or null if there 652 * is none 653 */ 654 public MaintenanceDocumentEntry getMaintenanceDocumentEntryForBusinessObjectClass(Class<?> businessObjectClass) { 655 return ddMapper.getMaintenanceDocumentEntryForBusinessObjectClass(ddIndex, businessObjectClass); 656 } 657 658 public Map<String, DocumentEntry> getDocumentEntries() { 659 return ddMapper.getDocumentEntries(ddIndex); 660 } 661 662 /** 663 * Returns the View entry identified by the given id 664 * 665 * @param viewId unique id for view 666 * @return View instance associated with the id 667 */ 668 public View getViewById(String viewId) { 669 return ddMapper.getViewById(uifIndex, viewId); 670 } 671 672 /** 673 * Returns the View entry identified by the given id, meant for view readonly 674 * access (not running the lifecycle but just checking configuration) 675 * 676 * @param viewId unique id for view 677 * @return View instance associated with the id 678 */ 679 public View getImmutableViewById(String viewId) { 680 return ddMapper.getImmutableViewById(uifIndex, viewId); 681 } 682 683 /** 684 * Returns View instance identified by the view type name and index 685 * 686 * @param viewTypeName - type name for the view 687 * @param indexKey - Map of index key parameters, these are the parameters the 688 * indexer used to index the view initially and needs to identify 689 * an unique view instance 690 * @return View instance that matches the given index 691 */ 692 public View getViewByTypeIndex(ViewType viewTypeName, Map<String, String> indexKey) { 693 return ddMapper.getViewByTypeIndex(uifIndex, viewTypeName, indexKey); 694 } 695 696 /** 697 * Returns the view id for the view that matches the given view type and index 698 * 699 * @param viewTypeName type name for the view 700 * @param indexKey Map of index key parameters, these are the parameters the 701 * indexer used to index the view initially and needs to identify 702 * an unique view instance 703 * @return id for the view that matches the view type and index or null if a match is not found 704 */ 705 public String getViewIdByTypeIndex(ViewType viewTypeName, Map<String, String> indexKey) { 706 return ddMapper.getViewIdByTypeIndex(uifIndex, viewTypeName, indexKey); 707 } 708 709 /** 710 * Indicates whether a <code>View</code> exists for the given view type and index information 711 * 712 * @param viewTypeName - type name for the view 713 * @param indexKey - Map of index key parameters, these are the parameters the 714 * indexer used to index the view initially and needs to identify 715 * an unique view instance 716 * @return boolean true if view exists, false if not 717 */ 718 public boolean viewByTypeExist(ViewType viewTypeName, Map<String, String> indexKey) { 719 return ddMapper.viewByTypeExist(uifIndex, viewTypeName, indexKey); 720 } 721 722 /** 723 * Gets all <code>View</code> prototypes configured for the given view type 724 * name 725 * 726 * @param viewTypeName - view type name to retrieve 727 * @return List<View> view prototypes with the given type name, or empty 728 * list 729 */ 730 public List<View> getViewsForType(ViewType viewTypeName) { 731 return ddMapper.getViewsForType(uifIndex, viewTypeName); 732 } 733 734 /** 735 * Returns an object from the dictionary by its spring bean name 736 * 737 * @param beanName id or name for the bean definition 738 * @return Object object instance created or the singleton being maintained 739 */ 740 public Object getDictionaryBean(final String beanName) { 741 return ddBeans.getBean(beanName); 742 } 743 744 /** 745 * Indicates whether the data dictionary contains a bean with the given id 746 * 747 * @param id id of the bean to check for 748 * @return boolean true if dictionary contains bean, false otherwise 749 */ 750 public boolean containsDictionaryBean(String id) { 751 return ddBeans.containsBean(id); 752 } 753 754 /** 755 * Returns a prototype object from the dictionary by its spring bean name 756 * 757 * @param beanName id or name for the bean definition 758 * @return Object object instance created 759 */ 760 public Object getDictionaryPrototype(final String beanName) { 761 if (!ddBeans.isPrototype(beanName)) { 762 throw new IllegalArgumentException("Bean name " + beanName 763 + " doesn't refer to a prototype bean in the data dictionary"); 764 } 765 766 return getDictionaryBean(beanName); 767 } 768 769 /** 770 * Returns a property value for the bean with the given name from the dictionary. 771 * 772 * @param beanName id or name for the bean definition 773 * @param propertyName name of the property to retrieve, must be a valid property configured on 774 * the bean definition 775 * @return Object property value for property 776 */ 777 public Object getDictionaryBeanProperty(String beanName, String propertyName) { 778 Object bean = ddBeans.getSingleton(beanName); 779 if (bean != null) { 780 return ObjectPropertyUtils.getPropertyValue(bean, propertyName); 781 } 782 783 BeanDefinition beanDefinition = ddBeans.getMergedBeanDefinition(beanName); 784 785 if (beanDefinition == null) { 786 throw new RuntimeException("Unable to get bean for bean name: " + beanName); 787 } 788 789 PropertyValues pvs = beanDefinition.getPropertyValues(); 790 if (pvs.contains(propertyName)) { 791 PropertyValue propertyValue = pvs.getPropertyValue(propertyName); 792 793 Object value; 794 if (propertyValue.isConverted()) { 795 value = propertyValue.getConvertedValue(); 796 } else if (propertyValue.getValue() instanceof String) { 797 String unconvertedValue = (String) propertyValue.getValue(); 798 Scope scope = ddBeans.getRegisteredScope(beanDefinition.getScope()); 799 BeanExpressionContext beanExpressionContext = new BeanExpressionContext(ddBeans, scope); 800 801 value = ddBeans.getBeanExpressionResolver().evaluate(unconvertedValue, beanExpressionContext); 802 } else { 803 value = propertyValue.getValue(); 804 } 805 806 return value; 807 } 808 809 return null; 810 } 811 812 /** 813 * Retrieves the configured property values for the view bean definition associated with the given id 814 * 815 * <p> 816 * Since constructing the View object can be expensive, when metadata only is needed this method can be used 817 * to retrieve the configured property values. Note this looks at the merged bean definition 818 * </p> 819 * 820 * @param viewId - id for the view to retrieve 821 * @return PropertyValues configured on the view bean definition, or null if view is not found 822 */ 823 public PropertyValues getViewPropertiesById(String viewId) { 824 return ddMapper.getViewPropertiesById(uifIndex, viewId); 825 } 826 827 /** 828 * Retrieves the configured property values for the view bean definition associated with the given type and 829 * index 830 * 831 * <p> 832 * Since constructing the View object can be expensive, when metadata only is needed this method can be used 833 * to retrieve the configured property values. Note this looks at the merged bean definition 834 * </p> 835 * 836 * @param viewTypeName - type name for the view 837 * @param indexKey - Map of index key parameters, these are the parameters the indexer used to index 838 * the view initially and needs to identify an unique view instance 839 * @return PropertyValues configured on the view bean definition, or null if view is not found 840 */ 841 public PropertyValues getViewPropertiesByType(ViewType viewTypeName, Map<String, String> indexKey) { 842 return ddMapper.getViewPropertiesByType(uifIndex, viewTypeName, indexKey); 843 } 844 845 /** 846 * Retrieves the list of dictionary bean names that are associated with the given namespace code 847 * 848 * @param namespaceCode - namespace code to retrieve associated bean names for 849 * @return List<String> bean names associated with the namespace 850 */ 851 public List<String> getBeanNamesForNamespace(String namespaceCode) { 852 List<String> namespaceBeans = new ArrayList<String>(); 853 854 Map<String, List<String>> dictionaryBeansByNamespace = ddIndex.getDictionaryBeansByNamespace(); 855 if (dictionaryBeansByNamespace.containsKey(namespaceCode)) { 856 namespaceBeans = dictionaryBeansByNamespace.get(namespaceCode); 857 } 858 859 return namespaceBeans; 860 } 861 862 /** 863 * Retrieves the namespace code the given bean name is associated with 864 * 865 * @param beanName - name of the dictionary bean to find namespace code for 866 * @return String namespace code the bean is associated with, or null if a namespace was not found 867 */ 868 public String getNamespaceForBeanDefinition(String beanName) { 869 String beanNamespace = null; 870 871 Map<String, List<String>> dictionaryBeansByNamespace = ddIndex.getDictionaryBeansByNamespace(); 872 for (Map.Entry<String, List<String>> moduleDefinitions : dictionaryBeansByNamespace.entrySet()) { 873 List<String> namespaceBeans = moduleDefinitions.getValue(); 874 if (namespaceBeans.contains(beanName)) { 875 beanNamespace = moduleDefinitions.getKey(); 876 break; 877 } 878 } 879 880 return beanNamespace; 881 } 882 883 /** 884 * @param targetClass 885 * @param propertyName 886 * @return true if the given propertyName names a property of the given class 887 * @throws CompletionException if there is a problem accessing the named property on the given class 888 */ 889 public static boolean isPropertyOf(Class targetClass, String propertyName) { 890 if (targetClass == null) { 891 throw new IllegalArgumentException("invalid (null) targetClass"); 892 } 893 if (StringUtils.isBlank(propertyName)) { 894 throw new IllegalArgumentException("invalid (blank) propertyName"); 895 } 896 try { 897 PropertyDescriptor propertyDescriptor = buildReadDescriptor(targetClass, propertyName); 898 899 return propertyDescriptor != null; 900 } catch ( Exception ex ) { 901 LOG.error( "Exception while obtaining property descriptor for " + targetClass.getName() + "." + propertyName, ex ); 902 return false; 903 } 904 } 905 906 /** 907 * @param targetClass 908 * @param propertyName 909 * @return true if the given propertyName names a Collection property of the given class 910 * @throws CompletionException if there is a problem accessing the named property on the given class 911 */ 912 public static boolean isCollectionPropertyOf(Class targetClass, String propertyName) { 913 boolean isCollectionPropertyOf = false; 914 915 PropertyDescriptor propertyDescriptor = buildReadDescriptor(targetClass, propertyName); 916 if (propertyDescriptor != null) { 917 Class clazz = propertyDescriptor.getPropertyType(); 918 919 if ((clazz != null) && Collection.class.isAssignableFrom(clazz)) { 920 isCollectionPropertyOf = true; 921 } 922 } 923 924 return isCollectionPropertyOf; 925 } 926 927 public static LegacyDataAdapter getLegacyDataAdapter() { 928 if (legacyDataAdapter == null) { 929 legacyDataAdapter = KRADServiceLocatorWeb.getLegacyDataAdapter(); 930 } 931 return legacyDataAdapter; 932 } 933 934 /** 935 * This method determines the Class of the attributeName passed in. Null will be returned if the member is not 936 * available, or if 937 * a reflection exception is thrown. 938 * 939 * @param boClass - Class that the attributeName property exists in. 940 * @param attributeName - Name of the attribute you want a class for. 941 * @return The Class of the attributeName, if the attribute exists on the rootClass. Null otherwise. 942 */ 943 public static Class getAttributeClass(Class boClass, String attributeName) { 944 945 // fail loudly if the attributeName isnt a member of rootClass 946 if (!isPropertyOf(boClass, attributeName)) { 947 throw new AttributeValidationException( 948 "unable to find attribute '" + attributeName + "' in rootClass '" + boClass.getName() + "'"); 949 } 950 951 //Implementing Externalizable Business Object Services... 952 //The boClass can be an interface, hence handling this separately, 953 //since the original method was throwing exception if the class could not be instantiated. 954 if (boClass.isInterface()) { 955 return getAttributeClassWhenBOIsInterface(boClass, attributeName); 956 } else { 957 return getAttributeClassWhenBOIsClass(boClass, attributeName); 958 } 959 960 } 961 962 /** 963 * This method gets the property type of the given attributeName when the bo class is a concrete class 964 * 965 * @param boClass 966 * @param attributeName 967 * @return property type 968 */ 969 private static Class<?> getAttributeClassWhenBOIsClass(Class<?> boClass, String attributeName) { 970 Object boInstance; 971 try { 972 973 //KULRICE-11351 should not differentiate between primitive types and their wrappers during DD validation 974 if (boClass.isPrimitive()) { 975 boClass = ClassUtils.primitiveToWrapper(boClass); 976 } 977 978 boInstance = boClass.newInstance(); 979 } catch (Exception e) { 980 throw new RuntimeException("Unable to instantiate Data Object: " + boClass, e); 981 } 982 983 // attempt to retrieve the class of the property 984 try { 985 return getLegacyDataAdapter().getPropertyType(boInstance, attributeName); 986 } catch (Exception e) { 987 throw new RuntimeException( 988 "Unable to determine property type for: " + boClass.getName() + "." + attributeName, e); 989 } 990 } 991 992 /** 993 * This method gets the property type of the given attributeName when the bo class is an interface 994 * This method will also work if the bo class is not an interface, 995 * but that case requires special handling, hence a separate method getAttributeClassWhenBOIsClass 996 * 997 * @param boClass 998 * @param attributeName 999 * @return property type 1000 */ 1001 private static Class<?> getAttributeClassWhenBOIsInterface(Class<?> boClass, String attributeName) { 1002 if (boClass == null) { 1003 throw new IllegalArgumentException("invalid (null) boClass"); 1004 } 1005 if (StringUtils.isBlank(attributeName)) { 1006 throw new IllegalArgumentException("invalid (blank) attributeName"); 1007 } 1008 1009 PropertyDescriptor propertyDescriptor = null; 1010 1011 String[] intermediateProperties = attributeName.split("\\."); 1012 int lastLevel = intermediateProperties.length - 1; 1013 Class currentClass = boClass; 1014 1015 for (int i = 0; i <= lastLevel; ++i) { 1016 1017 String currentPropertyName = intermediateProperties[i]; 1018 propertyDescriptor = buildSimpleReadDescriptor(currentClass, currentPropertyName); 1019 1020 if (propertyDescriptor != null) { 1021 1022 Class propertyType = propertyDescriptor.getPropertyType(); 1023 if (getLegacyDataAdapter().isExtensionAttribute(currentClass, currentPropertyName, propertyType)) { 1024 propertyType = getLegacyDataAdapter().getExtensionAttributeClass(currentClass, currentPropertyName); 1025 } 1026 if (Collection.class.isAssignableFrom(propertyType)) { 1027 // TODO: determine property type using generics type definition 1028 throw new AttributeValidationException( 1029 "Can't determine the Class of Collection elements because when the business object is an (possibly ExternalizableBusinessObject) interface."); 1030 } else { 1031 currentClass = propertyType; 1032 } 1033 } else { 1034 throw new AttributeValidationException( 1035 "Can't find getter method of " + boClass.getName() + " for property " + attributeName); 1036 } 1037 } 1038 return currentClass; 1039 } 1040 1041 /** 1042 * This method determines the Class of the elements in the collectionName passed in. 1043 * 1044 * @param boClass Class that the collectionName collection exists in. 1045 * @param collectionName the name of the collection you want the element class for 1046 * @return collection element type 1047 */ 1048 public static Class getCollectionElementClass(Class boClass, String collectionName) { 1049 if (boClass == null) { 1050 throw new IllegalArgumentException("invalid (null) boClass"); 1051 } 1052 if (StringUtils.isBlank(collectionName)) { 1053 throw new IllegalArgumentException("invalid (blank) collectionName"); 1054 } 1055 1056 PropertyDescriptor propertyDescriptor = null; 1057 1058 String[] intermediateProperties = collectionName.split("\\."); 1059 Class currentClass = boClass; 1060 1061 for (int i = 0; i < intermediateProperties.length; ++i) { 1062 1063 String currentPropertyName = intermediateProperties[i]; 1064 propertyDescriptor = buildSimpleReadDescriptor(currentClass, currentPropertyName); 1065 1066 if (propertyDescriptor != null) { 1067 1068 Class type = propertyDescriptor.getPropertyType(); 1069 if (Collection.class.isAssignableFrom(type)) { 1070 currentClass = getLegacyDataAdapter().determineCollectionObjectType(currentClass, currentPropertyName); 1071 } else { 1072 currentClass = propertyDescriptor.getPropertyType(); 1073 } 1074 } 1075 } 1076 1077 return currentClass; 1078 } 1079 1080 static private Map<String, Map<String, PropertyDescriptor>> cache = 1081 new TreeMap<String, Map<String, PropertyDescriptor>>(); 1082 1083 /** 1084 * @param propertyClass 1085 * @param propertyName 1086 * @return PropertyDescriptor for the getter for the named property of the given class, if one exists. 1087 */ 1088 public static PropertyDescriptor buildReadDescriptor(Class propertyClass, String propertyName) { 1089 if (propertyClass == null) { 1090 throw new IllegalArgumentException("invalid (null) propertyClass"); 1091 } 1092 if (StringUtils.isBlank(propertyName)) { 1093 throw new IllegalArgumentException("invalid (blank) propertyName"); 1094 } 1095 1096 PropertyDescriptor propertyDescriptor = null; 1097 1098 String[] intermediateProperties = propertyName.split("\\."); 1099 int lastLevel = intermediateProperties.length - 1; 1100 Class currentClass = propertyClass; 1101 1102 for (int i = 0; i <= lastLevel; ++i) { 1103 1104 String currentPropertyName = intermediateProperties[i]; 1105 propertyDescriptor = buildSimpleReadDescriptor(currentClass, currentPropertyName); 1106 1107 if (i < lastLevel) { 1108 1109 if (propertyDescriptor != null) { 1110 1111 Class propertyType = propertyDescriptor.getPropertyType(); 1112 if (getLegacyDataAdapter().isExtensionAttribute(currentClass, currentPropertyName, propertyType)) { 1113 propertyType = getLegacyDataAdapter().getExtensionAttributeClass(currentClass, 1114 currentPropertyName); 1115 } 1116 if (Collection.class.isAssignableFrom(propertyType)) { 1117 currentClass = getLegacyDataAdapter().determineCollectionObjectType(currentClass, currentPropertyName); 1118 } else { 1119 currentClass = propertyType; 1120 } 1121 1122 } 1123 1124 } 1125 1126 } 1127 1128 return propertyDescriptor; 1129 } 1130 1131 /** 1132 * @param propertyClass 1133 * @param propertyName 1134 * @return PropertyDescriptor for the getter for the named property of the given class, if one exists. 1135 */ 1136 public static PropertyDescriptor buildSimpleReadDescriptor(Class propertyClass, String propertyName) { 1137 if (propertyClass == null) { 1138 throw new IllegalArgumentException("invalid (null) propertyClass"); 1139 } 1140 if (StringUtils.isBlank(propertyName)) { 1141 throw new IllegalArgumentException("invalid (blank) propertyName"); 1142 } 1143 1144 PropertyDescriptor p = null; 1145 1146 // check to see if we've cached this descriptor already. if yes, return true. 1147 String propertyClassName = propertyClass.getName(); 1148 Map<String, PropertyDescriptor> m = cache.get(propertyClassName); 1149 if (null != m) { 1150 p = m.get(propertyName); 1151 if (null != p) { 1152 return p; 1153 } 1154 } 1155 1156 // Use PropertyUtils.getPropertyDescriptors instead of manually constructing PropertyDescriptor because of 1157 // issues with introspection and generic/co-variant return types 1158 // See https://issues.apache.org/jira/browse/BEANUTILS-340 for more details 1159 1160 PropertyDescriptor[] descriptors = PropertyUtils.getPropertyDescriptors(propertyClass); 1161 if (ArrayUtils.isNotEmpty(descriptors)) { 1162 for (PropertyDescriptor descriptor : descriptors) { 1163 if (descriptor.getName().equals(propertyName)) { 1164 p = descriptor; 1165 } 1166 } 1167 } 1168 1169 // cache the property descriptor if we found it. 1170 if (p != null) { 1171 if (m == null) { 1172 m = new TreeMap<String, PropertyDescriptor>(); 1173 cache.put(propertyClassName, m); 1174 } 1175 m.put(propertyName, p); 1176 } 1177 1178 return p; 1179 } 1180 1181 public Set<InactivationBlockingMetadata> getAllInactivationBlockingMetadatas(Class blockedClass) { 1182 return ddMapper.getAllInactivationBlockingMetadatas(ddIndex, blockedClass); 1183 } 1184 1185 /** 1186 * This method gathers beans of type BeanOverride and invokes each one's performOverride() method. 1187 */ 1188 // KULRICE-4513 1189 public void performBeanOverrides() { 1190 timer.start("Processing BeanOverride beans"); 1191 Collection<BeanOverride> beanOverrides = ddBeans.getBeansOfType(BeanOverride.class).values(); 1192 1193 if (beanOverrides.isEmpty()) { 1194 LOG.info("DataDictionary.performOverrides(): No beans to override"); 1195 } 1196 for (BeanOverride beanOverride : beanOverrides) { 1197 1198 Object bean = ddBeans.getBean(beanOverride.getBeanName()); 1199 beanOverride.performOverride(bean); 1200 LOG.info("DataDictionary.performOverrides(): Performing override on bean: " + bean.toString()); 1201 } 1202 timer.stop(); 1203 // This is the last hook we have upon startup, so pretty-print the results here 1204 LOG.info( "\n" + timer.prettyPrint() ); 1205 } 1206 1207}