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: 889880 $ $Date: 2009-12-11 21:45:24 -0500 (Fri, 11 Dec 2009) $
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> encryptedAttributes = new LinkedHashSet<String>();
073        private final Set<String> clearReferences = new LinkedHashSet<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.comment = original.getComment();
103            this.attributes.putAll(original.attributes);
104            this.propertyEditors.putAll(original.propertyEditors);
105            this.references.putAll(original.references);
106            this.clearAttributes.addAll(original.clearAttributes);
107            this.nullAttributes.addAll(original.nullAttributes);
108            this.encryptedAttributes.addAll(original.encryptedAttributes);
109            this.clearReferences.addAll(original.clearReferences);
110            this.gbeanInfo = original.gbeanInfo;
111            this.expressionParser = original.expressionParser;
112        }
113    
114        private static String replace(String original, String oldArtifact, String newArtifact) {
115            int pos = original.indexOf(oldArtifact);
116            if (pos == -1) {
117                return original;
118            }
119            int last = -1;
120            StringBuffer buf = new StringBuffer();
121            while (pos > -1) {
122                buf.append(original.substring(last + 1, pos));
123                buf.append(newArtifact);
124                last = pos + oldArtifact.length() - 1;
125                pos = original.indexOf(oldArtifact, last);
126            }
127            buf.append(original.substring(last + 1));
128            return buf.toString();
129        }
130    
131        public GBeanOverride(GBeanData gbeanData, JexlExpressionParser expressionParser, ClassLoader classLoader) throws InvalidAttributeException {
132            GBeanInfo gbeanInfo = gbeanData.getGBeanInfo();
133            this.gbeanInfo = gbeanInfo.getSourceClass();
134            if (this.gbeanInfo == null) {
135                throw new IllegalArgumentException("GBeanInfo must have a source class set");
136            }
137            name = gbeanData.getAbstractName();
138            load = true;
139    
140            // set attributes
141            for (Object o : gbeanData.getAttributes().entrySet()) {
142                Map.Entry entry = (Map.Entry) o;
143                String attributeName = (String) entry.getKey();
144                GAttributeInfo attributeInfo = gbeanInfo.getAttribute(attributeName);
145                if (attributeInfo == null) {
146                    throw new InvalidAttributeException("No attribute: " + attributeName + " for gbean: " + gbeanData.getAbstractName());
147                }
148                // TODO: shouldn't we only save manageable attributes here?
149                Object attributeValue = entry.getValue();
150                setAttribute(attributeInfo, attributeValue, classLoader);
151                if (attributeInfo.isEncrypted() && attributes.containsKey(attributeName)) {
152                    encryptedAttributes.add(attributeName);
153                }
154            }
155    
156            // references can be coppied in blind
157            references.putAll(gbeanData.getReferences());
158            this.expressionParser = expressionParser;
159        }
160    
161        public GBeanOverride(GbeanType gbean, JexlExpressionParser expressionParser) throws InvalidGBeanException {
162            String nameString = gbean.getName();
163            if (nameString.indexOf('?') > -1) {
164                name = new AbstractName(URI.create(nameString));
165            } else {
166                name = nameString;
167            }
168    
169            String gbeanInfoString = gbean.getGbeanInfo();
170            if (gbeanInfoString != null && gbeanInfoString.length() > 0) {
171                gbeanInfo = gbeanInfoString;
172            } else {
173                gbeanInfo = null;
174            }
175            if (gbeanInfo != null && !(name instanceof AbstractName)) {
176                throw new InvalidGBeanException("A gbean element using the gbeanInfo attribute must be specified using a full AbstractName: name=" + nameString);
177            }
178    
179            load = gbean.isLoad();
180            comment = gbean.getComment();
181    
182            // attributes
183            for (Object o : gbean.getAttributeOrReference()) {
184                if (o instanceof AttributeType) {
185                    AttributeType attr = (AttributeType) o;
186    
187                    String propertyEditor = attr.getPropertyEditor();
188                    if (null != propertyEditor) {
189                        propertyEditors.put(attr.getName(), propertyEditor);
190                    }
191                    
192                    if (attr.isNull()) {
193                        setNullAttribute(attr.getName());
194                    } else {
195                        String value;
196                        try {
197                            value = AttributesXmlUtil.extractAttributeValue(attr);
198                        } catch (JAXBException e) {
199                            throw new InvalidGBeanException("Could not extract attribute value from gbean override", e);
200                        } catch (XMLStreamException e) {
201                            throw new InvalidGBeanException("Could not extract attribute value from gbean override", e);
202                        }
203                        if (value == null || value.length() == 0) {
204                            setClearAttribute(attr.getName());
205                        } else {
206                            String truevalue = (String) EncryptionManager.decrypt(value);
207                            setAttribute(attr.getName(), truevalue);
208                            /*
209                             * If the original value is encrypted, retain this 
210                             * meta-information.
211                             */
212                            if (!value.equals(truevalue)
213                                    && attributes.containsKey(attr.getName())) {
214                                encryptedAttributes.add(attr.getName());
215                            }
216                        }
217                    }
218                } else if (o instanceof ReferenceType) {
219                    ReferenceType ref = (ReferenceType) o;
220                    if (ref.getPattern().isEmpty()) {
221                        setClearReference(ref.getName());
222                    } else {
223                        Set<AbstractNameQuery> patternSet = new HashSet<AbstractNameQuery>();
224                        for (ReferenceType.Pattern pattern : ref.getPattern()) {
225                            String groupId = pattern.getGroupId();
226                            String artifactId = pattern.getArtifactId();
227                            String version = pattern.getVersion();
228                            String type = pattern.getType();
229                            String module = pattern.getModule();
230                            String name = pattern.getName();
231    
232                            Artifact referenceArtifact = null;
233                            if (artifactId != null) {
234                                referenceArtifact = new Artifact(groupId, artifactId, version, type);
235                            }
236                            Map<String, String> nameMap = new HashMap<String, String>();
237                            if (module != null) {
238                                nameMap.put("module", module);
239                            }
240                            if (name != null) {
241                                nameMap.put("name", name);
242                            }
243                            AbstractNameQuery abstractNameQuery = new AbstractNameQuery(referenceArtifact, nameMap, Collections.EMPTY_SET);
244                            patternSet.add(abstractNameQuery);
245                        }
246                        ReferencePatterns patterns = new ReferencePatterns(patternSet);
247                        setReferencePatterns(ref.getName(), patterns);
248                    }
249                }
250            }
251            this.expressionParser = expressionParser;
252        }
253    
254        public Object getName() {
255            return name;
256        }
257    
258        public String getGBeanInfo() {
259            return gbeanInfo;
260        }
261    
262        public String getComment() {
263            return comment;
264        }
265    
266        public void setComment(String comment) {
267            this.comment = comment;
268        }
269    
270        public boolean isLoad() {
271            return load;
272        }
273    
274        public void setLoad(boolean load) {
275            this.load = load;
276        }
277    
278        public Map<String, String> getAttributes() {
279            return attributes;
280        }
281    
282        public String getAttribute(String attributeName) {
283            return attributes.get(attributeName);
284        }
285    
286        public Set<String> getClearAttributes() {
287            return clearAttributes;
288        }
289    
290        public Set<String> getNullAttributes() {
291            return nullAttributes;
292        }
293    
294        public boolean isNullAttribute(String attributeName) {
295            return nullAttributes.contains(attributeName);
296        }
297    
298        public boolean isClearAttribute(String attributeName) {
299            return clearAttributes.contains(attributeName);
300        }
301    
302        public Set<String> getClearReferences() {
303            return clearReferences;
304        }
305    
306        public boolean isClearReference(String referenceName) {
307            return clearReferences.contains(referenceName);
308        }
309    
310        public void setClearAttribute(String attributeName) {
311            clearAttributes.add(attributeName);
312            // remove attribute from other maps
313            nullAttributes.remove(attributeName);
314            attributes.remove(attributeName);
315        }
316    
317        public void setNullAttribute(String attributeName) {
318            nullAttributes.add(attributeName);
319            // remove attribute from other maps
320            clearAttributes.remove(attributeName);
321            attributes.remove(attributeName);
322        }
323    
324        public void setClearReference(String referenceName) {
325            clearReferences.add(referenceName);
326            references.remove(referenceName);
327        }
328    
329        public void setAttribute(GAttributeInfo attrInfo, Object attributeValue,
330                ClassLoader classLoader) throws InvalidAttributeException {
331            setAttribute(attrInfo.getName(), attributeValue, attrInfo.getType(),
332                    classLoader);
333            if (attrInfo.isEncrypted()
334                    && attributes.containsKey(attrInfo.getName())) {
335                encryptedAttributes.add(attrInfo.getName());
336            } else {
337                encryptedAttributes.remove(attrInfo.getName());
338            }
339        }
340    
341        /**
342         * This method should be discouraged for usage outside in future, as it does
343         * not pass in encryption meta-information about the attribute being set.
344         * 
345         * Use setAttribute(GAttributeInfo attrInfo, Object attributeValue,
346         * ClassLoader classLoader) instead.
347         * 
348         * @deprecated
349         */
350        void setAttribute(String attributeName, Object attributeValue, String attributeType, ClassLoader classLoader) throws InvalidAttributeException {
351            String stringValue = getAsText(attributeName, attributeValue, attributeType, classLoader);
352            setAttribute(attributeName, stringValue);
353        }
354    
355    
356        /**
357         * This method should be discouraged for usage outside in future, as it does
358         * not pass in encryption meta-information about the attribute being set.
359         * 
360         * Use setAttribute(GAttributeInfo attrInfo, Object attributeValue,
361         * ClassLoader classLoader) instead.
362         * 
363         * @deprecated
364         */
365        void setAttribute(String attributeName, String attributeValue) {
366            if (attributeValue == null || attributeValue.length() == 0) {
367                setClearAttribute(attributeName);
368            } else {
369                attributes.put(attributeName, attributeValue);
370                // remove attribute from other maps
371                clearAttributes.remove(attributeName);
372                nullAttributes.remove(attributeName);
373            }
374        }
375        
376        public Map<String, ReferencePatterns> getReferences() {
377            return references;
378        }
379    
380        public ReferencePatterns getReferencePatterns(String name) {
381            return references.get(name);
382        }
383    
384        public void setReferencePatterns(String name, ReferencePatterns patterns) {
385            references.put(name, patterns);
386            clearReferences.remove(name);
387        }
388    
389        public boolean applyOverrides(GBeanData data, Artifact configName, AbstractName gbeanName, ClassLoader classLoader) throws InvalidConfigException {
390            if (!isLoad()) {
391                return false;
392            }
393    
394            GBeanInfo gbeanInfo = data.getGBeanInfo();
395    
396            // set attributes
397            for (Map.Entry<String, String> entry : getAttributes().entrySet()) {
398                String attributeName = entry.getKey();
399                GAttributeInfo attributeInfo = gbeanInfo.getAttribute(attributeName);
400                if (attributeInfo == null) {
401                    throw new InvalidConfigException("No attribute: " + attributeName + " for gbean: " + data.getAbstractName());
402                }
403                String valueString = entry.getValue();
404                Object value = getValue(attributeInfo, valueString, configName, gbeanName, classLoader);
405                data.setAttribute(attributeName, value);
406                // Read the latest encryption meta-information on attributes
407                if (attributeInfo.isEncrypted()) {
408                    encryptedAttributes.add(attributeName);
409                } else {
410                    encryptedAttributes.remove(attributeName);
411                }
412            }
413    
414            //Clear attributes
415            for (String attribute : getClearAttributes()) {
416                data.clearAttribute(attribute);
417            }
418    
419            //Null attributes
420            for (String attribute : getNullAttributes()) {
421                data.setAttribute(attribute, null);
422            }
423    
424            // set references
425            for (Map.Entry<String, ReferencePatterns> entry : getReferences().entrySet()) {
426                String referenceName = entry.getKey();
427                GReferenceInfo referenceInfo = gbeanInfo.getReference(referenceName);
428                if (referenceInfo == null) {
429                    throw new InvalidConfigException("No reference: " + referenceName + " for gbean: " + data.getAbstractName());
430                }
431    
432                ReferencePatterns referencePatterns = entry.getValue();
433    
434                data.setReferencePatterns(referenceName, referencePatterns);
435            }
436    
437            //Clear references
438            for (String reference : getClearReferences()) {
439                data.clearReference(reference);
440            }
441    
442            return true;
443        }
444    
445        private synchronized Object getValue(GAttributeInfo attribute, String value, Artifact configurationName, AbstractName gbeanName, ClassLoader classLoader) {
446            if (value == null) {
447                return null;
448            }
449            value = substituteVariables(attribute.getName(), value);
450            PropertyEditor editor = loadPropertyEditor(attribute, classLoader);
451            editor.setAsText(value);
452            log.debug("Setting value for " + configurationName + "/" + gbeanName + "/" + attribute.getName() + " to value " + value);
453            return editor.getValue();
454        }
455    
456        protected PropertyEditor loadPropertyEditor(GAttributeInfo attribute, ClassLoader classLoader) {
457            String propertyEditor = propertyEditors.get(attribute.getName());
458            if (null == propertyEditor) {
459                PropertyEditor editor;
460                try {
461                    editor = PropertyEditors.findEditor(attribute.getType(), classLoader);
462                } catch (ClassNotFoundException e) {
463                    throw new IllegalStateException("Unable to load property editor for attribute type: " + attribute.getType());
464                }            
465                if (editor == null) {
466                    throw new IllegalStateException("Unable to parse attribute of type " + attribute.getType() + "; no editor found");
467                }
468                return editor;
469            } else {
470                try {
471                    Class propertyEditorClass = classLoader.loadClass(propertyEditor);
472                    return (PropertyEditor) propertyEditorClass.newInstance();
473                } catch (Exception ex) {
474                    throw new IllegalStateException("Cannot load property editor [" + propertyEditor + "]", ex);
475                }
476            }
477        }
478    
479        public String substituteVariables(String attributeName, String input) {
480            if (expressionParser != null) {
481                return expressionParser.parse(input);
482            }
483            return input;
484        }
485    
486        /**
487         * Creates a new child of the supplied parent with the data for this
488         * GBeanOverride, adds it to the parent, and then returns the new
489         * child element.
490         *
491         * @return newly created element for this override
492         */
493        public GbeanType writeXml() {
494            GbeanType gbean = new GbeanType();
495            String gbeanName;
496            if (name instanceof String) {
497                gbeanName = (String) name;
498            } else {
499                gbeanName = name.toString();
500            }
501            gbean.setName(gbeanName);
502            if (gbeanInfo != null) {
503                gbean.setGbeanInfo(gbeanInfo);
504            }
505            if (!load) {
506                gbean.setLoad(false);
507            }
508            if (comment != null) {
509                gbean.setComment(comment);
510            }
511    
512            // attributes
513            for (Map.Entry<String, String> entry : attributes.entrySet()) {
514                String name = entry.getKey();
515                String value = entry.getValue();
516                if (value == null) {
517                    nullAttributes.add(name);
518                    clearAttributes.remove(name);
519                } else {                
520                    nullAttributes.remove(name);
521                    clearAttributes.remove(name);
522                  
523                    if (encryptedAttributes.contains(name)) {
524                        value = EncryptionManager.encrypt(value);
525                    }
526    
527    /**
528     * if there was a value such as jdbc url with &amp; then when that value was oulled
529     * from the config.xml the &amp; would have been replaced/converted to '&', we need to check
530     * and change it back because an & would create a parse exception.
531     */
532                    value = "<attribute xmlns='" + ATTRIBUTE_NAMESPACE + "'>" + value.replaceAll("&(?!amp;)", "&amp;") + "</attribute>";
533                    Reader reader = new StringReader(value);
534                    try {
535                        AttributeType attribute = AttributesXmlUtil.loadAttribute(reader);
536                        attribute.setName(name);
537                        String editorClass = propertyEditors.get(name);
538                        if (null != editorClass) {
539                            attribute.setPropertyEditor(editorClass);
540                        }
541                        gbean.getAttributeOrReference().add(attribute);
542                    } catch (Exception e) {
543                        log.error("Could not serialize attribute " + name + " in gbean " + gbeanName + ", value: " + value, e);
544                    }
545                }
546            }
547    
548            // cleared attributes
549            for (String name : clearAttributes) {
550                AttributeType attribute = new AttributeType();
551                attribute.setName(name);
552                gbean.getAttributeOrReference().add(attribute);
553            }
554    
555            // Null attributes
556            for (String name : nullAttributes) {
557                AttributeType attribute = new AttributeType();
558                attribute.setName(name);
559                attribute.setNull(true);
560                gbean.getAttributeOrReference().add(attribute);
561            }
562    
563            // references
564            for (Map.Entry<String, ReferencePatterns> entry : references.entrySet()) {
565                String name = entry.getKey();
566                ReferencePatterns patterns = entry.getValue();
567                ReferenceType reference = new ReferenceType();
568                reference.setName(name);
569    
570                Set<AbstractNameQuery> patternSet;
571                if (patterns.isResolved()) {
572                    patternSet = Collections.singleton(new AbstractNameQuery(patterns.getAbstractName()));
573                } else {
574                    patternSet = patterns.getPatterns();
575                }
576    
577                for (AbstractNameQuery pattern : patternSet) {
578                    ReferenceType.Pattern patternType = new ReferenceType.Pattern();
579                    Artifact artifact = pattern.getArtifact();
580    
581                    if (artifact != null) {
582                        if (artifact.getGroupId() != null) {
583                            patternType.setGroupId(artifact.getGroupId());
584                        }
585                        if (artifact.getArtifactId() != null) {
586                            patternType.setArtifactId(artifact.getArtifactId());
587                        }
588                        if (artifact.getVersion() != null) {
589                            patternType.setVersion(artifact.getVersion().toString());
590                        }
591                        if (artifact.getType() != null) {
592                            patternType.setType(artifact.getType());
593                        }
594                    }
595    
596                    Map nameMap = pattern.getName();
597                    if (nameMap.get("module") != null) {
598                        patternType.setModule((String) nameMap.get("module"));
599                    }
600    
601                    if (nameMap.get("name") != null) {
602                        patternType.setName((String) nameMap.get("name"));
603                    }
604                    reference.getPattern().add(patternType);
605                }
606                gbean.getAttributeOrReference().add(reference);
607            }
608    
609            // cleared references
610            for (String name : clearReferences) {
611                ReferenceType reference = new ReferenceType();
612                reference.setName(name);
613                gbean.getAttributeOrReference().add(reference);
614            }
615    
616            return gbean;
617        }
618    
619        protected String getAsText(String attributeName, Object value, String type, ClassLoader classLoader) throws InvalidAttributeException {
620            try {
621                if (null == value || value instanceof String) {
622                    return (String) value;
623                }
624                
625                Class typeClass = ClassLoading.loadClass(type, classLoader);
626                PropertyEditor editor = PropertyEditors.findEditor(value.getClass());
627                if (null == editor) {
628                    editor = PropertyEditors.findEditor(typeClass);
629                    if (null == editor) {
630                        throw new InvalidAttributeException("Unable to format attribute of type " + type + "; no editor found");
631                    }
632                }
633                
634                if (!type.equals(value.getClass().getName())
635                        && !typeClass.isPrimitive()
636                        && !Collection.class.isAssignableFrom(typeClass)) {
637                    propertyEditors.put(attributeName, editor.getClass().getName());
638                }
639    
640                editor.setValue(value);
641                return editor.getAsText();
642            } catch (ClassNotFoundException e) {
643                //todo: use the Configuration's ClassLoader to load the attribute, if this ever becomes an issue
644                throw (InvalidAttributeException) new InvalidAttributeException("Unable to store attribute type " + type).initCause(e);
645            }
646        }
647    }