001    /**
002     *  Licensed to the Apache Software Foundation (ASF) under one or more
003     *  contributor license agreements.  See the NOTICE file distributed with
004     *  this work for additional information regarding copyright ownership.
005     *  The ASF licenses this file to You under the Apache License, Version 2.0
006     *  (the "License"); you may not use this file except in compliance with
007     *  the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    package org.apache.geronimo.system.configuration;
018    
019    import java.beans.PropertyEditor;
020    import java.io.Reader;
021    import java.io.Serializable;
022    import java.io.StringReader;
023    import java.net.URI;
024    import java.util.Collection;
025    import java.util.Collections;
026    import java.util.HashMap;
027    import java.util.HashSet;
028    import java.util.LinkedHashMap;
029    import java.util.LinkedHashSet;
030    import java.util.Map;
031    import java.util.Set;
032    
033    import javax.xml.bind.JAXBException;
034    import javax.xml.stream.XMLStreamException;
035    
036    import org.apache.commons.logging.Log;
037    import org.apache.commons.logging.LogFactory;
038    import org.apache.geronimo.common.propertyeditor.PropertyEditors;
039    import org.apache.geronimo.gbean.AbstractName;
040    import org.apache.geronimo.gbean.AbstractNameQuery;
041    import org.apache.geronimo.gbean.GAttributeInfo;
042    import org.apache.geronimo.gbean.GBeanData;
043    import org.apache.geronimo.gbean.GBeanInfo;
044    import org.apache.geronimo.gbean.GReferenceInfo;
045    import org.apache.geronimo.gbean.ReferencePatterns;
046    import org.apache.geronimo.kernel.ClassLoading;
047    import org.apache.geronimo.kernel.InvalidGBeanException;
048    import org.apache.geronimo.kernel.config.InvalidConfigException;
049    import org.apache.geronimo.kernel.repository.Artifact;
050    import org.apache.geronimo.system.configuration.condition.JexlExpressionParser;
051    import org.apache.geronimo.system.plugin.model.AttributeType;
052    import org.apache.geronimo.system.plugin.model.GbeanType;
053    import org.apache.geronimo.system.plugin.model.ReferenceType;
054    import org.apache.geronimo.crypto.EncryptionManager;
055    
056    /**
057     * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
058     */
059    public class GBeanOverride implements Serializable {
060    
061        private static final Log log = LogFactory.getLog(GBeanOverride.class);
062    
063        public static final String ATTRIBUTE_NAMESPACE = "http://geronimo.apache.org/xml/ns/attributes-1.2";
064        private final Object name;
065        private String comment;
066        private boolean load;
067        private final Map<String, String> attributes = new LinkedHashMap<String, String>();
068        private final Map<String, String> propertyEditors = new HashMap<String, String>();
069        private final Map<String, ReferencePatterns> references = new LinkedHashMap<String, ReferencePatterns>();
070        private final Set<String> clearAttributes = new LinkedHashSet<String>();
071        private final Set<String> nullAttributes = new LinkedHashSet<String>();
072        private final Set<String> clearReferences = new LinkedHashSet<String>();
073        private final String gbeanInfo;
074        private final JexlExpressionParser expressionParser;
075    
076        public GBeanOverride(String name, boolean load, JexlExpressionParser expressionParser) {
077            this.name = name;
078            this.load = load;
079            gbeanInfo = null;
080            this.expressionParser = expressionParser;
081        }
082    
083        public GBeanOverride(AbstractName name, boolean load, JexlExpressionParser expressionParser) {
084            this.name = name;
085            this.load = load;
086            gbeanInfo = null;
087            this.expressionParser = expressionParser;
088        }
089    
090        public GBeanOverride(GBeanOverride original, String oldArtifact, String newArtifact) {
091            Object name = original.name;
092            if (name instanceof String) {
093                name = replace((String) name, oldArtifact, newArtifact);
094            } else if (name instanceof AbstractName) {
095                String value = name.toString();
096                value = replace(value, oldArtifact, newArtifact);
097                name = new AbstractName(URI.create(value));
098            }
099            this.name = name;
100            this.load = original.load;
101            this.comment = original.getComment();
102            this.attributes.putAll(original.attributes);
103            this.propertyEditors.putAll(original.propertyEditors);
104            this.references.putAll(original.references);
105            this.clearAttributes.addAll(original.clearAttributes);
106            this.nullAttributes.addAll(original.nullAttributes);
107            this.clearReferences.addAll(original.clearReferences);
108            this.gbeanInfo = original.gbeanInfo;
109            this.expressionParser = original.expressionParser;
110        }
111    
112        private static String replace(String original, String oldArtifact, String newArtifact) {
113            int pos = original.indexOf(oldArtifact);
114            if (pos == -1) {
115                return original;
116            }
117            int last = -1;
118            StringBuffer buf = new StringBuffer();
119            while (pos > -1) {
120                buf.append(original.substring(last + 1, pos));
121                buf.append(newArtifact);
122                last = pos + oldArtifact.length() - 1;
123                pos = original.indexOf(oldArtifact, last);
124            }
125            buf.append(original.substring(last + 1));
126            return buf.toString();
127        }
128    
129        public GBeanOverride(GBeanData gbeanData, JexlExpressionParser expressionParser, ClassLoader classLoader) throws InvalidAttributeException {
130            GBeanInfo gbeanInfo = gbeanData.getGBeanInfo();
131            this.gbeanInfo = gbeanInfo.getSourceClass();
132            if (this.gbeanInfo == null) {
133                throw new IllegalArgumentException("GBeanInfo must have a source class set");
134            }
135            name = gbeanData.getAbstractName();
136            load = true;
137    
138            // set attributes
139            for (Object o : gbeanData.getAttributes().entrySet()) {
140                Map.Entry entry = (Map.Entry) o;
141                String attributeName = (String) entry.getKey();
142                GAttributeInfo attributeInfo = gbeanInfo.getAttribute(attributeName);
143                if (attributeInfo == null) {
144                    throw new InvalidAttributeException("No attribute: " + attributeName + " for gbean: " + gbeanData.getAbstractName());
145                }
146                Object attributeValue = entry.getValue();
147                setAttribute(attributeName, attributeValue, attributeInfo.getType(), classLoader);
148            }
149    
150            // references can be coppied in blind
151            references.putAll(gbeanData.getReferences());
152            this.expressionParser = expressionParser;
153        }
154    
155        public GBeanOverride(GbeanType gbean, JexlExpressionParser expressionParser) throws InvalidGBeanException {
156            String nameString = gbean.getName();
157            if (nameString.indexOf('?') > -1) {
158                name = new AbstractName(URI.create(nameString));
159            } else {
160                name = nameString;
161            }
162    
163            String gbeanInfoString = gbean.getGbeanInfo();
164            if (gbeanInfoString != null && gbeanInfoString.length() > 0) {
165                gbeanInfo = gbeanInfoString;
166            } else {
167                gbeanInfo = null;
168            }
169            if (gbeanInfo != null && !(name instanceof AbstractName)) {
170                throw new InvalidGBeanException("A gbean element using the gbeanInfo attribute must be specified using a full AbstractName: name=" + nameString);
171            }
172    
173            load = gbean.isLoad();
174            comment = gbean.getComment();
175    
176            // attributes
177            for (Object o : gbean.getAttributeOrReference()) {
178                if (o instanceof AttributeType) {
179                    AttributeType attr = (AttributeType) o;
180    
181                    String propertyEditor = attr.getPropertyEditor();
182                    if (null != propertyEditor) {
183                        propertyEditors.put(attr.getName(), propertyEditor);
184                    }
185                    
186                    if (attr.isNull()) {
187                        getNullAttributes().add(attr.getName());
188                    } else {
189                        String value;
190                        try {
191                            value = AttributesXmlUtil.extractAttributeValue(attr);
192                        } catch (JAXBException e) {
193                            throw new InvalidGBeanException("Could not extract attribute value from gbean override", e);
194                        } catch (XMLStreamException e) {
195                            throw new InvalidGBeanException("Could not extract attribute value from gbean override", e);
196                        }
197                        if (value == null || value.length() == 0) {
198                            setClearAttribute(attr.getName());
199                        } else {
200                            String truevalue = (String) EncryptionManager.decrypt(value);
201                            getAttributes().put(attr.getName(), truevalue);
202                        }
203                    }
204                } else if (o instanceof ReferenceType) {
205                    ReferenceType ref = (ReferenceType) o;
206                    if (ref.getPattern().isEmpty()) {
207                        setClearReference(ref.getName());
208                    } else {
209                        Set<AbstractNameQuery> patternSet = new HashSet<AbstractNameQuery>();
210                        for (ReferenceType.Pattern pattern : ref.getPattern()) {
211                            String groupId = pattern.getGroupId();
212                            String artifactId = pattern.getArtifactId();
213                            String version = pattern.getVersion();
214                            String type = pattern.getType();
215                            String module = pattern.getModule();
216                            String name = pattern.getName();
217    
218                            Artifact referenceArtifact = null;
219                            if (artifactId != null) {
220                                referenceArtifact = new Artifact(groupId, artifactId, version, type);
221                            }
222                            Map<String, String> nameMap = new HashMap<String, String>();
223                            if (module != null) {
224                                nameMap.put("module", module);
225                            }
226                            if (name != null) {
227                                nameMap.put("name", name);
228                            }
229                            AbstractNameQuery abstractNameQuery = new AbstractNameQuery(referenceArtifact, nameMap, Collections.EMPTY_SET);
230                            patternSet.add(abstractNameQuery);
231                        }
232                        ReferencePatterns patterns = new ReferencePatterns(patternSet);
233                        setReferencePatterns(ref.getName(), patterns);
234                    }
235                }
236            }
237            this.expressionParser = expressionParser;
238        }
239    
240        public Object getName() {
241            return name;
242        }
243    
244        public String getGBeanInfo() {
245            return gbeanInfo;
246        }
247    
248        public String getComment() {
249            return comment;
250        }
251    
252        public void setComment(String comment) {
253            this.comment = comment;
254        }
255    
256        public boolean isLoad() {
257            return load;
258        }
259    
260        public void setLoad(boolean load) {
261            this.load = load;
262        }
263    
264        public Map<String, String> getAttributes() {
265            return attributes;
266        }
267    
268        public String getAttribute(String attributeName) {
269            return attributes.get(attributeName);
270        }
271    
272        public Set<String> getClearAttributes() {
273            return clearAttributes;
274        }
275    
276        public Set<String> getNullAttributes() {
277            return nullAttributes;
278        }
279    
280        public boolean isNullAttribute(String attributeName) {
281            return nullAttributes.contains(attributeName);
282        }
283    
284        public boolean isClearAttribute(String attributeName) {
285            return clearAttributes.contains(attributeName);
286        }
287    
288        public Set<String> getClearReferences() {
289            return clearReferences;
290        }
291    
292        public boolean isClearReference(String referenceName) {
293            return clearReferences.contains(referenceName);
294        }
295    
296        public void setClearAttribute(String attributeName) {
297            clearAttributes.add(attributeName);
298        }
299    
300        public void setNullAttribute(String attributeName) {
301            nullAttributes.add(attributeName);
302        }
303    
304        public void setClearReference(String referenceName) {
305            clearReferences.add(referenceName);
306        }
307    
308        public void setAttribute(String attributeName, Object attributeValue, String attributeType, ClassLoader classLoader) throws InvalidAttributeException {
309            String stringValue = getAsText(attributeName, attributeValue, attributeType, classLoader);
310            setAttribute(attributeName, stringValue);
311        }
312    
313        public void setAttribute(String attributeName, String attributeValue) {
314            if (attributeValue == null || attributeValue.length() == 0) {
315                clearAttributes.add(attributeName);
316            } else {
317                attributes.put(attributeName, attributeValue);
318            }
319        }
320        
321        public Map<String, ReferencePatterns> getReferences() {
322            return references;
323        }
324    
325        public ReferencePatterns getReferencePatterns(String name) {
326            return references.get(name);
327        }
328    
329        public void setReferencePatterns(String name, ReferencePatterns patterns) {
330            references.put(name, patterns);
331        }
332    
333        public boolean applyOverrides(GBeanData data, Artifact configName, AbstractName gbeanName, ClassLoader classLoader) throws InvalidConfigException {
334            if (!isLoad()) {
335                return false;
336            }
337    
338            GBeanInfo gbeanInfo = data.getGBeanInfo();
339    
340            // set attributes
341            for (Map.Entry<String, String> entry : getAttributes().entrySet()) {
342                String attributeName = entry.getKey();
343                GAttributeInfo attributeInfo = gbeanInfo.getAttribute(attributeName);
344                if (attributeInfo == null) {
345                    throw new InvalidConfigException("No attribute: " + attributeName + " for gbean: " + data.getAbstractName());
346                }
347                String valueString = entry.getValue();
348                Object value = getValue(attributeInfo, valueString, configName, gbeanName, classLoader);
349                data.setAttribute(attributeName, value);
350            }
351    
352            //Clear attributes
353            for (String attribute : getClearAttributes()) {
354                data.clearAttribute(attribute);
355            }
356    
357            //Null attributes
358            for (String attribute : getNullAttributes()) {
359                data.setAttribute(attribute, null);
360            }
361    
362            // set references
363            for (Map.Entry<String, ReferencePatterns> entry : getReferences().entrySet()) {
364                String referenceName = entry.getKey();
365                GReferenceInfo referenceInfo = gbeanInfo.getReference(referenceName);
366                if (referenceInfo == null) {
367                    throw new InvalidConfigException("No reference: " + referenceName + " for gbean: " + data.getAbstractName());
368                }
369    
370                ReferencePatterns referencePatterns = entry.getValue();
371    
372                data.setReferencePatterns(referenceName, referencePatterns);
373            }
374    
375            //Clear references
376            for (String reference : getClearReferences()) {
377                data.clearReference(reference);
378            }
379    
380            return true;
381        }
382    
383        private synchronized Object getValue(GAttributeInfo attribute, String value, Artifact configurationName, AbstractName gbeanName, ClassLoader classLoader) {
384            if (value == null) {
385                return null;
386            }
387            value = substituteVariables(attribute.getName(), value);
388            PropertyEditor editor = loadPropertyEditor(attribute, classLoader);
389            editor.setAsText(value);
390            log.debug("Setting value for " + configurationName + "/" + gbeanName + "/" + attribute.getName() + " to value " + value);
391            return editor.getValue();
392        }
393    
394        protected PropertyEditor loadPropertyEditor(GAttributeInfo attribute, ClassLoader classLoader) {
395            String propertyEditor = propertyEditors.get(attribute.getName());
396            if (null == propertyEditor) {
397                PropertyEditor editor;
398                try {
399                    editor = PropertyEditors.findEditor(attribute.getType(), classLoader);
400                } catch (ClassNotFoundException e) {
401                    throw new IllegalStateException("Unable to load property editor for attribute type: " + attribute.getType());
402                }            
403                if (editor == null) {
404                    throw new IllegalStateException("Unable to parse attribute of type " + attribute.getType() + "; no editor found");
405                }
406                return editor;
407            } else {
408                try {
409                    Class propertyEditorClass = classLoader.loadClass(propertyEditor);
410                    return (PropertyEditor) propertyEditorClass.newInstance();
411                } catch (Exception ex) {
412                    throw new IllegalStateException("Cannot load property editor [" + propertyEditor + "]", ex);
413                }
414            }
415        }
416    
417        public String substituteVariables(String attributeName, String input) {
418            if (expressionParser != null) {
419                return expressionParser.parse(input);
420            }
421            return input;
422        }
423    
424        /**
425         * Creates a new child of the supplied parent with the data for this
426         * GBeanOverride, adds it to the parent, and then returns the new
427         * child element.
428         *
429         * @return newly created element for this override
430         */
431        public GbeanType writeXml() {
432            GbeanType gbean = new GbeanType();
433            String gbeanName;
434            if (name instanceof String) {
435                gbeanName = (String) name;
436            } else {
437                gbeanName = name.toString();
438            }
439            gbean.setName(gbeanName);
440            if (gbeanInfo != null) {
441                gbean.setGbeanInfo(gbeanInfo);
442            }
443            if (!load) {
444                gbean.setLoad(false);
445            }
446            if (comment != null) {
447                gbean.setComment(comment);
448            }
449    
450            // attributes
451            for (Map.Entry<String, String> entry : attributes.entrySet()) {
452                String name = entry.getKey();
453                String value = entry.getValue();
454                if (value == null) {
455                    setNullAttribute(name);
456                } else {
457                    if (isNullAttribute(name)) {
458                        nullAttributes.remove(name);
459                    }
460                    if (name.toLowerCase().indexOf("password") > -1) {
461                        value = EncryptionManager.encrypt(value);
462                    }
463    /**
464     * if there was a value such as jdbc url with &amp; then when that value was oulled
465     * from the config.xml the &amp; would have been replaced/converted to '&', we need to check
466     * and change it back because an & would create a parse exception.
467     */
468                    value = "<attribute xmlns='" + ATTRIBUTE_NAMESPACE + "'>" + value.replaceAll("&(?!amp;)", "&amp;") + "</attribute>";
469                    Reader reader = new StringReader(value);
470                    try {
471                        AttributeType attribute = AttributesXmlUtil.loadAttribute(reader);
472                        attribute.setName(name);
473                        String editorClass = propertyEditors.get(name);
474                        if (null != editorClass) {
475                            attribute.setPropertyEditor(editorClass);
476                        }
477                        gbean.getAttributeOrReference().add(attribute);
478                    } catch (Exception e) {
479                        log.error("Could not serialize attribute " + name + " in gbean " + gbeanName + ", value: " + value, e);
480                    }
481                }
482            }
483    
484            // cleared attributes
485            for (String name : clearAttributes) {
486                AttributeType attribute = new AttributeType();
487                attribute.setName(name);
488                gbean.getAttributeOrReference().add(attribute);
489            }
490    
491            // Null attributes
492            for (String name : nullAttributes) {
493                AttributeType attribute = new AttributeType();
494                attribute.setName(name);
495                attribute.setNull(true);
496                gbean.getAttributeOrReference().add(attribute);
497            }
498    
499            // references
500            for (Map.Entry<String, ReferencePatterns> entry : references.entrySet()) {
501                String name = entry.getKey();
502                ReferencePatterns patterns = entry.getValue();
503                ReferenceType reference = new ReferenceType();
504                reference.setName(name);
505    
506                Set<AbstractNameQuery> patternSet;
507                if (patterns.isResolved()) {
508                    patternSet = Collections.singleton(new AbstractNameQuery(patterns.getAbstractName()));
509                } else {
510                    patternSet = patterns.getPatterns();
511                }
512    
513                for (AbstractNameQuery pattern : patternSet) {
514                    ReferenceType.Pattern patternType = new ReferenceType.Pattern();
515                    Artifact artifact = pattern.getArtifact();
516    
517                    if (artifact != null) {
518                        if (artifact.getGroupId() != null) {
519                            patternType.setGroupId(artifact.getGroupId());
520                        }
521                        if (artifact.getArtifactId() != null) {
522                            patternType.setArtifactId(artifact.getArtifactId());
523                        }
524                        if (artifact.getVersion() != null) {
525                            patternType.setVersion(artifact.getVersion().toString());
526                        }
527                        if (artifact.getType() != null) {
528                            patternType.setType(artifact.getType());
529                        }
530                    }
531    
532                    Map nameMap = pattern.getName();
533                    if (nameMap.get("module") != null) {
534                        patternType.setModule((String) nameMap.get("module"));
535                    }
536    
537                    if (nameMap.get("name") != null) {
538                        patternType.setName((String) nameMap.get("name"));
539                    }
540                    reference.getPattern().add(patternType);
541                }
542                gbean.getAttributeOrReference().add(reference);
543            }
544    
545            // cleared references
546            for (String name : clearReferences) {
547                ReferenceType reference = new ReferenceType();
548                reference.setName(name);
549                gbean.getAttributeOrReference().add(reference);
550            }
551    
552            return gbean;
553        }
554    
555        protected String getAsText(String attributeName, Object value, String type, ClassLoader classLoader) throws InvalidAttributeException {
556            try {
557                if (null == value || value instanceof String) {
558                    return (String) value;
559                }
560                
561                Class typeClass = ClassLoading.loadClass(type, classLoader);
562                PropertyEditor editor = PropertyEditors.findEditor(value.getClass());
563                if (null == editor) {
564                    editor = PropertyEditors.findEditor(typeClass);
565                    if (null == editor) {
566                        throw new InvalidAttributeException("Unable to format attribute of type " + type + "; no editor found");
567                    }
568                }
569                
570                if (!type.equals(value.getClass().getName())
571                        && !typeClass.isPrimitive()
572                        && !Collection.class.isAssignableFrom(typeClass)) {
573                    propertyEditors.put(attributeName, editor.getClass().getName());
574                }
575    
576                editor.setValue(value);
577                return editor.getAsText();
578            } catch (ClassNotFoundException e) {
579                //todo: use the Configuration's ClassLoader to load the attribute, if this ever becomes an issue
580                throw (InvalidAttributeException) new InvalidAttributeException("Unable to store attribute type " + type).initCause(e);
581            }
582        }
583    }