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: 889880 $ $Date: 2009-12-11 21:45:24 -0500 (Fri, 11 Dec 2009) $
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> encryptedAttributes = new LinkedHashSet<String>();
073 private final Set<String> clearReferences = new LinkedHashSet<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.comment = original.getComment();
103 this.attributes.putAll(original.attributes);
104 this.propertyEditors.putAll(original.propertyEditors);
105 this.references.putAll(original.references);
106 this.clearAttributes.addAll(original.clearAttributes);
107 this.nullAttributes.addAll(original.nullAttributes);
108 this.encryptedAttributes.addAll(original.encryptedAttributes);
109 this.clearReferences.addAll(original.clearReferences);
110 this.gbeanInfo = original.gbeanInfo;
111 this.expressionParser = original.expressionParser;
112 }
113
114 private static String replace(String original, String oldArtifact, String newArtifact) {
115 int pos = original.indexOf(oldArtifact);
116 if (pos == -1) {
117 return original;
118 }
119 int last = -1;
120 StringBuffer buf = new StringBuffer();
121 while (pos > -1) {
122 buf.append(original.substring(last + 1, pos));
123 buf.append(newArtifact);
124 last = pos + oldArtifact.length() - 1;
125 pos = original.indexOf(oldArtifact, last);
126 }
127 buf.append(original.substring(last + 1));
128 return buf.toString();
129 }
130
131 public GBeanOverride(GBeanData gbeanData, JexlExpressionParser expressionParser, ClassLoader classLoader) throws InvalidAttributeException {
132 GBeanInfo gbeanInfo = gbeanData.getGBeanInfo();
133 this.gbeanInfo = gbeanInfo.getSourceClass();
134 if (this.gbeanInfo == null) {
135 throw new IllegalArgumentException("GBeanInfo must have a source class set");
136 }
137 name = gbeanData.getAbstractName();
138 load = true;
139
140 // set attributes
141 for (Object o : gbeanData.getAttributes().entrySet()) {
142 Map.Entry entry = (Map.Entry) o;
143 String attributeName = (String) entry.getKey();
144 GAttributeInfo attributeInfo = gbeanInfo.getAttribute(attributeName);
145 if (attributeInfo == null) {
146 throw new InvalidAttributeException("No attribute: " + attributeName + " for gbean: " + gbeanData.getAbstractName());
147 }
148 // TODO: shouldn't we only save manageable attributes here?
149 Object attributeValue = entry.getValue();
150 setAttribute(attributeInfo, attributeValue, classLoader);
151 if (attributeInfo.isEncrypted() && attributes.containsKey(attributeName)) {
152 encryptedAttributes.add(attributeName);
153 }
154 }
155
156 // references can be coppied in blind
157 references.putAll(gbeanData.getReferences());
158 this.expressionParser = expressionParser;
159 }
160
161 public GBeanOverride(GbeanType gbean, JexlExpressionParser expressionParser) throws InvalidGBeanException {
162 String nameString = gbean.getName();
163 if (nameString.indexOf('?') > -1) {
164 name = new AbstractName(URI.create(nameString));
165 } else {
166 name = nameString;
167 }
168
169 String gbeanInfoString = gbean.getGbeanInfo();
170 if (gbeanInfoString != null && gbeanInfoString.length() > 0) {
171 gbeanInfo = gbeanInfoString;
172 } else {
173 gbeanInfo = null;
174 }
175 if (gbeanInfo != null && !(name instanceof AbstractName)) {
176 throw new InvalidGBeanException("A gbean element using the gbeanInfo attribute must be specified using a full AbstractName: name=" + nameString);
177 }
178
179 load = gbean.isLoad();
180 comment = gbean.getComment();
181
182 // attributes
183 for (Object o : gbean.getAttributeOrReference()) {
184 if (o instanceof AttributeType) {
185 AttributeType attr = (AttributeType) o;
186
187 String propertyEditor = attr.getPropertyEditor();
188 if (null != propertyEditor) {
189 propertyEditors.put(attr.getName(), propertyEditor);
190 }
191
192 if (attr.isNull()) {
193 setNullAttribute(attr.getName());
194 } else {
195 String value;
196 try {
197 value = AttributesXmlUtil.extractAttributeValue(attr);
198 } catch (JAXBException e) {
199 throw new InvalidGBeanException("Could not extract attribute value from gbean override", e);
200 } catch (XMLStreamException e) {
201 throw new InvalidGBeanException("Could not extract attribute value from gbean override", e);
202 }
203 if (value == null || value.length() == 0) {
204 setClearAttribute(attr.getName());
205 } else {
206 String truevalue = (String) EncryptionManager.decrypt(value);
207 setAttribute(attr.getName(), truevalue);
208 /*
209 * If the original value is encrypted, retain this
210 * meta-information.
211 */
212 if (!value.equals(truevalue)
213 && attributes.containsKey(attr.getName())) {
214 encryptedAttributes.add(attr.getName());
215 }
216 }
217 }
218 } else if (o instanceof ReferenceType) {
219 ReferenceType ref = (ReferenceType) o;
220 if (ref.getPattern().isEmpty()) {
221 setClearReference(ref.getName());
222 } else {
223 Set<AbstractNameQuery> patternSet = new HashSet<AbstractNameQuery>();
224 for (ReferenceType.Pattern pattern : ref.getPattern()) {
225 String groupId = pattern.getGroupId();
226 String artifactId = pattern.getArtifactId();
227 String version = pattern.getVersion();
228 String type = pattern.getType();
229 String module = pattern.getModule();
230 String name = pattern.getName();
231
232 Artifact referenceArtifact = null;
233 if (artifactId != null) {
234 referenceArtifact = new Artifact(groupId, artifactId, version, type);
235 }
236 Map<String, String> nameMap = new HashMap<String, String>();
237 if (module != null) {
238 nameMap.put("module", module);
239 }
240 if (name != null) {
241 nameMap.put("name", name);
242 }
243 AbstractNameQuery abstractNameQuery = new AbstractNameQuery(referenceArtifact, nameMap, Collections.EMPTY_SET);
244 patternSet.add(abstractNameQuery);
245 }
246 ReferencePatterns patterns = new ReferencePatterns(patternSet);
247 setReferencePatterns(ref.getName(), patterns);
248 }
249 }
250 }
251 this.expressionParser = expressionParser;
252 }
253
254 public Object getName() {
255 return name;
256 }
257
258 public String getGBeanInfo() {
259 return gbeanInfo;
260 }
261
262 public String getComment() {
263 return comment;
264 }
265
266 public void setComment(String comment) {
267 this.comment = comment;
268 }
269
270 public boolean isLoad() {
271 return load;
272 }
273
274 public void setLoad(boolean load) {
275 this.load = load;
276 }
277
278 public Map<String, String> getAttributes() {
279 return attributes;
280 }
281
282 public String getAttribute(String attributeName) {
283 return attributes.get(attributeName);
284 }
285
286 public Set<String> getClearAttributes() {
287 return clearAttributes;
288 }
289
290 public Set<String> getNullAttributes() {
291 return nullAttributes;
292 }
293
294 public boolean isNullAttribute(String attributeName) {
295 return nullAttributes.contains(attributeName);
296 }
297
298 public boolean isClearAttribute(String attributeName) {
299 return clearAttributes.contains(attributeName);
300 }
301
302 public Set<String> getClearReferences() {
303 return clearReferences;
304 }
305
306 public boolean isClearReference(String referenceName) {
307 return clearReferences.contains(referenceName);
308 }
309
310 public void setClearAttribute(String attributeName) {
311 clearAttributes.add(attributeName);
312 // remove attribute from other maps
313 nullAttributes.remove(attributeName);
314 attributes.remove(attributeName);
315 }
316
317 public void setNullAttribute(String attributeName) {
318 nullAttributes.add(attributeName);
319 // remove attribute from other maps
320 clearAttributes.remove(attributeName);
321 attributes.remove(attributeName);
322 }
323
324 public void setClearReference(String referenceName) {
325 clearReferences.add(referenceName);
326 references.remove(referenceName);
327 }
328
329 public void setAttribute(GAttributeInfo attrInfo, Object attributeValue,
330 ClassLoader classLoader) throws InvalidAttributeException {
331 setAttribute(attrInfo.getName(), attributeValue, attrInfo.getType(),
332 classLoader);
333 if (attrInfo.isEncrypted()
334 && attributes.containsKey(attrInfo.getName())) {
335 encryptedAttributes.add(attrInfo.getName());
336 } else {
337 encryptedAttributes.remove(attrInfo.getName());
338 }
339 }
340
341 /**
342 * This method should be discouraged for usage outside in future, as it does
343 * not pass in encryption meta-information about the attribute being set.
344 *
345 * Use setAttribute(GAttributeInfo attrInfo, Object attributeValue,
346 * ClassLoader classLoader) instead.
347 *
348 * @deprecated
349 */
350 void setAttribute(String attributeName, Object attributeValue, String attributeType, ClassLoader classLoader) throws InvalidAttributeException {
351 String stringValue = getAsText(attributeName, attributeValue, attributeType, classLoader);
352 setAttribute(attributeName, stringValue);
353 }
354
355
356 /**
357 * This method should be discouraged for usage outside in future, as it does
358 * not pass in encryption meta-information about the attribute being set.
359 *
360 * Use setAttribute(GAttributeInfo attrInfo, Object attributeValue,
361 * ClassLoader classLoader) instead.
362 *
363 * @deprecated
364 */
365 void setAttribute(String attributeName, String attributeValue) {
366 if (attributeValue == null || attributeValue.length() == 0) {
367 setClearAttribute(attributeName);
368 } else {
369 attributes.put(attributeName, attributeValue);
370 // remove attribute from other maps
371 clearAttributes.remove(attributeName);
372 nullAttributes.remove(attributeName);
373 }
374 }
375
376 public Map<String, ReferencePatterns> getReferences() {
377 return references;
378 }
379
380 public ReferencePatterns getReferencePatterns(String name) {
381 return references.get(name);
382 }
383
384 public void setReferencePatterns(String name, ReferencePatterns patterns) {
385 references.put(name, patterns);
386 clearReferences.remove(name);
387 }
388
389 public boolean applyOverrides(GBeanData data, Artifact configName, AbstractName gbeanName, ClassLoader classLoader) throws InvalidConfigException {
390 if (!isLoad()) {
391 return false;
392 }
393
394 GBeanInfo gbeanInfo = data.getGBeanInfo();
395
396 // set attributes
397 for (Map.Entry<String, String> entry : getAttributes().entrySet()) {
398 String attributeName = entry.getKey();
399 GAttributeInfo attributeInfo = gbeanInfo.getAttribute(attributeName);
400 if (attributeInfo == null) {
401 throw new InvalidConfigException("No attribute: " + attributeName + " for gbean: " + data.getAbstractName());
402 }
403 String valueString = entry.getValue();
404 Object value = getValue(attributeInfo, valueString, configName, gbeanName, classLoader);
405 data.setAttribute(attributeName, value);
406 // Read the latest encryption meta-information on attributes
407 if (attributeInfo.isEncrypted()) {
408 encryptedAttributes.add(attributeName);
409 } else {
410 encryptedAttributes.remove(attributeName);
411 }
412 }
413
414 //Clear attributes
415 for (String attribute : getClearAttributes()) {
416 data.clearAttribute(attribute);
417 }
418
419 //Null attributes
420 for (String attribute : getNullAttributes()) {
421 data.setAttribute(attribute, null);
422 }
423
424 // set references
425 for (Map.Entry<String, ReferencePatterns> entry : getReferences().entrySet()) {
426 String referenceName = entry.getKey();
427 GReferenceInfo referenceInfo = gbeanInfo.getReference(referenceName);
428 if (referenceInfo == null) {
429 throw new InvalidConfigException("No reference: " + referenceName + " for gbean: " + data.getAbstractName());
430 }
431
432 ReferencePatterns referencePatterns = entry.getValue();
433
434 data.setReferencePatterns(referenceName, referencePatterns);
435 }
436
437 //Clear references
438 for (String reference : getClearReferences()) {
439 data.clearReference(reference);
440 }
441
442 return true;
443 }
444
445 private synchronized Object getValue(GAttributeInfo attribute, String value, Artifact configurationName, AbstractName gbeanName, ClassLoader classLoader) {
446 if (value == null) {
447 return null;
448 }
449 value = substituteVariables(attribute.getName(), value);
450 PropertyEditor editor = loadPropertyEditor(attribute, classLoader);
451 editor.setAsText(value);
452 log.debug("Setting value for " + configurationName + "/" + gbeanName + "/" + attribute.getName() + " to value " + value);
453 return editor.getValue();
454 }
455
456 protected PropertyEditor loadPropertyEditor(GAttributeInfo attribute, ClassLoader classLoader) {
457 String propertyEditor = propertyEditors.get(attribute.getName());
458 if (null == propertyEditor) {
459 PropertyEditor editor;
460 try {
461 editor = PropertyEditors.findEditor(attribute.getType(), classLoader);
462 } catch (ClassNotFoundException e) {
463 throw new IllegalStateException("Unable to load property editor for attribute type: " + attribute.getType());
464 }
465 if (editor == null) {
466 throw new IllegalStateException("Unable to parse attribute of type " + attribute.getType() + "; no editor found");
467 }
468 return editor;
469 } else {
470 try {
471 Class propertyEditorClass = classLoader.loadClass(propertyEditor);
472 return (PropertyEditor) propertyEditorClass.newInstance();
473 } catch (Exception ex) {
474 throw new IllegalStateException("Cannot load property editor [" + propertyEditor + "]", ex);
475 }
476 }
477 }
478
479 public String substituteVariables(String attributeName, String input) {
480 if (expressionParser != null) {
481 return expressionParser.parse(input);
482 }
483 return input;
484 }
485
486 /**
487 * Creates a new child of the supplied parent with the data for this
488 * GBeanOverride, adds it to the parent, and then returns the new
489 * child element.
490 *
491 * @return newly created element for this override
492 */
493 public GbeanType writeXml() {
494 GbeanType gbean = new GbeanType();
495 String gbeanName;
496 if (name instanceof String) {
497 gbeanName = (String) name;
498 } else {
499 gbeanName = name.toString();
500 }
501 gbean.setName(gbeanName);
502 if (gbeanInfo != null) {
503 gbean.setGbeanInfo(gbeanInfo);
504 }
505 if (!load) {
506 gbean.setLoad(false);
507 }
508 if (comment != null) {
509 gbean.setComment(comment);
510 }
511
512 // attributes
513 for (Map.Entry<String, String> entry : attributes.entrySet()) {
514 String name = entry.getKey();
515 String value = entry.getValue();
516 if (value == null) {
517 nullAttributes.add(name);
518 clearAttributes.remove(name);
519 } else {
520 nullAttributes.remove(name);
521 clearAttributes.remove(name);
522
523 if (encryptedAttributes.contains(name)) {
524 value = EncryptionManager.encrypt(value);
525 }
526
527 /**
528 * if there was a value such as jdbc url with & then when that value was oulled
529 * from the config.xml the & would have been replaced/converted to '&', we need to check
530 * and change it back because an & would create a parse exception.
531 */
532 value = "<attribute xmlns='" + ATTRIBUTE_NAMESPACE + "'>" + value.replaceAll("&(?!amp;)", "&") + "</attribute>";
533 Reader reader = new StringReader(value);
534 try {
535 AttributeType attribute = AttributesXmlUtil.loadAttribute(reader);
536 attribute.setName(name);
537 String editorClass = propertyEditors.get(name);
538 if (null != editorClass) {
539 attribute.setPropertyEditor(editorClass);
540 }
541 gbean.getAttributeOrReference().add(attribute);
542 } catch (Exception e) {
543 log.error("Could not serialize attribute " + name + " in gbean " + gbeanName + ", value: " + value, e);
544 }
545 }
546 }
547
548 // cleared attributes
549 for (String name : clearAttributes) {
550 AttributeType attribute = new AttributeType();
551 attribute.setName(name);
552 gbean.getAttributeOrReference().add(attribute);
553 }
554
555 // Null attributes
556 for (String name : nullAttributes) {
557 AttributeType attribute = new AttributeType();
558 attribute.setName(name);
559 attribute.setNull(true);
560 gbean.getAttributeOrReference().add(attribute);
561 }
562
563 // references
564 for (Map.Entry<String, ReferencePatterns> entry : references.entrySet()) {
565 String name = entry.getKey();
566 ReferencePatterns patterns = entry.getValue();
567 ReferenceType reference = new ReferenceType();
568 reference.setName(name);
569
570 Set<AbstractNameQuery> patternSet;
571 if (patterns.isResolved()) {
572 patternSet = Collections.singleton(new AbstractNameQuery(patterns.getAbstractName()));
573 } else {
574 patternSet = patterns.getPatterns();
575 }
576
577 for (AbstractNameQuery pattern : patternSet) {
578 ReferenceType.Pattern patternType = new ReferenceType.Pattern();
579 Artifact artifact = pattern.getArtifact();
580
581 if (artifact != null) {
582 if (artifact.getGroupId() != null) {
583 patternType.setGroupId(artifact.getGroupId());
584 }
585 if (artifact.getArtifactId() != null) {
586 patternType.setArtifactId(artifact.getArtifactId());
587 }
588 if (artifact.getVersion() != null) {
589 patternType.setVersion(artifact.getVersion().toString());
590 }
591 if (artifact.getType() != null) {
592 patternType.setType(artifact.getType());
593 }
594 }
595
596 Map nameMap = pattern.getName();
597 if (nameMap.get("module") != null) {
598 patternType.setModule((String) nameMap.get("module"));
599 }
600
601 if (nameMap.get("name") != null) {
602 patternType.setName((String) nameMap.get("name"));
603 }
604 reference.getPattern().add(patternType);
605 }
606 gbean.getAttributeOrReference().add(reference);
607 }
608
609 // cleared references
610 for (String name : clearReferences) {
611 ReferenceType reference = new ReferenceType();
612 reference.setName(name);
613 gbean.getAttributeOrReference().add(reference);
614 }
615
616 return gbean;
617 }
618
619 protected String getAsText(String attributeName, Object value, String type, ClassLoader classLoader) throws InvalidAttributeException {
620 try {
621 if (null == value || value instanceof String) {
622 return (String) value;
623 }
624
625 Class typeClass = ClassLoading.loadClass(type, classLoader);
626 PropertyEditor editor = PropertyEditors.findEditor(value.getClass());
627 if (null == editor) {
628 editor = PropertyEditors.findEditor(typeClass);
629 if (null == editor) {
630 throw new InvalidAttributeException("Unable to format attribute of type " + type + "; no editor found");
631 }
632 }
633
634 if (!type.equals(value.getClass().getName())
635 && !typeClass.isPrimitive()
636 && !Collection.class.isAssignableFrom(typeClass)) {
637 propertyEditors.put(attributeName, editor.getClass().getName());
638 }
639
640 editor.setValue(value);
641 return editor.getAsText();
642 } catch (ClassNotFoundException e) {
643 //todo: use the Configuration's ClassLoader to load the attribute, if this ever becomes an issue
644 throw (InvalidAttributeException) new InvalidAttributeException("Unable to store attribute type " + type).initCause(e);
645 }
646 }
647 }