001/* 002 * Copyright (c) 2003-2010 The Regents of the University of California. 003 * All rights reserved. 004 * 005 * '$Author: crawl $' 006 * '$Date: 2012-06-14 16:45:38 +0000 (Thu, 14 Jun 2012) $' 007 * '$Revision: 29944 $' 008 * 009 * Permission is hereby granted, without written agreement and without 010 * license or royalty fees, to use, copy, modify, and distribute this 011 * software and its documentation for any purpose, provided that the above 012 * copyright notice and the following two paragraphs appear in all copies 013 * of this software. 014 * 015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 019 * SUCH DAMAGE. 020 * 021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 026 * ENHANCEMENTS, OR MODIFICATIONS. 027 * 028 */ 029 030package org.kepler.configuration; 031 032import java.io.File; 033import java.io.FileInputStream; 034import java.util.ArrayList; 035import java.util.HashMap; 036import java.util.Iterator; 037import java.util.List; 038import java.util.Locale; 039import java.util.Map; 040import java.util.Vector; 041 042import org.apache.commons.configuration.ConfigurationException; 043import org.apache.commons.configuration.XMLConfiguration; 044import org.apache.commons.configuration.tree.ConfigurationNode; 045import org.apache.commons.logging.Log; 046import org.apache.commons.logging.LogFactory; 047import org.kepler.build.modules.Module; 048import org.kepler.build.modules.ModuleTree; 049import org.kepler.util.DotKeplerManager; 050 051/** 052 * An interface to the deserialization methods required by the configuration 053 * manager to deserialize properties 054 */ 055public class CommonsConfigurationReader implements ConfigurationReader 056{ 057 private ConfigurationWriter configurationWriter; 058 059 /** Logging. */ 060 private final static Log _log = LogFactory.getLog(CommonsConfigurationReader.class); 061 062 /** True if log level is set to DEBUG. */ 063 private final static boolean _isDebugging = _log.isDebugEnabled(); 064 065 /** 066 * constructor 067 */ 068 public CommonsConfigurationReader() 069 { 070 } 071 072 /** 073 * constructor 074 */ 075 public CommonsConfigurationReader(ConfigurationWriter configurationWriter) 076 { 077 this.configurationWriter = configurationWriter; 078 } 079 080 /** 081 * load all configurations for a given module 082 */ 083 public List<RootConfigurationProperty> loadConfigurations(Module m) 084 throws ConfigurationManagerException 085 { 086 // NOTE: the following are for testing. Oracle JDK appears to 087 // ignore the value of $LANG for Locale.getDefault(). 088 //Locale l = new Locale("en" ,"US"); 089 //Locale l = new Locale("fi" ,"FI"); 090 //Locale l = new Locale("zh", "TW"); 091 //return loadConfigurations(m, l); //Locale.getDefault()); 092 return loadConfigurations(m, Locale.getDefault()); 093 } 094 095 /** 096 * load all configurations for a given module 097 */ 098 public List<RootConfigurationProperty> loadConfigurations(Module m, Locale l) 099 throws ConfigurationManagerException 100 { 101 Vector<RootConfigurationProperty> configs = new Vector<RootConfigurationProperty>(); 102 File configDir = m.getConfigurationsDir(); 103 if(configDir == null || !configDir.exists()) 104 { 105 return null; 106 } 107 108 try 109 { 110 File[] configFiles = configDir.listFiles(); 111 //set the name of this config prop as the name of the config file 112 for(File f : configFiles) 113 { 114 RootConfigurationProperty cp = null; 115 if(!f.isDirectory()) 116 { //try to open each file with the commons reader 117 if(f.getName().endsWith(".xml")) 118 { //use the xml reader 119 //System.out.println("loading config file " + f.getName() + " for module " + m.getName()); 120 cp = loadConfiguration(m, f, l); 121 //if(cp == null) { System.out.println("is NULL"); } 122 } 123 } 124 125 if(cp != null) 126 { 127 configs.add(cp); 128 } 129 } 130 } 131 catch(Exception e) 132 { 133 e.printStackTrace(); 134 throw new ConfigurationManagerException( 135 "Error loading configuration: " + e.getMessage()); 136 } 137 138 return configs; 139 } 140 141 /** 142 * load the configuration for a single file in a module 143 */ 144 public RootConfigurationProperty loadConfiguration(Module m, File f) 145 throws ConfigurationManagerException 146 { 147 return loadConfiguration(m, f, Locale.getDefault()); 148 } 149 150 /** 151 * load the configuration for a single file in a module 152 */ 153 public RootConfigurationProperty loadConfiguration(Module m, File f, Locale l) 154 throws ConfigurationManagerException 155 { 156 try 157 { 158 boolean loadfile = loadThisFile(f, l); 159 //System.out.println("Should we load " + f.getName() + 160 //" for locale " + l.toString() + " : " + loadfile); 161 if(!loadfile) 162 { //don't load this file if it's not of the correct locale 163 return null; 164 } 165 166 167 f = ConfigurationManager.getOverwriteFile(m, f); 168 169 XMLConfiguration xmlconfig = new XMLConfiguration(); 170 xmlconfig.setDelimiterParsingDisabled(true); 171 172 FileInputStream stream = null; 173 try 174 { 175 stream = new FileInputStream(f); 176 xmlconfig.load(stream); 177 } 178 finally 179 { 180 if(stream != null) 181 { 182 stream.close(); 183 } 184 } 185 186 //David Welker: Adding configuration directives 187 boolean mustSave = applyConfigurationDirectives(m, f, xmlconfig); 188 189 RootConfigurationProperty cp = new RootConfigurationProperty(m, f); 190 //set the default namespace 191 ConfigurationNamespace namespace = new ConfigurationNamespace( 192 ConfigurationManager.removeLocaleDesignator( 193 ConfigurationManager.removeFileExtension(f.getName()))); 194 cp.setNamespace(namespace); 195 196 ConfigurationNode rootCN = xmlconfig.getRootNode(); 197 198 //check to see if there is a namsepace element and use that if there is 199 if(rootCN.getChild(0).getName().equals("namespace")) 200 { 201 String ns = (String)rootCN.getChild(0).getValue(); 202 namespace = new ConfigurationNamespace(ns); 203 cp.setNamespace(namespace); 204 } 205 //create the config property 206 ConfigurationProperty deserializedProp = getConfiguration(rootCN, 207 m, namespace); 208 209 cp.addProperty(deserializedProp); 210 211 if( mustSave && configurationWriter != null) 212 configurationWriter.writeConfiguration(cp); 213 214 return cp; 215 } 216 catch(Exception e) 217 { 218 e.printStackTrace(); 219 throw new ConfigurationManagerException("Error loading configuration file " + 220 f.getPath() + ": " + e.getMessage()); 221 } 222 } 223 224 /** 225 * Author: David Welker 226 * 227 * This method will apply any previously unrun configuration directives to the configuration. 228 * Note: Remove configuration and change configuration do not do anything yet, pending further discussion. 229 * @param xmlconfig 230 * @return Returns true if a configuration directive is applied. 231 */ 232 private boolean applyConfigurationDirectives(Module module, File configurationFile, XMLConfiguration xmlconfig) throws ConfigurationException 233 { 234 boolean directiveApplied = false; 235 File configurationDirectivesDir = module.getConfigurationDirectivesDir(); 236 if( !configurationDirectivesDir.isDirectory() ) 237 return directiveApplied; 238 String configurationFilename = configurationFile.getName(); 239 boolean useDefaultNames = configurationFilename.equals("configuration.xml"); 240 String addDirectivesFilename = useDefaultNames ? "add.xml" : configurationFilename + "-add.xml"; 241 String changeDirectivesFilename = useDefaultNames ? "change.xml" : configurationFilename + "-change.xml"; 242 String removeDirecivesFilename = useDefaultNames ? "remove.xml" : configurationFilename + "-remove.xml"; 243 File addDirectivesFile = new File(configurationDirectivesDir, addDirectivesFilename); 244 if( addDirectivesFile.exists() ) 245 directiveApplied = applyAddDirectives(module, addDirectivesFile, xmlconfig); 246 File changeDirectivesFile = new File(configurationDirectivesDir, changeDirectivesFilename); 247 if( changeDirectivesFile.exists() ) 248 directiveApplied = applyChangeDirectives(module, changeDirectivesFile, xmlconfig); 249 File removeDirectivesFile = new File(configurationDirectivesDir, removeDirecivesFilename); 250 if( removeDirectivesFile.exists() ) 251 directiveApplied = applyRemoveDirectives(module, removeDirectivesFile, xmlconfig); 252 return directiveApplied; 253 } 254 255 public static String trimmedKey(String key) 256 { 257 if( !key.contains(".") ) 258 return key; 259 return key.substring(key.indexOf('.') + 1, key.length()); 260 } 261 262 public static boolean addMatch(List<String> addKeys, List<String> addedKeys, XMLConfiguration addXmlConfig, XMLConfiguration addedXmlConfig) 263 { 264 if( addKeys.size() != addedKeys.size() ) 265 return false; 266 267 HashMap<String, String> matchedKeys = new HashMap<String,String>(); 268 for( String addKey : addKeys ) 269 { 270 boolean keyMatchFound = false; 271 for( String addedKey : addedKeys ) 272 { 273 if( trimmedKey(addKey).equals(trimmedKey(addedKey)) ) 274 { 275 keyMatchFound = true; 276 matchedKeys.put(addKey, addedKey); 277 } 278 } 279 if( !keyMatchFound ) 280 return false; 281 } 282 283 for( Map.Entry<String,String> entry : matchedKeys.entrySet() ) 284 { 285 Object addProperty = addXmlConfig.getProperty(entry.getKey()); 286 Object addedProperty = addedXmlConfig.getProperty(entry.getValue()); 287 288 if( !addProperty.equals(addedProperty) ) 289 return false; 290 } 291 292 return true; 293 } 294 295 296 /** 297 * Author: David Welker 298 * 299 * This method applies unrun add directives to the configuration. 300 * @param addXml 301 * @param xmlconfig 302 */ 303 private boolean applyAddDirectives(Module module, File addXml, XMLConfiguration xmlconfig) throws ConfigurationException 304 { 305 String addXmlFilename = addXml.getName(); 306 String addedXmlFilename = addXmlFilename.substring(0,addXmlFilename.length()-4) + "ed.xml"; 307 308 File addedXml = new File(DotKeplerManager.getInstance().getModuleConfigurationDirectory(module.getName()), addedXmlFilename); 309 310 Iterator i; 311 312 XMLConfiguration addXmlConfig = new XMLConfiguration(); 313 addXmlConfig.setDelimiterParsingDisabled(true); 314 addXmlConfig.load(addXml); 315 316 XMLConfiguration addedXmlConfig = new XMLConfiguration(); 317 addedXmlConfig.setDelimiterParsingDisabled(true); 318 if( addedXml.exists() ) 319 addedXmlConfig.load(addedXml); 320 321 i = addXmlConfig.getKeys(); 322 if( !i.hasNext() ) 323 return false; 324 325 List<String> firstParts = new ArrayList<String>(); 326 while( i.hasNext() ) 327 { 328 String key = (String)i.next(); 329 if( key.contains(".") ) 330 { 331 String candidate = key.substring(0, key.indexOf('.')); 332 if( !firstParts.contains(candidate)) 333 firstParts.add(candidate); 334 } 335 } 336 337 for( String firstPart : firstParts ) 338 { 339 340 int maxAddIndex = addXmlConfig.getMaxIndex(firstPart); 341 int maxAddedIndex = addedXmlConfig.getMaxIndex(firstPart); 342 int addIndex = xmlconfig.getMaxIndex(firstPart) + 1; 343 344 List<String> removeKeys = new ArrayList<String>(); 345 for( int j = 0; j <= maxAddIndex; j++ ) 346 { 347 List<String> addKeys = new ArrayList<String>(); 348 Iterator x1 = addXmlConfig.getKeys(firstPart+"("+j+")"); 349 while( x1.hasNext() ) 350 { 351 String key = (String)x1.next(); 352 addKeys.add(key); 353 } 354 for( int k = 0; k <= maxAddedIndex; k++ ) 355 { 356 List<String> addedKeys = new ArrayList<String>(); 357 Iterator x2 = addedXmlConfig.getKeys(firstPart+"("+k+")"); 358 while( x2.hasNext() ) 359 { 360 String key = (String)x2.next(); 361 addedKeys.add(key); 362 } 363 364 if( addMatch(addKeys, addedKeys, addXmlConfig, addedXmlConfig) ) 365 { 366 for( String addKey : addKeys ) 367 removeKeys.add(addKey); 368 } 369 } 370 } 371 for( int j = removeKeys.size() - 1; j >= 0; j-- ) 372 { 373 String removeKey = removeKeys.get(j); 374 addXmlConfig.clearProperty(removeKey); 375 } 376 377 for( int j = 0; j <= maxAddIndex; j++ ) 378 { 379 String addXMLKey = firstPart + "("+j+")"; 380 i = addXmlConfig.getKeys(addXMLKey); 381 while( i.hasNext() ) 382 { 383 String addXmlConfigKey = (String)i.next(); 384 String lastPart = addXmlConfigKey.substring(addXmlConfigKey.indexOf('.')+1,addXmlConfigKey.length()); 385 String originalXmlConfigKey = firstPart + "("+(addIndex+j)+")."+lastPart; 386 String addedXmlConfigKey = firstPart + "("+(maxAddedIndex+1+j)+")."+lastPart; 387 xmlconfig.addProperty(originalXmlConfigKey, addXmlConfig.getProperty(addXmlConfigKey)); 388 addedXmlConfig.addProperty(addedXmlConfigKey, addXmlConfig.getProperty(addXmlConfigKey)); 389 } 390 } 391 } 392 393 List<String> addedKeys = new ArrayList<String>(); 394 i = addedXmlConfig.getKeys(); 395 while( i.hasNext() ) 396 addedKeys.add((String)i.next()); 397 398 i = addXmlConfig.getKeys(); 399 while( i.hasNext() ) 400 { 401 String addKey = (String)i.next(); 402 if( addKey.contains(".") ) 403 continue; 404 Object value = addXmlConfig.getProperty(addKey); 405 if( addedKeys.contains(addKey) ) 406 { 407 if( addedXmlConfig.getProperty(addKey).equals(value) ) 408 continue; 409 } 410 411 xmlconfig.addProperty(addKey, value); 412 addedXmlConfig.addProperty(addKey, value); 413 } 414 415 addedXmlConfig.save(addedXml); 416 return true; 417 418 } 419 420 //David Welker - Not Implemented - Further Discussion Needed 421 private boolean applyChangeDirectives(Module module, File changeXml, XMLConfiguration xmlconfig) throws ConfigurationException 422 { 423 return false; 424 } 425 426 //David Welker - Not Implemented - Further Discussion Needed 427 private boolean applyRemoveDirectives(Module module, File removeXml, XMLConfiguration xmlconfig) throws ConfigurationException 428 { 429 return false; 430 } 431 432 /** 433 * return true if this file should be loaded. if it has a locale designation 434 * that matches the given locale, return true. If it does not contain a 435 * locale designation and the locale is en_US return true. If the locale is 436 * not en_US, but there are no other config files with the proper designator, 437 * then return true. 438 */ 439 private static boolean loadThisFile(File f, Locale l) 440 { 441 //System.out.println("looking for file " + f.getName() + " with locale " + l.toString()); 442 443 // see if the locale matches the locale in the file, 444 // or the locale is en_US and file has no locale 445 if(checkLocaleDesignator(f, l)) 446 { 447 return true; 448 } 449 else 450 { //see if there is a file to load that isn't this one 451 File dir = new File(f.getParent()); 452 String s[] = dir.list(); 453 boolean filefound = false; 454 String baseName = getBaseName(f); 455 for(int i=0; i<s.length; i++) 456 { 457 //if(f.getName().startsWith("uiMenuMap")) System.out.println(" checking possibility " + s[i]); 458 459 File possibleFile = new File(dir, s[i]); 460 // basename must match 461 String possibleBaseName = getBaseName(possibleFile); 462 if (!possibleBaseName.equals(baseName)) { 463 //if(f.getName().startsWith("uiMenuMap")) System.out.println(" base name does not match for " + s[i]); 464 continue; 465 } 466 // see if the other possibility has a matching locale, 467 // or no locale and our locale is en_US. in this case, 468 // this file should be loaded instead 469 if(checkLocaleDesignator(possibleFile, l)) { 470 filefound = true; 471 //System.out.println(" " + s[i] + " should be loaded instead"); 472 break; 473 } 474 //else if(f.getName().startsWith("uiMenuMap")) System.out.println(" checkLocaleDesignator fails"); 475 476 } 477 478 if(filefound) 479 { 480 //there is another config file that should get loaded for this locale 481 //so don't load this one. 482 return false; 483 } 484 } 485 486 /* 487 if(f.getName().startsWith("uiMenuMap")) { 488 System.out.println(" no exact match."); 489 System.out.println(" gld = " + getLocaleDesignator(f)); 490 System.out.println(" nocm = " + noOtherConfigurationMatch(f, l)); 491 } 492 */ 493 494 //there is no exact match for what we want 495 if(getLocaleDesignator(f) == null || noOtherConfigurationMatch(f, l)) 496 { //if this is a default file, use it 497 //System.out.println(" no exact match; using this since it's default."); 498 return true; 499 } 500 else 501 { //if not, don't 502 return false; 503 } 504 } 505 506 /** 507 * return true if this is the only configuration file with its stem name 508 * or if this configuration file is for en_US and there are no other 509 * configuration files with the same base name with a matching locale. 510 */ 511 private static boolean noOtherConfigurationMatch(File f, Locale l) 512 { 513 File dir = f.getParentFile(); 514 String[] list = dir.list(); 515 String fBasename = getBaseName(f); 516 517 // NOTE: if this file is en_US and l is not en_US, return true 518 // since we should use this file. this method is only called 519 // after we have checked all other possibilities. 520 String localeStr = getLocaleDesignator(f); 521 if(localeStr != null && localeStr.equals("en_US") && !l.toString().equals("en_US")) 522 { 523 return true; 524 } 525 526 527 for(int i=0; i<list.length; i++) 528 { 529 File dirF = new File(dir, list[i]); 530 if(dirF.getAbsolutePath().equals(f.getAbsolutePath())) 531 { //don't look at the actual file, just its siblings 532 continue; 533 } 534 String basename = getBaseName(dirF); 535 if(basename.equals(fBasename)) 536 { 537 return false; 538 } 539 } 540 541 542 return true; 543 } 544 545 /** 546 * return true if the designator on the file matches the locale exactly 547 */ 548 private static boolean checkLocaleDesignator(File f, Locale l) 549 { 550 String designator = getLocaleDesignator(f); 551 552 //System.out.println("designator: " + designator); 553 //System.out.println("locale: " + l.toString()); 554 //System.out.println("File: " + f.getName()); 555 556 if(designator == null && l.toString().equals("en_US")) 557 { //if there is no designator and the locale is en_US, use the file 558 return true; 559 } 560 561 //the designator is not null, see if this file matches the designator 562 if(designator != null && designator.equals(l.toString())) 563 { //load this file! 564 return true; 565 } 566 567 return false; 568 } 569 570 /** 571 * return the locale designator from a filename, or null if there isn't one 572 */ 573 private static String getLocaleDesignator(File f) 574 { 575 String basename = ConfigurationManager.removeFileExtension(f.getName()); 576 String designator = null; 577 if(basename.length() > 7) 578 { 579 designator = basename.substring(basename.length() - 6, basename.length()); 580 if(designator.charAt(0) != '_' || designator.charAt(3) != '_') 581 { //these characters do not represnet a locale 582 designator = null; 583 } 584 else 585 { 586 designator = designator.substring(1, designator.length()); 587 } 588 } 589 return designator; 590 } 591 592 /** 593 * return the base name of a file without an extension or locale designator 594 * @param f 595 */ 596 private static String getBaseName(File f) 597 { 598 String basename = ConfigurationManager.removeFileExtension(f.getName()); 599 String designator = null; 600 if(basename.length() > 7) 601 { 602 designator = basename.substring(basename.length() - 6, basename.length()); 603 if(designator.charAt(0) != '_' || designator.charAt(3) != '_') 604 { //these characters do not represnet a locale 605 return basename; 606 } 607 else 608 { 609 return basename.substring(0, basename.length() - 6); 610 } 611 } 612 return basename; 613 } 614 615 /** 616 * add the structure of root to prop 617 */ 618 private ConfigurationProperty getConfiguration(ConfigurationNode root, Module m, ConfigurationNamespace namespace) 619 throws Exception 620 { 621 String originMod; 622 boolean originOk = true; 623 ConfigurationProperty cp = new ConfigurationProperty(m, root.getName()); 624 cp.setNamespace(namespace); 625 String value = (String)root.getValue(); 626 boolean mutable = true; 627 if(value != null && !value.equals("")) 628 { 629 cp.setValue(value); 630 } 631 632 if(root.getChildrenCount() != 0) 633 { 634 Iterator it = root.getChildren().iterator(); 635 while(it.hasNext()) 636 { 637 ConfigurationNode child = (ConfigurationNode)it.next(); 638 ConfigurationProperty nextProp = getConfiguration(child, m, namespace); 639 if(nextProp == null) 640 { 641 if(_isDebugging) 642 { 643 _log.debug("not loading property " + child.getName() + 644 " because it was added from an inactive module."); 645 } 646 continue; 647 } 648 649 if(nextProp.getName().equals("mutable")) 650 { 651 if(nextProp.getValue() != null && 652 nextProp.getValue().equals("false")) 653 { 654 cp.setMutable(false); 655 } 656 } 657 else if(nextProp.getName().equals("originModule")) 658 { 659 if(nextProp.getValue() != null && 660 !nextProp.getValue().equals("")) 661 { 662 originMod = nextProp.getValue(); 663 if(!ModuleTree.instance().contains(originMod)) 664 { 665 originOk = false; 666 } 667 else 668 { 669 cp.setOriginModule(ConfigurationManager.getModule(originMod)); 670 } 671 } 672 } 673 674 cp.addProperty(nextProp, true); 675 } 676 } 677 678 if(originOk) 679 { 680 return cp; 681 } 682 else 683 { 684 return null; 685 } 686 } 687}