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 }