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 &amp; then when that value was oulled
518                             * from the config.xml the &amp; 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;)", "&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    }