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.StringWriter;
023    import java.io.Serializable;
024    import java.io.StringReader;
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.DocumentBuilderFactory;
036    import javax.xml.parsers.DocumentBuilder;
037    
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.ReferencePatterns;
045    import org.apache.geronimo.kernel.InvalidGBeanException;
046    import org.apache.geronimo.kernel.util.XmlUtil;
047    import org.apache.geronimo.kernel.repository.Artifact;
048    import org.apache.geronimo.util.EncryptionManager;
049    import org.w3c.dom.Element;
050    import org.w3c.dom.Node;
051    import org.w3c.dom.NodeList;
052    import org.w3c.dom.Document;
053    import org.xml.sax.InputSource;
054    
055    /**
056     * @version $Rev: 487175 $ $Date: 2006-12-14 03:10:31 -0800 (Thu, 14 Dec 2006) $
057     */
058    public class GBeanOverride implements Serializable {
059        private final Object name;
060        private boolean load;
061        private final Map attributes = new LinkedHashMap();
062        private final Map references = new LinkedHashMap();
063        private final ArrayList clearAttributes = new ArrayList();
064        private final ArrayList nullAttributes = new ArrayList();
065        private final ArrayList clearReferences = new ArrayList();
066        private final String gbeanInfo;
067    
068        public GBeanOverride(String name, boolean load) {
069            this.name = name;
070            this.load = load;
071            gbeanInfo = null;
072        }
073    
074        public GBeanOverride(AbstractName name, boolean load) {
075            this.name = name;
076            this.load = load;
077            gbeanInfo = null;
078        }
079    
080        public GBeanOverride(GBeanData gbeanData) throws InvalidAttributeException {
081            GBeanInfo gbeanInfo = gbeanData.getGBeanInfo();
082            this.gbeanInfo = gbeanInfo.getSourceClass();
083            if (this.gbeanInfo == null) {
084                throw new IllegalArgumentException("GBeanInfo must have a source class set");
085            }
086            name = gbeanData.getAbstractName();
087            load = true;
088    
089            // set attributes
090            for (Iterator iterator = gbeanData.getAttributes().entrySet().iterator(); iterator.hasNext();) {
091                Map.Entry entry = (Map.Entry) iterator.next();
092                String attributeName = (String) entry.getKey();
093                GAttributeInfo attributeInfo = gbeanInfo.getAttribute(attributeName);
094                if (attributeInfo == null) {
095                    throw new InvalidAttributeException("No attribute: " + attributeName + " for gbean: " + gbeanData.getAbstractName());
096                }
097                Object attributeValue = entry.getValue();
098                setAttribute(attributeName, attributeValue, attributeInfo.getType());
099            }
100    
101            // references can be coppied in blind
102            references.putAll(gbeanData.getReferences());
103        }
104    
105        public GBeanOverride(Element gbean) throws InvalidGBeanException {
106            String nameString = gbean.getAttribute("name");
107            if (nameString.indexOf('?') > -1) {
108                name = new AbstractName(URI.create(nameString));
109            } else {
110                name = nameString;
111            }
112    
113            String gbeanInfoString = gbean.getAttribute("gbeanInfo");
114            if (gbeanInfoString.length() > 0) {
115                gbeanInfo = gbeanInfoString;
116            } else {
117                gbeanInfo = null;
118            }
119            if (gbeanInfo != null && !(name instanceof AbstractName)) {
120                throw new InvalidGBeanException("A gbean element using the gbeanInfo attribute must be specified using a full AbstractName: name=" + nameString);
121            }
122    
123            String loadString = gbean.getAttribute("load");
124            load = !"false".equals(loadString);
125    
126            // attributes
127            NodeList attributes = gbean.getElementsByTagName("attribute");
128            for (int a = 0; a < attributes.getLength(); a++) {
129                Element attribute = (Element) attributes.item(a);
130    
131                String attributeName = attribute.getAttribute("name");
132    
133                // Check to see if there is a value attribute
134                if (attribute.hasAttribute("value")) {
135                    setAttribute(attributeName, (String) EncryptionManager.decrypt(attribute.getAttribute("value")));
136                    continue;
137                }
138    
139                // Check to see if there is a null attribute
140                if (attribute.hasAttribute("null")) {
141                    String nullString = attribute.getAttribute("null");
142                    if (nullString.equals("true")) {
143                        setNullAttribute(attributeName);
144                        continue;
145                    }
146                }
147    
148                String rawAttribute = getContentsAsText(attribute);
149                // If there are no contents, then it's to be cleared
150                if (rawAttribute.length() == 0) {
151                    setClearAttribute(attributeName);
152                    continue;
153                }
154                String attributeValue = (String) EncryptionManager.decrypt(rawAttribute);
155    
156                setAttribute(attributeName, attributeValue);
157            }
158    
159            // references
160            NodeList references = gbean.getElementsByTagName("reference");
161            for (int r = 0; r < references.getLength(); r++) {
162                Element reference = (Element) references.item(r);
163    
164                String referenceName = reference.getAttribute("name");
165    
166                Set objectNamePatterns = new LinkedHashSet();
167                NodeList patterns = reference.getElementsByTagName("pattern");
168    
169                // If there is no pattern, then its an empty set, so its a
170                // cleared value
171                if (patterns.getLength() == 0) {
172                    setClearReference(referenceName);
173                    continue;
174                }
175    
176                for (int p = 0; p < patterns.getLength(); p++) {
177                    Element pattern = (Element) patterns.item(p);
178                    if (pattern == null)
179                        continue;
180    
181                    String groupId = getChildAsText(pattern, "groupId");
182                    String artifactId = getChildAsText(pattern, "artifactId");
183                    String version = getChildAsText(pattern, "version");
184                    String type = getChildAsText(pattern, "type");
185                    String module = getChildAsText(pattern, "module");
186                    String name = getChildAsText(pattern, "name");
187    
188                    Artifact referenceArtifact = null;
189                    if (artifactId != null) {
190                        referenceArtifact = new Artifact(groupId, artifactId, version, type);
191                    }
192                    Map nameMap = new HashMap();
193                    if (module != null) {
194                        nameMap.put("module", module);
195                    }
196                    if (name != null) {
197                        nameMap.put("name", name);
198                    }
199                    AbstractNameQuery abstractNameQuery = new AbstractNameQuery(referenceArtifact, nameMap, Collections.EMPTY_SET);
200                    objectNamePatterns.add(abstractNameQuery);
201                }
202    
203                setReferencePatterns(referenceName, new ReferencePatterns(objectNamePatterns));
204            }
205        }
206    
207        private static String getChildAsText(Element element, String name) throws InvalidGBeanException {
208            NodeList children = element.getElementsByTagName(name);
209            if (children == null || children.getLength() == 0) {
210                return null;
211            }
212            if (children.getLength() > 1) {
213                throw new InvalidGBeanException("invalid name, too many parts named: " + name);
214            }
215            return getContentsAsText((Element) children.item(0));
216        }
217    
218        private static String getContentsAsText(Element element) throws InvalidGBeanException {
219            String value = "";
220            NodeList text = element.getChildNodes();
221            for (int t = 0; t < text.getLength(); t++) {
222                Node n = text.item(t);
223                if (n.getNodeType() == Node.TEXT_NODE) {
224                    value += n.getNodeValue();
225                } else {
226                    StringWriter sw = new StringWriter();
227                    PrintWriter pw = new PrintWriter(sw);
228                    OutputFormat of = new OutputFormat(Method.XML, null, false);
229                    of.setOmitXMLDeclaration(true);
230                    XMLSerializer serializer = new XMLSerializer(pw, of);
231                    try {
232                        serializer.prepare();
233                        serializer.serializeNode(n);
234                        value += sw.toString();
235                    } catch (IOException ioe) {
236                        throw new InvalidGBeanException("Error serializing GBean element", ioe);
237                    }
238                }
239            }
240            return value.trim();
241        }
242    
243        public Object getName() {
244            return name;
245        }
246    
247        public String getGBeanInfo() {
248            return gbeanInfo;
249        }
250    
251        public boolean isLoad() {
252            return load;
253        }
254    
255        public void setLoad(boolean load) {
256            this.load = load;
257        }
258    
259        public Map getAttributes() {
260            return attributes;
261        }
262    
263        public String getAttribute(String attributeName) {
264            return (String) attributes.get(attributeName);
265        }
266    
267        public ArrayList getClearAttributes() {
268            return clearAttributes;
269        }
270    
271        public ArrayList getNullAttributes() {
272            return nullAttributes;
273        }
274    
275        public boolean getNullAttribute(String attributeName) {
276            return nullAttributes.contains(attributeName);
277        }
278    
279        public boolean getClearAttribute(String attributeName) {
280            return clearAttributes.contains(attributeName);
281        }
282    
283        public ArrayList getClearReferences() {
284            return clearReferences;
285        }
286    
287        public boolean getClearReference(String referenceName) {
288            return clearReferences.contains(referenceName);
289        }
290    
291        public void setClearAttribute(String attributeName) {
292            if (!clearAttributes.contains(attributeName))
293                clearAttributes.add(attributeName);
294        }
295    
296        public void setNullAttribute(String attributeName) {
297            if (!nullAttributes.contains(attributeName))
298                nullAttributes.add(attributeName);
299        }
300    
301        public void setClearReference(String referenceName) {
302            if (!clearReferences.contains(referenceName))
303                clearReferences.add(referenceName);
304        }
305    
306        public void setAttribute(String attributeName, Object attributeValue, String attributeType) throws InvalidAttributeException {
307            String stringValue = getAsText(attributeValue, attributeType);
308            attributes.put(attributeName, stringValue);
309        }
310    
311        public void setAttribute(String attributeName, String attributeValue) {
312            attributes.put(attributeName, attributeValue);
313        }
314    
315        public Map getReferences() {
316            return references;
317        }
318    
319        public ReferencePatterns getReferencePatterns(String name) {
320            return (ReferencePatterns) references.get(name);
321        }
322    
323        public void setReferencePatterns(String name, ReferencePatterns patterns) {
324            references.put(name, patterns);
325        }
326    
327        /**
328         * Creates a new child of the supplied parent with the data for this
329         * GBeanOverride, adds it to the parent, and then returns the new
330         * child element.
331         */
332        public Element writeXml(Document doc, Element parent) {
333            String gbeanName;
334            if (name instanceof String) {
335                gbeanName = (String) name;
336            } else {
337                gbeanName = name.toString();
338            }
339    
340            Element gbean = doc.createElement("gbean");
341            parent.appendChild(gbean);
342            gbean.setAttribute("name", gbeanName);
343            if (gbeanInfo != null) {
344                gbean.setAttribute("gbeanInfo", gbeanInfo);
345            }
346            if (!load) {
347                gbean.setAttribute("load", "false");
348            }
349    
350            // attributes
351            for (Iterator iterator = attributes.entrySet().iterator(); iterator.hasNext();) {
352                Map.Entry entry = (Map.Entry) iterator.next();
353                String name = (String) entry.getKey();
354                String value = (String) entry.getValue();
355                if (value == null) {
356                    setNullAttribute(name);
357                }
358                else {
359                    if (getNullAttribute(name)) {
360                        nullAttributes.remove(name);
361                    }
362                    if (name.toLowerCase().indexOf("password") > -1) {
363                        value = EncryptionManager.encrypt(value);
364                    }
365                    Element attribute = doc.createElement("attribute");
366                    attribute.setAttribute("name", name);
367                    gbean.appendChild(attribute);
368                    if (value.length() == 0) {
369                        attribute.setAttribute("value", "");
370                    }
371                    else {
372                        try {
373                            //
374                            // NOTE: Construct a new document to handle mixed content attribute values
375                            //       then add nodes which are children of the first node.  This allows
376                            //       value to be XML or text.
377                            //
378                            
379                            DocumentBuilderFactory factory = XmlUtil.newDocumentBuilderFactory();
380                            DocumentBuilder builder = factory.newDocumentBuilder();
381    
382                            // Wrap value in an element to be sure we can handle xml or text values
383                            String xml = "<fragment>" + value + "</fragment>";
384                            InputSource input = new InputSource(new StringReader(xml));
385                            Document fragment = builder.parse(input);
386    
387                            Node root = fragment.getFirstChild();
388                            NodeList children = root.getChildNodes();
389                            for (int i=0; i<children.getLength(); i++) {
390                                Node child = children.item(i);
391    
392                                // Import the child (and its children) into the new document
393                                child = doc.importNode(child, true);
394                                attribute.appendChild(child);
395                            }
396                        }
397                        catch (Exception e) {
398                            throw new RuntimeException("Failed to write attribute value fragment: " + e.getMessage(), e);
399                        }
400                    }
401                }
402            }
403    
404            // cleared attributes
405            for (Iterator iterator = clearAttributes.iterator(); iterator.hasNext();) {
406                String name = (String) iterator.next();
407                Element attribute = doc.createElement("attribute");
408                gbean.appendChild(attribute);
409                attribute.setAttribute("name", name);
410            }
411    
412            // Null attributes
413            for (Iterator iterator = nullAttributes.iterator(); iterator.hasNext();) {
414                String name = (String) iterator.next();
415                Element attribute = doc.createElement("attribute");
416                gbean.appendChild(attribute);
417                attribute.setAttribute("name", name);
418                attribute.setAttribute("null", "true");
419            }
420    
421            // references
422            for (Iterator iterator = references.entrySet().iterator(); iterator.hasNext();) {
423                Map.Entry entry = (Map.Entry) iterator.next();
424                String name = (String) entry.getKey();
425                ReferencePatterns patterns = (ReferencePatterns) entry.getValue();
426    
427                Element reference = doc.createElement("reference");
428                reference.setAttribute("name", name);
429                gbean.appendChild(reference);
430    
431                Set patternSet;
432                if (patterns.isResolved()) {
433                    patternSet = Collections.singleton(new AbstractNameQuery(patterns.getAbstractName()));
434                } else {
435                    patternSet = patterns.getPatterns();
436                }
437    
438                for (Iterator patternIterator = patternSet.iterator(); patternIterator.hasNext();) {
439                    AbstractNameQuery pattern = (AbstractNameQuery) patternIterator.next();
440                    Element pat = doc.createElement("pattern");
441                    reference.appendChild(pat);
442                    Artifact artifact = pattern.getArtifact();
443    
444                    if (artifact != null) {
445                        if (artifact.getGroupId() != null) {
446                            Element group = doc.createElement("groupId");
447                            group.appendChild(doc.createTextNode(artifact.getGroupId()));
448                            pat.appendChild(group);
449                        }
450                        if (artifact.getArtifactId() != null) {
451                            Element art = doc.createElement("artifactId");
452                            art.appendChild(doc.createTextNode(artifact.getArtifactId()));
453                            pat.appendChild(art);
454                        }
455                        if (artifact.getVersion() != null) {
456                            Element version = doc.createElement("version");
457                            version.appendChild(doc.createTextNode(artifact.getVersion().toString()));
458                            pat.appendChild(version);
459                        }
460                        if (artifact.getType() != null) {
461                            Element type = doc.createElement("type");
462                            type.appendChild(doc.createTextNode(artifact.getType()));
463                            pat.appendChild(type);
464                        }
465                    }
466    
467                    Map nameMap = pattern.getName();
468                    if (nameMap.get("module") != null) {
469                        Element module = doc.createElement("module");
470                        module.appendChild(doc.createTextNode(nameMap.get("module").toString()));
471                        pat.appendChild(module);
472                    }
473    
474                    if (nameMap.get("name") != null) {
475                        Element patName = doc.createElement("name");
476                        patName.appendChild(doc.createTextNode(nameMap.get("name").toString()));
477                        pat.appendChild(patName);
478                    }
479                }
480            }
481    
482            // cleared references
483            for (Iterator iterator = clearReferences.iterator(); iterator.hasNext();) {
484                String name = (String) iterator.next();
485                Element reference = doc.createElement("reference");
486                reference.setAttribute("name", name);
487                gbean.appendChild(reference);
488            }
489    
490            return gbean;
491        }
492    
493        public static String getAsText(Object value, String type) throws InvalidAttributeException {
494            try {
495                String attributeStringValue = null;
496                if (value != null) {
497                    PropertyEditor editor = PropertyEditors.findEditor(type, GBeanOverride.class.getClassLoader());
498                    if (editor == null) {
499                        throw new InvalidAttributeException("Unable to format attribute of type " + type + "; no editor found");
500                    }
501                    editor.setValue(value);
502                    attributeStringValue = editor.getAsText();
503                }
504                return attributeStringValue;
505            } catch (ClassNotFoundException e) {
506                //todo: use the Configuration's ClassLoader to load the attribute, if this ever becomes an issue
507                throw new InvalidAttributeException("Unable to store attribute type " + type);
508            }
509        }
510    }