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 }