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.Reader; 021 import java.io.Serializable; 022 import java.io.StringReader; 023 import java.net.URI; 024 import java.util.Collection; 025 import java.util.Collections; 026 import java.util.HashMap; 027 import java.util.HashSet; 028 import java.util.LinkedHashMap; 029 import java.util.LinkedHashSet; 030 import java.util.Map; 031 import java.util.Set; 032 033 import javax.xml.bind.JAXBException; 034 import javax.xml.stream.XMLStreamException; 035 036 import org.apache.commons.logging.Log; 037 import org.apache.commons.logging.LogFactory; 038 import org.apache.geronimo.common.propertyeditor.PropertyEditors; 039 import org.apache.geronimo.gbean.AbstractName; 040 import org.apache.geronimo.gbean.AbstractNameQuery; 041 import org.apache.geronimo.gbean.GAttributeInfo; 042 import org.apache.geronimo.gbean.GBeanData; 043 import org.apache.geronimo.gbean.GBeanInfo; 044 import org.apache.geronimo.gbean.GReferenceInfo; 045 import org.apache.geronimo.gbean.ReferencePatterns; 046 import org.apache.geronimo.kernel.ClassLoading; 047 import org.apache.geronimo.kernel.InvalidGBeanException; 048 import org.apache.geronimo.kernel.config.InvalidConfigException; 049 import org.apache.geronimo.kernel.repository.Artifact; 050 import org.apache.geronimo.system.configuration.condition.JexlExpressionParser; 051 import org.apache.geronimo.system.plugin.model.AttributeType; 052 import org.apache.geronimo.system.plugin.model.GbeanType; 053 import org.apache.geronimo.system.plugin.model.ReferenceType; 054 import org.apache.geronimo.crypto.EncryptionManager; 055 056 /** 057 * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $ 058 */ 059 public class GBeanOverride implements Serializable { 060 061 private static final Log log = LogFactory.getLog(GBeanOverride.class); 062 063 public static final String ATTRIBUTE_NAMESPACE = "http://geronimo.apache.org/xml/ns/attributes-1.2"; 064 private final Object name; 065 private String comment; 066 private boolean load; 067 private final Map<String, String> attributes = new LinkedHashMap<String, String>(); 068 private final Map<String, String> propertyEditors = new HashMap<String, String>(); 069 private final Map<String, ReferencePatterns> references = new LinkedHashMap<String, ReferencePatterns>(); 070 private final Set<String> clearAttributes = new LinkedHashSet<String>(); 071 private final Set<String> nullAttributes = new LinkedHashSet<String>(); 072 private final Set<String> clearReferences = new LinkedHashSet<String>(); 073 private final String gbeanInfo; 074 private final JexlExpressionParser expressionParser; 075 076 public GBeanOverride(String name, boolean load, JexlExpressionParser expressionParser) { 077 this.name = name; 078 this.load = load; 079 gbeanInfo = null; 080 this.expressionParser = expressionParser; 081 } 082 083 public GBeanOverride(AbstractName name, boolean load, JexlExpressionParser expressionParser) { 084 this.name = name; 085 this.load = load; 086 gbeanInfo = null; 087 this.expressionParser = expressionParser; 088 } 089 090 public GBeanOverride(GBeanOverride original, String oldArtifact, String newArtifact) { 091 Object name = original.name; 092 if (name instanceof String) { 093 name = replace((String) name, oldArtifact, newArtifact); 094 } else if (name instanceof AbstractName) { 095 String value = name.toString(); 096 value = replace(value, oldArtifact, newArtifact); 097 name = new AbstractName(URI.create(value)); 098 } 099 this.name = name; 100 this.load = original.load; 101 this.comment = original.getComment(); 102 this.attributes.putAll(original.attributes); 103 this.propertyEditors.putAll(original.propertyEditors); 104 this.references.putAll(original.references); 105 this.clearAttributes.addAll(original.clearAttributes); 106 this.nullAttributes.addAll(original.nullAttributes); 107 this.clearReferences.addAll(original.clearReferences); 108 this.gbeanInfo = original.gbeanInfo; 109 this.expressionParser = original.expressionParser; 110 } 111 112 private static String replace(String original, String oldArtifact, String newArtifact) { 113 int pos = original.indexOf(oldArtifact); 114 if (pos == -1) { 115 return original; 116 } 117 int last = -1; 118 StringBuffer buf = new StringBuffer(); 119 while (pos > -1) { 120 buf.append(original.substring(last + 1, pos)); 121 buf.append(newArtifact); 122 last = pos + oldArtifact.length() - 1; 123 pos = original.indexOf(oldArtifact, last); 124 } 125 buf.append(original.substring(last + 1)); 126 return buf.toString(); 127 } 128 129 public GBeanOverride(GBeanData gbeanData, JexlExpressionParser expressionParser, ClassLoader classLoader) throws InvalidAttributeException { 130 GBeanInfo gbeanInfo = gbeanData.getGBeanInfo(); 131 this.gbeanInfo = gbeanInfo.getSourceClass(); 132 if (this.gbeanInfo == null) { 133 throw new IllegalArgumentException("GBeanInfo must have a source class set"); 134 } 135 name = gbeanData.getAbstractName(); 136 load = true; 137 138 // set attributes 139 for (Object o : gbeanData.getAttributes().entrySet()) { 140 Map.Entry entry = (Map.Entry) o; 141 String attributeName = (String) entry.getKey(); 142 GAttributeInfo attributeInfo = gbeanInfo.getAttribute(attributeName); 143 if (attributeInfo == null) { 144 throw new InvalidAttributeException("No attribute: " + attributeName + " for gbean: " + gbeanData.getAbstractName()); 145 } 146 Object attributeValue = entry.getValue(); 147 setAttribute(attributeName, attributeValue, attributeInfo.getType(), classLoader); 148 } 149 150 // references can be coppied in blind 151 references.putAll(gbeanData.getReferences()); 152 this.expressionParser = expressionParser; 153 } 154 155 public GBeanOverride(GbeanType gbean, JexlExpressionParser expressionParser) throws InvalidGBeanException { 156 String nameString = gbean.getName(); 157 if (nameString.indexOf('?') > -1) { 158 name = new AbstractName(URI.create(nameString)); 159 } else { 160 name = nameString; 161 } 162 163 String gbeanInfoString = gbean.getGbeanInfo(); 164 if (gbeanInfoString != null && gbeanInfoString.length() > 0) { 165 gbeanInfo = gbeanInfoString; 166 } else { 167 gbeanInfo = null; 168 } 169 if (gbeanInfo != null && !(name instanceof AbstractName)) { 170 throw new InvalidGBeanException("A gbean element using the gbeanInfo attribute must be specified using a full AbstractName: name=" + nameString); 171 } 172 173 load = gbean.isLoad(); 174 comment = gbean.getComment(); 175 176 // attributes 177 for (Object o : gbean.getAttributeOrReference()) { 178 if (o instanceof AttributeType) { 179 AttributeType attr = (AttributeType) o; 180 181 String propertyEditor = attr.getPropertyEditor(); 182 if (null != propertyEditor) { 183 propertyEditors.put(attr.getName(), propertyEditor); 184 } 185 186 if (attr.isNull()) { 187 getNullAttributes().add(attr.getName()); 188 } else { 189 String value; 190 try { 191 value = AttributesXmlUtil.extractAttributeValue(attr); 192 } catch (JAXBException e) { 193 throw new InvalidGBeanException("Could not extract attribute value from gbean override", e); 194 } catch (XMLStreamException e) { 195 throw new InvalidGBeanException("Could not extract attribute value from gbean override", e); 196 } 197 if (value == null || value.length() == 0) { 198 setClearAttribute(attr.getName()); 199 } else { 200 String truevalue = (String) EncryptionManager.decrypt(value); 201 getAttributes().put(attr.getName(), truevalue); 202 } 203 } 204 } else if (o instanceof ReferenceType) { 205 ReferenceType ref = (ReferenceType) o; 206 if (ref.getPattern().isEmpty()) { 207 setClearReference(ref.getName()); 208 } else { 209 Set<AbstractNameQuery> patternSet = new HashSet<AbstractNameQuery>(); 210 for (ReferenceType.Pattern pattern : ref.getPattern()) { 211 String groupId = pattern.getGroupId(); 212 String artifactId = pattern.getArtifactId(); 213 String version = pattern.getVersion(); 214 String type = pattern.getType(); 215 String module = pattern.getModule(); 216 String name = pattern.getName(); 217 218 Artifact referenceArtifact = null; 219 if (artifactId != null) { 220 referenceArtifact = new Artifact(groupId, artifactId, version, type); 221 } 222 Map<String, String> nameMap = new HashMap<String, String>(); 223 if (module != null) { 224 nameMap.put("module", module); 225 } 226 if (name != null) { 227 nameMap.put("name", name); 228 } 229 AbstractNameQuery abstractNameQuery = new AbstractNameQuery(referenceArtifact, nameMap, Collections.EMPTY_SET); 230 patternSet.add(abstractNameQuery); 231 } 232 ReferencePatterns patterns = new ReferencePatterns(patternSet); 233 setReferencePatterns(ref.getName(), patterns); 234 } 235 } 236 } 237 this.expressionParser = expressionParser; 238 } 239 240 public Object getName() { 241 return name; 242 } 243 244 public String getGBeanInfo() { 245 return gbeanInfo; 246 } 247 248 public String getComment() { 249 return comment; 250 } 251 252 public void setComment(String comment) { 253 this.comment = comment; 254 } 255 256 public boolean isLoad() { 257 return load; 258 } 259 260 public void setLoad(boolean load) { 261 this.load = load; 262 } 263 264 public Map<String, String> getAttributes() { 265 return attributes; 266 } 267 268 public String getAttribute(String attributeName) { 269 return attributes.get(attributeName); 270 } 271 272 public Set<String> getClearAttributes() { 273 return clearAttributes; 274 } 275 276 public Set<String> getNullAttributes() { 277 return nullAttributes; 278 } 279 280 public boolean isNullAttribute(String attributeName) { 281 return nullAttributes.contains(attributeName); 282 } 283 284 public boolean isClearAttribute(String attributeName) { 285 return clearAttributes.contains(attributeName); 286 } 287 288 public Set<String> getClearReferences() { 289 return clearReferences; 290 } 291 292 public boolean isClearReference(String referenceName) { 293 return clearReferences.contains(referenceName); 294 } 295 296 public void setClearAttribute(String attributeName) { 297 clearAttributes.add(attributeName); 298 } 299 300 public void setNullAttribute(String attributeName) { 301 nullAttributes.add(attributeName); 302 } 303 304 public void setClearReference(String referenceName) { 305 clearReferences.add(referenceName); 306 } 307 308 public void setAttribute(String attributeName, Object attributeValue, String attributeType, ClassLoader classLoader) throws InvalidAttributeException { 309 String stringValue = getAsText(attributeName, attributeValue, attributeType, classLoader); 310 setAttribute(attributeName, stringValue); 311 } 312 313 public void setAttribute(String attributeName, String attributeValue) { 314 if (attributeValue == null || attributeValue.length() == 0) { 315 clearAttributes.add(attributeName); 316 } else { 317 attributes.put(attributeName, attributeValue); 318 } 319 } 320 321 public Map<String, ReferencePatterns> getReferences() { 322 return references; 323 } 324 325 public ReferencePatterns getReferencePatterns(String name) { 326 return references.get(name); 327 } 328 329 public void setReferencePatterns(String name, ReferencePatterns patterns) { 330 references.put(name, patterns); 331 } 332 333 public boolean applyOverrides(GBeanData data, Artifact configName, AbstractName gbeanName, ClassLoader classLoader) throws InvalidConfigException { 334 if (!isLoad()) { 335 return false; 336 } 337 338 GBeanInfo gbeanInfo = data.getGBeanInfo(); 339 340 // set attributes 341 for (Map.Entry<String, String> entry : getAttributes().entrySet()) { 342 String attributeName = entry.getKey(); 343 GAttributeInfo attributeInfo = gbeanInfo.getAttribute(attributeName); 344 if (attributeInfo == null) { 345 throw new InvalidConfigException("No attribute: " + attributeName + " for gbean: " + data.getAbstractName()); 346 } 347 String valueString = entry.getValue(); 348 Object value = getValue(attributeInfo, valueString, configName, gbeanName, classLoader); 349 data.setAttribute(attributeName, value); 350 } 351 352 //Clear attributes 353 for (String attribute : getClearAttributes()) { 354 data.clearAttribute(attribute); 355 } 356 357 //Null attributes 358 for (String attribute : getNullAttributes()) { 359 data.setAttribute(attribute, null); 360 } 361 362 // set references 363 for (Map.Entry<String, ReferencePatterns> entry : getReferences().entrySet()) { 364 String referenceName = entry.getKey(); 365 GReferenceInfo referenceInfo = gbeanInfo.getReference(referenceName); 366 if (referenceInfo == null) { 367 throw new InvalidConfigException("No reference: " + referenceName + " for gbean: " + data.getAbstractName()); 368 } 369 370 ReferencePatterns referencePatterns = entry.getValue(); 371 372 data.setReferencePatterns(referenceName, referencePatterns); 373 } 374 375 //Clear references 376 for (String reference : getClearReferences()) { 377 data.clearReference(reference); 378 } 379 380 return true; 381 } 382 383 private synchronized Object getValue(GAttributeInfo attribute, String value, Artifact configurationName, AbstractName gbeanName, ClassLoader classLoader) { 384 if (value == null) { 385 return null; 386 } 387 value = substituteVariables(attribute.getName(), value); 388 PropertyEditor editor = loadPropertyEditor(attribute, classLoader); 389 editor.setAsText(value); 390 log.debug("Setting value for " + configurationName + "/" + gbeanName + "/" + attribute.getName() + " to value " + value); 391 return editor.getValue(); 392 } 393 394 protected PropertyEditor loadPropertyEditor(GAttributeInfo attribute, ClassLoader classLoader) { 395 String propertyEditor = propertyEditors.get(attribute.getName()); 396 if (null == propertyEditor) { 397 PropertyEditor editor; 398 try { 399 editor = PropertyEditors.findEditor(attribute.getType(), classLoader); 400 } catch (ClassNotFoundException e) { 401 throw new IllegalStateException("Unable to load property editor for attribute type: " + attribute.getType()); 402 } 403 if (editor == null) { 404 throw new IllegalStateException("Unable to parse attribute of type " + attribute.getType() + "; no editor found"); 405 } 406 return editor; 407 } else { 408 try { 409 Class propertyEditorClass = classLoader.loadClass(propertyEditor); 410 return (PropertyEditor) propertyEditorClass.newInstance(); 411 } catch (Exception ex) { 412 throw new IllegalStateException("Cannot load property editor [" + propertyEditor + "]", ex); 413 } 414 } 415 } 416 417 public String substituteVariables(String attributeName, String input) { 418 if (expressionParser != null) { 419 return expressionParser.parse(input); 420 } 421 return input; 422 } 423 424 /** 425 * Creates a new child of the supplied parent with the data for this 426 * GBeanOverride, adds it to the parent, and then returns the new 427 * child element. 428 * 429 * @return newly created element for this override 430 */ 431 public GbeanType writeXml() { 432 GbeanType gbean = new GbeanType(); 433 String gbeanName; 434 if (name instanceof String) { 435 gbeanName = (String) name; 436 } else { 437 gbeanName = name.toString(); 438 } 439 gbean.setName(gbeanName); 440 if (gbeanInfo != null) { 441 gbean.setGbeanInfo(gbeanInfo); 442 } 443 if (!load) { 444 gbean.setLoad(false); 445 } 446 if (comment != null) { 447 gbean.setComment(comment); 448 } 449 450 // attributes 451 for (Map.Entry<String, String> entry : attributes.entrySet()) { 452 String name = entry.getKey(); 453 String value = entry.getValue(); 454 if (value == null) { 455 setNullAttribute(name); 456 } else { 457 if (isNullAttribute(name)) { 458 nullAttributes.remove(name); 459 } 460 if (name.toLowerCase().indexOf("password") > -1) { 461 value = EncryptionManager.encrypt(value); 462 } 463 /** 464 * if there was a value such as jdbc url with & then when that value was oulled 465 * from the config.xml the & would have been replaced/converted to '&', we need to check 466 * and change it back because an & would create a parse exception. 467 */ 468 value = "<attribute xmlns='" + ATTRIBUTE_NAMESPACE + "'>" + value.replaceAll("&(?!amp;)", "&") + "</attribute>"; 469 Reader reader = new StringReader(value); 470 try { 471 AttributeType attribute = AttributesXmlUtil.loadAttribute(reader); 472 attribute.setName(name); 473 String editorClass = propertyEditors.get(name); 474 if (null != editorClass) { 475 attribute.setPropertyEditor(editorClass); 476 } 477 gbean.getAttributeOrReference().add(attribute); 478 } catch (Exception e) { 479 log.error("Could not serialize attribute " + name + " in gbean " + gbeanName + ", value: " + value, e); 480 } 481 } 482 } 483 484 // cleared attributes 485 for (String name : clearAttributes) { 486 AttributeType attribute = new AttributeType(); 487 attribute.setName(name); 488 gbean.getAttributeOrReference().add(attribute); 489 } 490 491 // Null attributes 492 for (String name : nullAttributes) { 493 AttributeType attribute = new AttributeType(); 494 attribute.setName(name); 495 attribute.setNull(true); 496 gbean.getAttributeOrReference().add(attribute); 497 } 498 499 // references 500 for (Map.Entry<String, ReferencePatterns> entry : references.entrySet()) { 501 String name = entry.getKey(); 502 ReferencePatterns patterns = entry.getValue(); 503 ReferenceType reference = new ReferenceType(); 504 reference.setName(name); 505 506 Set<AbstractNameQuery> patternSet; 507 if (patterns.isResolved()) { 508 patternSet = Collections.singleton(new AbstractNameQuery(patterns.getAbstractName())); 509 } else { 510 patternSet = patterns.getPatterns(); 511 } 512 513 for (AbstractNameQuery pattern : patternSet) { 514 ReferenceType.Pattern patternType = new ReferenceType.Pattern(); 515 Artifact artifact = pattern.getArtifact(); 516 517 if (artifact != null) { 518 if (artifact.getGroupId() != null) { 519 patternType.setGroupId(artifact.getGroupId()); 520 } 521 if (artifact.getArtifactId() != null) { 522 patternType.setArtifactId(artifact.getArtifactId()); 523 } 524 if (artifact.getVersion() != null) { 525 patternType.setVersion(artifact.getVersion().toString()); 526 } 527 if (artifact.getType() != null) { 528 patternType.setType(artifact.getType()); 529 } 530 } 531 532 Map nameMap = pattern.getName(); 533 if (nameMap.get("module") != null) { 534 patternType.setModule((String) nameMap.get("module")); 535 } 536 537 if (nameMap.get("name") != null) { 538 patternType.setName((String) nameMap.get("name")); 539 } 540 reference.getPattern().add(patternType); 541 } 542 gbean.getAttributeOrReference().add(reference); 543 } 544 545 // cleared references 546 for (String name : clearReferences) { 547 ReferenceType reference = new ReferenceType(); 548 reference.setName(name); 549 gbean.getAttributeOrReference().add(reference); 550 } 551 552 return gbean; 553 } 554 555 protected String getAsText(String attributeName, Object value, String type, ClassLoader classLoader) throws InvalidAttributeException { 556 try { 557 if (null == value || value instanceof String) { 558 return (String) value; 559 } 560 561 Class typeClass = ClassLoading.loadClass(type, classLoader); 562 PropertyEditor editor = PropertyEditors.findEditor(value.getClass()); 563 if (null == editor) { 564 editor = PropertyEditors.findEditor(typeClass); 565 if (null == editor) { 566 throw new InvalidAttributeException("Unable to format attribute of type " + type + "; no editor found"); 567 } 568 } 569 570 if (!type.equals(value.getClass().getName()) 571 && !typeClass.isPrimitive() 572 && !Collection.class.isAssignableFrom(typeClass)) { 573 propertyEditors.put(attributeName, editor.getClass().getName()); 574 } 575 576 editor.setValue(value); 577 return editor.getAsText(); 578 } catch (ClassNotFoundException e) { 579 //todo: use the Configuration's ClassLoader to load the attribute, if this ever becomes an issue 580 throw (InvalidAttributeException) new InvalidAttributeException("Unable to store attribute type " + type).initCause(e); 581 } 582 } 583 }