001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.geronimo.system.configuration; 018 019 import java.beans.PropertyEditor; 020 import java.io.IOException; 021 import java.io.PrintWriter; 022 import java.io.Serializable; 023 import java.io.StringReader; 024 import java.io.StringWriter; 025 import java.net.URI; 026 import java.util.ArrayList; 027 import java.util.Collections; 028 import java.util.HashMap; 029 import java.util.Iterator; 030 import java.util.LinkedHashMap; 031 import java.util.LinkedHashSet; 032 import java.util.Map; 033 import java.util.Set; 034 035 import javax.xml.parsers.DocumentBuilder; 036 import javax.xml.parsers.DocumentBuilderFactory; 037 038 import org.apache.commons.logging.Log; 039 import org.apache.commons.logging.LogFactory; 040 import org.apache.geronimo.common.propertyeditor.PropertyEditors; 041 import org.apache.geronimo.gbean.AbstractName; 042 import org.apache.geronimo.gbean.AbstractNameQuery; 043 import org.apache.geronimo.gbean.GAttributeInfo; 044 import org.apache.geronimo.gbean.GBeanData; 045 import org.apache.geronimo.gbean.GBeanInfo; 046 import org.apache.geronimo.gbean.ReferencePatterns; 047 import org.apache.geronimo.gbean.GReferenceInfo; 048 import org.apache.geronimo.kernel.InvalidGBeanException; 049 import org.apache.geronimo.kernel.config.InvalidConfigException; 050 import org.apache.geronimo.kernel.repository.Artifact; 051 import org.apache.geronimo.kernel.util.XmlUtil; 052 import org.apache.geronimo.util.EncryptionManager; 053 import org.apache.geronimo.system.configuration.condition.JexlExpressionParser; 054 import org.w3c.dom.Document; 055 import org.w3c.dom.Element; 056 import org.w3c.dom.Node; 057 import org.w3c.dom.NodeList; 058 import org.xml.sax.InputSource; 059 060 /** 061 * @version $Rev: 555993 $ $Date: 2007-07-13 09:39:25 -0400 (Fri, 13 Jul 2007) $ 062 */ 063 public class GBeanOverride implements Serializable { 064 065 private static final Log log = LogFactory.getLog(GBeanOverride.class); 066 067 private final Object name; 068 private boolean load; 069 private final Map<String, String> attributes = new LinkedHashMap<String, String>(); 070 private final Map<String, ReferencePatterns> references = new LinkedHashMap<String, ReferencePatterns>(); 071 private final ArrayList<String> clearAttributes = new ArrayList<String>(); 072 private final ArrayList<String> nullAttributes = new ArrayList<String>(); 073 private final ArrayList<String> clearReferences = new ArrayList<String>(); 074 private final String gbeanInfo; 075 private final JexlExpressionParser expressionParser; 076 077 public GBeanOverride(String name, boolean load, JexlExpressionParser expressionParser) { 078 this.name = name; 079 this.load = load; 080 gbeanInfo = null; 081 this.expressionParser = expressionParser; 082 } 083 084 public GBeanOverride(AbstractName name, boolean load, JexlExpressionParser expressionParser) { 085 this.name = name; 086 this.load = load; 087 gbeanInfo = null; 088 this.expressionParser = expressionParser; 089 } 090 091 public GBeanOverride(GBeanOverride original, String oldArtifact, String newArtifact) { 092 Object name = original.name; 093 if (name instanceof String) { 094 name = replace((String) name, oldArtifact, newArtifact); 095 } else if (name instanceof AbstractName) { 096 String value = name.toString(); 097 value = replace(value, oldArtifact, newArtifact); 098 name = new AbstractName(URI.create(value)); 099 } 100 this.name = name; 101 this.load = original.load; 102 this.attributes.putAll(original.attributes); 103 this.references.putAll(original.references); 104 this.clearAttributes.addAll(original.clearAttributes); 105 this.nullAttributes.addAll(original.nullAttributes); 106 this.clearReferences.addAll(original.clearReferences); 107 this.gbeanInfo = original.gbeanInfo; 108 this.expressionParser = original.expressionParser; 109 } 110 111 private static String replace(String original, String oldArtifact, String newArtifact) { 112 int pos = original.indexOf(oldArtifact); 113 if (pos == -1) { 114 return original; 115 } 116 int last = -1; 117 StringBuffer buf = new StringBuffer(); 118 while (pos > -1) { 119 buf.append(original.substring(last + 1, pos)); 120 buf.append(newArtifact); 121 last = pos + oldArtifact.length() - 1; 122 pos = original.indexOf(oldArtifact, last); 123 } 124 buf.append(original.substring(last + 1)); 125 return buf.toString(); 126 } 127 128 public GBeanOverride(GBeanData gbeanData, JexlExpressionParser expressionParser, ClassLoader classLoader) throws InvalidAttributeException { 129 GBeanInfo gbeanInfo = gbeanData.getGBeanInfo(); 130 this.gbeanInfo = gbeanInfo.getSourceClass(); 131 if (this.gbeanInfo == null) { 132 throw new IllegalArgumentException("GBeanInfo must have a source class set"); 133 } 134 name = gbeanData.getAbstractName(); 135 load = true; 136 137 // set attributes 138 for (Object o : gbeanData.getAttributes().entrySet()) { 139 Map.Entry entry = (Map.Entry) o; 140 String attributeName = (String) entry.getKey(); 141 GAttributeInfo attributeInfo = gbeanInfo.getAttribute(attributeName); 142 if (attributeInfo == null) { 143 throw new InvalidAttributeException("No attribute: " + attributeName + " for gbean: " + gbeanData.getAbstractName()); 144 } 145 Object attributeValue = entry.getValue(); 146 setAttribute(attributeName, attributeValue, attributeInfo.getType(), classLoader); 147 } 148 149 // references can be coppied in blind 150 references.putAll(gbeanData.getReferences()); 151 this.expressionParser = expressionParser; 152 } 153 154 public GBeanOverride(Element gbean, JexlExpressionParser expressionParser) throws InvalidGBeanException { 155 String nameString = gbean.getAttribute("name"); 156 if (nameString.indexOf('?') > -1) { 157 name = new AbstractName(URI.create(nameString)); 158 } else { 159 name = nameString; 160 } 161 162 String gbeanInfoString = gbean.getAttribute("gbeanInfo"); 163 if (gbeanInfoString.length() > 0) { 164 gbeanInfo = gbeanInfoString; 165 } else { 166 gbeanInfo = null; 167 } 168 if (gbeanInfo != null && !(name instanceof AbstractName)) { 169 throw new InvalidGBeanException("A gbean element using the gbeanInfo attribute must be specified using a full AbstractName: name=" + nameString); 170 } 171 172 String loadString = gbean.getAttribute("load"); 173 load = !"false".equals(loadString); 174 175 // attributes 176 NodeList attributes = gbean.getElementsByTagName("attribute"); 177 for (int a = 0; a < attributes.getLength(); a++) { 178 Element attribute = (Element) attributes.item(a); 179 180 String attributeName = attribute.getAttribute("name"); 181 182 // Check to see if there is a value attribute 183 if (attribute.hasAttribute("value")) { 184 setAttribute(attributeName, (String) EncryptionManager.decrypt(attribute.getAttribute("value"))); 185 continue; 186 } 187 188 // Check to see if there is a null attribute 189 if (attribute.hasAttribute("null")) { 190 String nullString = attribute.getAttribute("null"); 191 if (nullString.equals("true")) { 192 setNullAttribute(attributeName); 193 continue; 194 } 195 } 196 197 String rawAttribute = getContentsAsText(attribute); 198 // If there are no contents, then it's to be cleared 199 if (rawAttribute.length() == 0) { 200 setClearAttribute(attributeName); 201 continue; 202 } 203 String attributeValue = (String) EncryptionManager.decrypt(rawAttribute); 204 205 setAttribute(attributeName, attributeValue); 206 } 207 208 // references 209 NodeList references = gbean.getElementsByTagName("reference"); 210 for (int r = 0; r < references.getLength(); r++) { 211 Element reference = (Element) references.item(r); 212 213 String referenceName = reference.getAttribute("name"); 214 215 Set<AbstractNameQuery> objectNamePatterns = new LinkedHashSet<AbstractNameQuery>(); 216 NodeList patterns = reference.getElementsByTagName("pattern"); 217 218 // If there is no pattern, then its an empty set, so its a 219 // cleared value 220 if (patterns.getLength() == 0) { 221 setClearReference(referenceName); 222 continue; 223 } 224 225 for (int p = 0; p < patterns.getLength(); p++) { 226 Element pattern = (Element) patterns.item(p); 227 if (pattern == null) 228 continue; 229 230 String groupId = getChildAsText(pattern, "groupId"); 231 String artifactId = getChildAsText(pattern, "artifactId"); 232 String version = getChildAsText(pattern, "version"); 233 String type = getChildAsText(pattern, "type"); 234 String module = getChildAsText(pattern, "module"); 235 String name = getChildAsText(pattern, "name"); 236 237 Artifact referenceArtifact = null; 238 if (artifactId != null) { 239 referenceArtifact = new Artifact(groupId, artifactId, version, type); 240 } 241 Map<String, String> nameMap = new HashMap<String, String>(); 242 if (module != null) { 243 nameMap.put("module", module); 244 } 245 if (name != null) { 246 nameMap.put("name", name); 247 } 248 AbstractNameQuery abstractNameQuery = new AbstractNameQuery(referenceArtifact, nameMap, Collections.EMPTY_SET); 249 objectNamePatterns.add(abstractNameQuery); 250 } 251 252 setReferencePatterns(referenceName, new ReferencePatterns(objectNamePatterns)); 253 } 254 this.expressionParser = expressionParser; 255 } 256 257 private static String getChildAsText(Element element, String name) throws InvalidGBeanException { 258 NodeList children = element.getElementsByTagName(name); 259 if (children == null || children.getLength() == 0) { 260 return null; 261 } 262 if (children.getLength() > 1) { 263 throw new InvalidGBeanException("invalid name, too many parts named: " + name); 264 } 265 return getContentsAsText((Element) children.item(0)); 266 } 267 268 private static String getContentsAsText(Element element) throws InvalidGBeanException { 269 String value = ""; 270 NodeList text = element.getChildNodes(); 271 for (int t = 0; t < text.getLength(); t++) { 272 Node n = text.item(t); 273 if (n.getNodeType() == Node.TEXT_NODE) { 274 value += n.getNodeValue(); 275 } else { 276 StringWriter sw = new StringWriter(); 277 PrintWriter pw = new PrintWriter(sw); 278 OutputFormat of = new OutputFormat(Method.XML, null, false); 279 of.setOmitXMLDeclaration(true); 280 XMLSerializer serializer = new XMLSerializer(pw, of); 281 try { 282 serializer.prepare(); 283 serializer.serializeNode(n); 284 value += sw.toString(); 285 } catch (IOException ioe) { 286 throw new InvalidGBeanException("Error serializing GBean element", ioe); 287 } 288 } 289 } 290 return value.trim(); 291 } 292 293 public Object getName() { 294 return name; 295 } 296 297 public String getGBeanInfo() { 298 return gbeanInfo; 299 } 300 301 public boolean isLoad() { 302 return load; 303 } 304 305 public void setLoad(boolean load) { 306 this.load = load; 307 } 308 309 public Map<String, String> getAttributes() { 310 return attributes; 311 } 312 313 public String getAttribute(String attributeName) { 314 return attributes.get(attributeName); 315 } 316 317 public ArrayList<String> getClearAttributes() { 318 return clearAttributes; 319 } 320 321 public ArrayList<String> getNullAttributes() { 322 return nullAttributes; 323 } 324 325 public boolean getNullAttribute(String attributeName) { 326 return nullAttributes.contains(attributeName); 327 } 328 329 public boolean getClearAttribute(String attributeName) { 330 return clearAttributes.contains(attributeName); 331 } 332 333 public ArrayList<String> getClearReferences() { 334 return clearReferences; 335 } 336 337 public boolean getClearReference(String referenceName) { 338 return clearReferences.contains(referenceName); 339 } 340 341 public void setClearAttribute(String attributeName) { 342 if (!clearAttributes.contains(attributeName)) 343 clearAttributes.add(attributeName); 344 } 345 346 public void setNullAttribute(String attributeName) { 347 if (!nullAttributes.contains(attributeName)) 348 nullAttributes.add(attributeName); 349 } 350 351 public void setClearReference(String referenceName) { 352 if (!clearReferences.contains(referenceName)) 353 clearReferences.add(referenceName); 354 } 355 356 public void setAttribute(String attributeName, Object attributeValue, String attributeType, ClassLoader classLoader) throws InvalidAttributeException { 357 String stringValue = getAsText(attributeValue, attributeType, classLoader); 358 setAttribute(attributeName, stringValue); 359 } 360 361 public void setAttribute(String attributeName, String attributeValue) { 362 attributes.put(attributeName, attributeValue); 363 } 364 365 public Map<String, ReferencePatterns> getReferences() { 366 return references; 367 } 368 369 public ReferencePatterns getReferencePatterns(String name) { 370 return references.get(name); 371 } 372 373 public void setReferencePatterns(String name, ReferencePatterns patterns) { 374 references.put(name, patterns); 375 } 376 377 public boolean applyOverrides(GBeanData data, Artifact configName, AbstractName gbeanName, ClassLoader classLoader) throws InvalidConfigException { 378 if (!isLoad()) { 379 return false; 380 } 381 382 GBeanInfo gbeanInfo = data.getGBeanInfo(); 383 384 // set attributes 385 for (Map.Entry<String, String> entry : getAttributes().entrySet()) { 386 String attributeName = entry.getKey(); 387 GAttributeInfo attributeInfo = gbeanInfo.getAttribute(attributeName); 388 if (attributeInfo == null) { 389 throw new InvalidConfigException("No attribute: " + attributeName + " for gbean: " + data.getAbstractName()); 390 } 391 String valueString = entry.getValue(); 392 Object value = getValue(attributeInfo, valueString, configName, gbeanName, classLoader); 393 data.setAttribute(attributeName, value); 394 } 395 396 //Clear attributes 397 for (String attribute : getClearAttributes()) { 398 if (getClearAttribute(attribute)) { 399 data.clearAttribute(attribute); 400 } 401 } 402 403 //Null attributes 404 for (String attribute : getNullAttributes()) { 405 if (getNullAttribute(attribute)) { 406 data.setAttribute(attribute, null); 407 } 408 } 409 410 // set references 411 for (Map.Entry<String, ReferencePatterns> entry : getReferences().entrySet()) { 412 413 String referenceName = entry.getKey(); 414 GReferenceInfo referenceInfo = gbeanInfo.getReference(referenceName); 415 if (referenceInfo == null) { 416 throw new InvalidConfigException("No reference: " + referenceName + " for gbean: " + data.getAbstractName()); 417 } 418 419 ReferencePatterns referencePatterns = entry.getValue(); 420 421 data.setReferencePatterns(referenceName, referencePatterns); 422 } 423 424 //Clear references 425 for (String reference : getClearReferences()) { 426 if (getClearReference(reference)) { 427 data.clearReference(reference); 428 } 429 } 430 431 return true; 432 } 433 434 private synchronized Object getValue(GAttributeInfo attribute, String value, Artifact configurationName, AbstractName gbeanName, ClassLoader classLoader) { 435 if (value == null) { 436 return null; 437 } 438 value = substituteVariables(attribute.getName(), value); 439 try { 440 PropertyEditor editor = PropertyEditors.findEditor(attribute.getType(), classLoader); 441 if (editor == null) { 442 log.debug("Unable to parse attribute of type " + attribute.getType() + "; no editor found"); 443 return null; 444 } 445 editor.setAsText(value); 446 log.debug("Setting value for " + configurationName + "/" + gbeanName + "/" + attribute.getName() + " to value " + value); 447 return editor.getValue(); 448 } catch (ClassNotFoundException e) { 449 log.error("Unable to load attribute type " + attribute.getType()); 450 return null; 451 } 452 } 453 454 public String substituteVariables(String attributeName, String input) { 455 if (expressionParser != null) { 456 return expressionParser.parse(input); 457 } 458 return input; 459 } 460 461 /** 462 * Creates a new child of the supplied parent with the data for this 463 * GBeanOverride, adds it to the parent, and then returns the new 464 * child element. 465 * @param doc document containing the module, hence also the element returned from this method. 466 * @param parent module element this override will be inserted into 467 * @return newly created element for this override 468 */ 469 public Element writeXml(Document doc, Element parent) { 470 String gbeanName; 471 if (name instanceof String) { 472 gbeanName = (String) name; 473 } else { 474 gbeanName = name.toString(); 475 } 476 477 Element gbean = doc.createElement("gbean"); 478 parent.appendChild(gbean); 479 gbean.setAttribute("name", gbeanName); 480 if (gbeanInfo != null) { 481 gbean.setAttribute("gbeanInfo", gbeanInfo); 482 } 483 if (!load) { 484 gbean.setAttribute("load", "false"); 485 } 486 487 // attributes 488 for (Map.Entry<String, String> entry : attributes.entrySet()) { 489 String name = entry.getKey(); 490 String value = entry.getValue(); 491 if (value == null) { 492 setNullAttribute(name); 493 } else { 494 if (getNullAttribute(name)) { 495 nullAttributes.remove(name); 496 } 497 if (name.toLowerCase().indexOf("password") > -1) { 498 value = EncryptionManager.encrypt(value); 499 } 500 Element attribute = doc.createElement("attribute"); 501 attribute.setAttribute("name", name); 502 gbean.appendChild(attribute); 503 if (value.length() == 0) { 504 attribute.setAttribute("value", ""); 505 } else { 506 try { 507 // 508 // NOTE: Construct a new document to handle mixed content attribute values 509 // then add nodes which are children of the first node. This allows 510 // value to be XML or text. 511 // 512 513 DocumentBuilderFactory factory = XmlUtil.newDocumentBuilderFactory(); 514 DocumentBuilder builder = factory.newDocumentBuilder(); 515 516 /** 517 * if there was a value such as jdbc url with & then when that value was oulled 518 * from the config.xml the & would have been replaced/converted to '&', we need to check 519 * and change it back because an & would create a parse exception. 520 */ 521 value = value.replaceAll("&(?!amp;)", "&"); 522 523 // String unsubstitutedValue = unsubstitutedAttributes.get(name); 524 // if (unsubstitutedValue != null) { 525 // log.debug("writeXML attribute " + name 526 // + " using raw value " 527 // + unsubstitutedValue 528 // + " instead of cooked value " 529 // + value + "."); 530 // value = unsubstitutedValue; 531 // } 532 533 // Wrap value in an element to be sure we can handle xml or text values 534 String xml = "<fragment>" + value + "</fragment>"; 535 InputSource input = new InputSource(new StringReader(xml)); 536 Document fragment = builder.parse(input); 537 538 Node root = fragment.getFirstChild(); 539 NodeList children = root.getChildNodes(); 540 for (int i = 0; i < children.getLength(); i++) { 541 Node child = children.item(i); 542 543 // Import the child (and its children) into the new document 544 child = doc.importNode(child, true); 545 attribute.appendChild(child); 546 } 547 } 548 catch (Exception e) { 549 throw new RuntimeException("Failed to write attribute value fragment: " + e.getMessage(), e); 550 } 551 } 552 } 553 } 554 555 // cleared attributes 556 for (String name : clearAttributes) { 557 Element attribute = doc.createElement("attribute"); 558 gbean.appendChild(attribute); 559 attribute.setAttribute("name", name); 560 } 561 562 // Null attributes 563 for (String name : nullAttributes) { 564 Element attribute = doc.createElement("attribute"); 565 gbean.appendChild(attribute); 566 attribute.setAttribute("name", name); 567 attribute.setAttribute("null", "true"); 568 } 569 570 // references 571 for (Map.Entry<String, ReferencePatterns> entry : references.entrySet()) { 572 String name = entry.getKey(); 573 ReferencePatterns patterns = entry.getValue(); 574 575 Element reference = doc.createElement("reference"); 576 reference.setAttribute("name", name); 577 gbean.appendChild(reference); 578 579 Set<AbstractNameQuery> patternSet; 580 if (patterns.isResolved()) { 581 patternSet = Collections.singleton(new AbstractNameQuery(patterns.getAbstractName())); 582 } else { 583 patternSet = patterns.getPatterns(); 584 } 585 586 for (AbstractNameQuery pattern : patternSet) { 587 Element pat = doc.createElement("pattern"); 588 reference.appendChild(pat); 589 Artifact artifact = pattern.getArtifact(); 590 591 if (artifact != null) { 592 if (artifact.getGroupId() != null) { 593 Element group = doc.createElement("groupId"); 594 group.appendChild(doc.createTextNode(artifact.getGroupId())); 595 pat.appendChild(group); 596 } 597 if (artifact.getArtifactId() != null) { 598 Element art = doc.createElement("artifactId"); 599 art.appendChild(doc.createTextNode(artifact.getArtifactId())); 600 pat.appendChild(art); 601 } 602 if (artifact.getVersion() != null) { 603 Element version = doc.createElement("version"); 604 version.appendChild(doc.createTextNode(artifact.getVersion().toString())); 605 pat.appendChild(version); 606 } 607 if (artifact.getType() != null) { 608 Element type = doc.createElement("type"); 609 type.appendChild(doc.createTextNode(artifact.getType())); 610 pat.appendChild(type); 611 } 612 } 613 614 Map nameMap = pattern.getName(); 615 if (nameMap.get("module") != null) { 616 Element module = doc.createElement("module"); 617 module.appendChild(doc.createTextNode(nameMap.get("module").toString())); 618 pat.appendChild(module); 619 } 620 621 if (nameMap.get("name") != null) { 622 Element patName = doc.createElement("name"); 623 patName.appendChild(doc.createTextNode(nameMap.get("name").toString())); 624 pat.appendChild(patName); 625 } 626 } 627 } 628 629 // cleared references 630 for (String name : clearReferences) { 631 Element reference = doc.createElement("reference"); 632 reference.setAttribute("name", name); 633 gbean.appendChild(reference); 634 } 635 636 return gbean; 637 } 638 639 public static String getAsText(Object value, String type, ClassLoader classLoader) throws InvalidAttributeException { 640 try { 641 String attributeStringValue = null; 642 if (value != null) { 643 PropertyEditor editor = PropertyEditors.findEditor(type, classLoader); 644 if (editor == null) { 645 throw new InvalidAttributeException("Unable to format attribute of type " + type + "; no editor found"); 646 } 647 editor.setValue(value); 648 attributeStringValue = editor.getAsText(); 649 } 650 return attributeStringValue; 651 } catch (ClassNotFoundException e) { 652 //todo: use the Configuration's ClassLoader to load the attribute, if this ever becomes an issue 653 throw (InvalidAttributeException)new InvalidAttributeException("Unable to store attribute type " + type).initCause(e); 654 } 655 } 656 }