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