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.io.File;
020    import java.io.FileInputStream;
021    import java.io.FileOutputStream;
022    import java.io.IOException;
023    import java.io.Writer;
024    import java.io.Reader;
025    import java.io.FileReader;
026    import java.io.BufferedReader;
027    import java.io.FileWriter;
028    import java.io.BufferedWriter;
029    import java.util.ArrayList;
030    import java.util.Collection;
031    import java.util.HashMap;
032    import java.util.Iterator;
033    import java.util.List;
034    import java.util.Map;
035    import java.util.Properties;
036    import java.util.Timer;
037    import java.util.TimerTask;
038    
039    import javax.xml.parsers.ParserConfigurationException;
040    import javax.xml.bind.JAXBException;
041    import javax.xml.stream.XMLStreamException;
042    
043    import org.apache.commons.logging.Log;
044    import org.apache.commons.logging.LogFactory;
045    import org.apache.geronimo.gbean.AbstractName;
046    import org.apache.geronimo.gbean.GAttributeInfo;
047    import org.apache.geronimo.gbean.GBeanData;
048    import org.apache.geronimo.gbean.GBeanInfo;
049    import org.apache.geronimo.gbean.GBeanInfoBuilder;
050    import org.apache.geronimo.gbean.GBeanLifecycle;
051    import org.apache.geronimo.gbean.GReferenceInfo;
052    import org.apache.geronimo.gbean.ReferencePatterns;
053    import org.apache.geronimo.kernel.InvalidGBeanException;
054    import org.apache.geronimo.kernel.config.Configuration;
055    import org.apache.geronimo.kernel.config.InvalidConfigException;
056    import org.apache.geronimo.kernel.config.ManageableAttributeStore;
057    import org.apache.geronimo.kernel.config.PersistentConfigurationList;
058    import org.apache.geronimo.kernel.repository.Artifact;
059    import org.apache.geronimo.system.configuration.condition.JexlExpressionParser;
060    import org.apache.geronimo.system.configuration.condition.ParserUtils;
061    import org.apache.geronimo.system.plugin.model.GbeanType;
062    import org.apache.geronimo.system.plugin.model.AttributesType;
063    import org.apache.geronimo.system.serverinfo.ServerInfo;
064    import org.xml.sax.SAXException;
065    
066    /**
067     * Stores managed attributes in an XML file on the local filesystem.
068     *
069     * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
070     */
071    public class LocalAttributeManager implements LocalPluginAttributeStore, PersistentConfigurationList, GBeanLifecycle {
072        private static final Log log = LogFactory.getLog(LocalAttributeManager.class);
073    
074        private static final String CONFIG_FILE_PROPERTY = "org.apache.geronimo.config.file";
075        private final static String SUBSTITUTIONS_FILE_PROPERTY = "org.apache.geronimo.config.substitutions.file";
076        private final static String SUBSTITUTION_PREFIX_PREFIX = "org.apache.geronimo.config.substitution.prefix";
077    
078        private static final String BACKUP_EXTENSION = ".bak";
079        private static final String TEMP_EXTENSION = ".working";
080        private static final int SAVE_BUFFER_MS = 5000;
081    
082        private final ServerInfo serverInfo;
083        private final String configFile;
084        private final boolean readOnly;
085        private final JexlExpressionParser expressionParser;
086    
087        private File attributeFile;
088        private File backupFile;
089        private File tempFile;
090        private ServerOverride serverOverride;
091    
092        private Timer timer;
093        private TimerTask currentTask;
094    
095        private boolean kernelFullyStarted;
096    
097        private String prefix;
098        private File configSubstitutionsFile;
099        private Properties localConfigSubstitutions;
100        private String resolvedPropertiesFile;
101        private static final byte[] INSTRUCTION = ("# Put variables and their substitution values in this file. \n"
102                + "# They will be used when processing the corresponding config.xml. \n"
103                + "# Values in this file can be overridden by environment variables and system properties \n"
104                + "# by prefixing the property name with 'org.apache.geronimo.config.substitution.' \n"
105                + "# For example, an entry such as hostName=localhost \n"
106                + "# can be overridden by an environment variable or system property org.apache.geronimo.config.substitution.hostName=foo \n"
107                + "# When running multiple instances of Geronimo choose a PortOffset value such that none of the ports conflict. \n"
108                + "# For example, try PortOffset=10 \n").getBytes();
109    
110        public LocalAttributeManager(String configFile, String configSubstitutionsFileName, String configSubstitutionsPrefix, boolean readOnly, ServerInfo serverInfo) {
111            this.configFile = System.getProperty(CONFIG_FILE_PROPERTY, configFile);
112            resolvedPropertiesFile = System.getProperty(SUBSTITUTIONS_FILE_PROPERTY, configSubstitutionsFileName);
113            configSubstitutionsFile = resolvedPropertiesFile == null? null: serverInfo.resolveServer(resolvedPropertiesFile);
114            localConfigSubstitutions = loadConfigSubstitutions(configSubstitutionsFile);
115            prefix = System.getProperty(SUBSTITUTION_PREFIX_PREFIX, configSubstitutionsPrefix);
116            Map<String, Object> configSubstitutions = loadAllConfigSubstitutions(localConfigSubstitutions, prefix);
117            expressionParser = new JexlExpressionParser(configSubstitutions);
118            this.readOnly = readOnly;
119            this.serverInfo = serverInfo;
120            serverOverride = new ServerOverride();
121            log.debug("setting configSubstitutionsFile to " + configSubstitutionsFile + ".");
122        }
123    
124        public boolean isReadOnly() {
125            return readOnly;
126        }
127    
128    
129        public String getConfigFile() {
130            return configFile;
131        }
132    
133        public String getConfigSubstitutionsFile() {
134            return resolvedPropertiesFile;
135        }
136    
137        public String getConfigSubstitutionsPrefix() {
138            return prefix;
139        }
140    
141        public synchronized Collection applyOverrides(Artifact configName, Collection<GBeanData> untypedGbeanDatas, ClassLoader classLoader) throws InvalidConfigException {
142            // clone the datas since we will be modifying this collection
143            Collection<GBeanData> gbeanDatas = new ArrayList<GBeanData>(untypedGbeanDatas);
144    
145            ConfigurationOverride configuration = serverOverride.getConfiguration(configName);
146            if (configuration == null) {
147                return gbeanDatas;
148            }
149    
150            // index the incoming datas
151            Map<Object, GBeanData> datasByName = new HashMap<Object, GBeanData>();
152            for (GBeanData gbeanData : gbeanDatas) {
153                datasByName.put(gbeanData.getAbstractName(), gbeanData);
154                datasByName.put(gbeanData.getAbstractName().getName().get("name"), gbeanData);
155            }
156    
157            // add the new GBeans
158            for (Object o : configuration.getGBeans().entrySet()) {
159                Map.Entry entry = (Map.Entry) o;
160                Object name = entry.getKey();
161                GBeanOverride gbean = (GBeanOverride) entry.getValue();
162                if (!datasByName.containsKey(name) && gbean.isLoad()) {
163                    if (gbean.getGBeanInfo() == null || !(name instanceof AbstractName)) {
164                        String sep = "";
165                        StringBuffer message = new StringBuffer("New GBeans must be specified with ");
166                        if (gbean.getGBeanInfo() == null) {
167                            message.append("a GBeanInfo ");
168                            sep = "and ";
169                        }
170                        if (!(name instanceof AbstractName)) {
171                            message.append(sep).append("a full AbstractName ");
172                        }
173                        message.append("configuration=").append(configName);
174                        message.append(" gbeanName=").append(name);
175                        throw new InvalidConfigException(message.toString());
176                    }
177                    GBeanInfo gbeanInfo = GBeanInfo.getGBeanInfo(gbean.getGBeanInfo(), classLoader);
178                    AbstractName abstractName = (AbstractName) name;
179                    GBeanData gBeanData = new GBeanData(abstractName, gbeanInfo);
180                    gbeanDatas.add(gBeanData);
181                }
182            }
183    
184            // set the attributes
185            for (Iterator iterator = gbeanDatas.iterator(); iterator.hasNext();) {
186                GBeanData data = (GBeanData) iterator.next();
187                boolean load = setAttributes(data, configuration, configName, classLoader);
188                if (!load) {
189                    iterator.remove();
190                }
191            }
192            return gbeanDatas;
193        }
194    
195        /**
196         * Set the attributes from the attribute store on a single gbean, and return whether or not to load the gbean.
197         *
198         * @param data          GBeanData we are going to override attributes on
199         * @param configuration the module override the gbean relates to
200         * @param configName    name of the module (why can't this be determined from the configuration?)
201         * @param classLoader   ClassLoader to use for property objects/PropertyEditors
202         * @return true if the gbean should be loaded, false otherwise.
203         * @throws org.apache.geronimo.kernel.config.InvalidConfigException
204         *          if we cannot update the gbeanData
205         */
206        private synchronized boolean setAttributes(GBeanData data, ConfigurationOverride configuration, Artifact configName, ClassLoader classLoader) throws InvalidConfigException {
207            AbstractName gbeanName = data.getAbstractName();
208            GBeanOverride gbean = configuration.getGBean(gbeanName);
209            if (gbean == null) {
210                gbean = configuration.getGBean((String) gbeanName.getName().get("name"));
211            }
212    
213            if (gbean == null) {
214                //no attr info, load by default
215                return true;
216            }
217    
218            return gbean.applyOverrides(data, configName, gbeanName, classLoader);
219        }
220    
221        public void setModuleGBeans(Artifact moduleName, List<GbeanType> gbeans, boolean load, String condition) throws InvalidGBeanException {
222            if (readOnly) {
223                return;
224            }
225            ConfigurationOverride configuration = serverOverride.getConfiguration(moduleName, true);
226            if (gbeans != null) {
227                for (GbeanType gbean : gbeans) {
228                    GBeanOverride override = new GBeanOverride(gbean, expressionParser);
229                    configuration.addGBean(override);
230                }
231            }
232            configuration.setLoad(load);
233            configuration.setCondition(condition);
234            log.info("Added gbeans for module: " + moduleName + " load: " + load);
235            attributeChanged();
236        }
237    
238        public void addConfigSubstitutions(Properties properties) {
239            localConfigSubstitutions.putAll(properties);
240            Map<String, Object> configSubstutions = loadAllConfigSubstitutions(localConfigSubstitutions, prefix);
241            storeConfigSubstitutions(configSubstitutionsFile, localConfigSubstitutions);
242            expressionParser.setVariables(configSubstutions);
243        }
244    
245        public synchronized void setValue(Artifact configurationName, AbstractName gbeanName, GAttributeInfo attribute, Object value, ClassLoader classLoader) {
246            if (readOnly) {
247                return;
248            }
249            ConfigurationOverride configuration = serverOverride.getConfiguration(configurationName, true);
250            GBeanOverride gbean = configuration.getGBean(gbeanName);
251            if (gbean == null) {
252                gbean = configuration.getGBean((String) gbeanName.getName().get("name"));
253                if (gbean == null) {
254                    gbean = new GBeanOverride(gbeanName, true, expressionParser);
255                    configuration.addGBean(gbeanName, gbean);
256                }
257            }
258    
259            try {
260                gbean.setAttribute(attribute.getName(), value, attribute.getType(), classLoader);
261                attributeChanged();
262            } catch (InvalidAttributeException e) {
263                // attribute can not be represented as a string
264                log.error(e.getMessage());
265            }
266        }
267    
268        public synchronized void setReferencePatterns(Artifact configurationName, AbstractName gbeanName, GReferenceInfo reference, ReferencePatterns patterns) {
269            if (readOnly) {
270                return;
271            }
272    
273            ConfigurationOverride configuration = serverOverride.getConfiguration(configurationName, true);
274            GBeanOverride gbean = configuration.getGBean(gbeanName);
275            if (gbean == null) {
276                gbean = configuration.getGBean((String) gbeanName.getName().get("name"));
277                if (gbean == null) {
278                    gbean = new GBeanOverride(gbeanName, true, expressionParser);
279                    configuration.addGBean(gbeanName, gbean);
280                }
281            }
282            gbean.setReferencePatterns(reference.getName(), patterns);
283            attributeChanged();
284        }
285    
286        public synchronized void setShouldLoad(Artifact configurationName, AbstractName gbeanName, boolean load) {
287            if (readOnly) {
288                return;
289            }
290            ConfigurationOverride configuration = serverOverride.getConfiguration(configurationName, true);
291    
292            GBeanOverride gbean = configuration.getGBean(gbeanName);
293            if (gbean == null) {
294                // attempt to lookup by short name
295                gbean = configuration.getGBean((String) gbeanName.getName().get("name"));
296            }
297    
298            if (gbean == null) {
299                gbean = new GBeanOverride(gbeanName, load, expressionParser);
300                configuration.addGBean(gbeanName, gbean);
301            } else {
302                gbean.setLoad(load);
303            }
304            attributeChanged();
305        }
306    
307        public void addGBean(Artifact configurationName, GBeanData gbeanData, ClassLoader classLoader) {
308            if (readOnly) {
309                return;
310            }
311            ConfigurationOverride configuration = serverOverride.getConfiguration(configurationName);
312            if (configuration == null) {
313                log.debug("Can not add GBean; Configuration not found " + configurationName);
314                return;
315            }
316            try {
317                GBeanOverride gbean = new GBeanOverride(gbeanData, expressionParser, classLoader);
318                configuration.addGBean(gbean);
319                attributeChanged();
320            } catch (InvalidAttributeException e) {
321                // attribute can not be represented as a string
322                log.error(e.getMessage());
323            }
324        }
325    
326        public synchronized void load() throws IOException {
327            ensureParentDirectory();
328            if (!attributeFile.exists()) {
329                return;
330            }
331            Reader input = new BufferedReader(new FileReader(attributeFile));
332    
333            try {
334                serverOverride = read(input, expressionParser);
335    
336            } catch (SAXException e) {
337                log.error("Unable to read saved manageable attributes", e);
338            } catch (ParserConfigurationException e) {
339                log.error("Unable to read saved manageable attributes", e);
340            } catch (InvalidGBeanException e) {
341                log.error("Unable to read saved manageable attributes", e);
342            } catch (JAXBException e) {
343                log.error("Unable to read saved manageable attributes", e);
344            } catch (XMLStreamException e) {
345                log.error("Unable to read saved manageable attributes", e);
346            } finally {
347                // input is always non-null
348                input.close();
349            }
350        }
351    
352        static ServerOverride read(Reader input, JexlExpressionParser expressionParser) throws ParserConfigurationException, IOException, SAXException, JAXBException, XMLStreamException, InvalidGBeanException {
353            AttributesType attributes = AttributesXmlUtil.loadAttributes(input);
354            return new ServerOverride(attributes, expressionParser);
355        }
356    
357        public synchronized void save() throws IOException {
358            if (readOnly) {
359                return;
360            }
361            ensureParentDirectory();
362            if (!tempFile.exists() && !tempFile.createNewFile()) {
363                throw new IOException("Unable to create manageable attribute working file for save " + tempFile.getAbsolutePath());
364            }
365            if (!tempFile.canWrite()) {
366                throw new IOException("Unable to write to manageable attribute working file for save " + tempFile.getAbsolutePath());
367            }
368    
369            // write the new configuration to the temp file
370            saveXmlToFile(tempFile, serverOverride);
371    
372            // delete the current backup file
373            if (backupFile.exists()) {
374                if (!backupFile.delete()) {
375                    throw new IOException("Unable to delete old backup file in order to back up current manageable attribute working file for save");
376                }
377            }
378    
379            // rename the existing configuration file to the backup file
380            if (attributeFile.exists()) {
381                if (!attributeFile.renameTo(backupFile)) {
382                    throw new IOException("Unable to rename " + attributeFile.getAbsolutePath() + " to " + backupFile.getAbsolutePath() + " in order to back up manageable attribute save file");
383                }
384            }
385    
386            // rename the temp file the the configuration file
387            if (!tempFile.renameTo(attributeFile)) {
388                throw new IOException(
389                        "EXTREMELY CRITICAL!  Unable to move manageable attributes working file to proper file name!  Configuration will revert to defaults unless this is manually corrected!  (could not rename " + tempFile.getAbsolutePath() + " to " + attributeFile.getAbsolutePath() + ")");
390            }
391        }
392    
393        private static void saveXmlToFile(File file, ServerOverride serverOverride) {
394            try {
395                Writer fileOut = new FileWriter(file);
396                try {
397                    Writer writer = new BufferedWriter(fileOut);
398                    write(serverOverride, writer);
399                } catch (JAXBException e) {
400                    log.error("Unable to write config.xml", e);
401                } catch (XMLStreamException e) {
402                    log.error("Unable to write config.xml", e);
403                } finally {
404                    fileOut.close();
405                }
406            } catch (IOException e) {
407                log.error("Unable to write config.xml", e);
408            }
409       }
410    
411        static void write(ServerOverride serverOverride, Writer writer) throws XMLStreamException, JAXBException, IOException {
412            AttributesType attributes = serverOverride.writeXml();
413            AttributesXmlUtil.writeAttributes(attributes, writer);
414            writer.flush();
415        }
416    
417        //PersistentConfigurationList
418        public synchronized boolean isKernelFullyStarted() {
419            return kernelFullyStarted;
420        }
421    
422        public synchronized void setKernelFullyStarted(boolean kernelFullyStarted) {
423            this.kernelFullyStarted = kernelFullyStarted;
424        }
425    
426        public synchronized List<Artifact> restore() throws IOException {
427            List<Artifact> configs = new ArrayList<Artifact>();
428            for (Map.Entry<Artifact, ConfigurationOverride> entry : serverOverride.getConfigurations().entrySet()) {
429                ConfigurationOverride configuration = entry.getValue();
430                if (configuration.isLoad()) {
431                    Artifact configID = entry.getKey();
432                    configs.add(configID);
433                }
434            }
435            return configs;
436        }
437    
438        public void startConfiguration(Artifact configurationName) {
439            if (readOnly) {
440                return;
441            }
442            ConfigurationOverride configuration = serverOverride.getConfiguration(configurationName, false);
443            if (configuration == null) {
444                return;
445            }
446            configuration.setLoad(true);
447            attributeChanged();
448        }
449    
450        public synchronized void addConfiguration(Artifact configurationName) {
451            if (readOnly) {
452                return;
453            }
454            // Check whether we have it already
455            ConfigurationOverride configuration = serverOverride.getConfiguration(configurationName, false);
456            // If not, initialize it
457            if (configuration == null) {
458                configuration = serverOverride.getConfiguration(configurationName, true);
459                configuration.setLoad(false);
460                attributeChanged();
461            }
462        }
463    
464        public synchronized void removeConfiguration(Artifact configName) {
465            if (readOnly) {
466                return;
467            }
468            ConfigurationOverride configuration = serverOverride.getConfiguration(configName);
469            if (configuration == null) {
470                return;
471            }
472            serverOverride.removeConfiguration(configName);
473            attributeChanged();
474        }
475    
476        public Artifact[] getListedConfigurations(Artifact query) {
477            return serverOverride.queryConfigurations(query);
478        }
479    
480        public void stopConfiguration(Artifact configName) {
481            if (readOnly) {
482                return;
483            }
484            ConfigurationOverride configuration = serverOverride.getConfiguration(configName);
485            if (configuration == null) {
486                return;
487            }
488            configuration.setLoad(false);
489            attributeChanged();
490        }
491    
492        public void migrateConfiguration(Artifact oldName, Artifact newName, Configuration configuration) {
493            if (readOnly) {
494                return;
495            }
496            ConfigurationOverride configInfo = serverOverride.getConfiguration(oldName);
497            if (configInfo == null) {
498                throw new IllegalArgumentException("Trying to migrate unknown configuration: " + oldName);
499            }
500            serverOverride.removeConfiguration(oldName);
501            configInfo = new ConfigurationOverride(configInfo, newName);
502            //todo: check whether all the attributes are still valid for the new configuration
503            serverOverride.addConfiguration(configInfo);
504            attributeChanged();
505        }
506    
507        /**
508         * This method checks if there are any custom gbean attributes in the configuration.
509         *
510         * @param configName Name of the configuration
511         * @return true if the configuration contains any custom gbean attributes
512         */
513        public boolean hasGBeanAttributes(Artifact configName) {
514            ConfigurationOverride configInfo = serverOverride.getConfiguration(configName);
515            return configInfo != null && !configInfo.getGBeans().isEmpty();
516        }
517    
518        //GBeanLifeCycle
519        public synchronized void doStart() throws Exception {
520            load();
521            if (!readOnly) {
522                timer = new Timer(true);
523            }
524            log.debug("Started LocalAttributeManager with data on " + serverOverride.getConfigurations().size() + " configurations");
525        }
526    
527        public synchronized void doStop() throws Exception {
528            boolean doSave = false;
529            synchronized (this) {
530                if (timer != null) {
531                    timer.cancel();
532                    if (currentTask != null) {
533                        currentTask.cancel();
534                        doSave = true;
535                    }
536                }
537            }
538            if (doSave) {
539                save();
540            }
541            log.debug("Stopped LocalAttributeManager with data on " + serverOverride.getConfigurations().size() + " configurations");
542            serverOverride = new ServerOverride();
543        }
544    
545        public synchronized void doFail() {
546            synchronized (this) {
547                if (timer != null) {
548                    timer.cancel();
549                    if (currentTask != null) {
550                        currentTask.cancel();
551                    }
552                }
553            }
554            serverOverride = new ServerOverride();
555        }
556    
557        private synchronized void ensureParentDirectory() throws IOException {
558            if (attributeFile == null) {
559                attributeFile = serverInfo.resolveServer(configFile);
560                tempFile = new File(attributeFile.getAbsolutePath() + TEMP_EXTENSION);
561                backupFile = new File(attributeFile.getAbsolutePath() + BACKUP_EXTENSION);
562            }
563            File parent = attributeFile.getParentFile();
564            if (!parent.isDirectory()) {
565                if (!parent.mkdirs()) {
566                    throw new IOException("Unable to create directory for list:" + parent);
567                }
568            }
569            if (!parent.canRead()) {
570                throw new IOException("Unable to read manageable attribute files in directory " + parent.getAbsolutePath());
571            }
572            if (!readOnly && !parent.canWrite()) {
573                throw new IOException("Unable to write manageable attribute files to directory " + parent.getAbsolutePath());
574            }
575        }
576    
577        private synchronized void attributeChanged() {
578            if (currentTask != null) {
579                currentTask.cancel();
580            }
581            if (timer != null) {
582                currentTask = new TimerTask() {
583    
584                    public void run() {
585                        try {
586                            LocalAttributeManager.this.save();
587                        } catch (IOException e) {
588                            log.error("IOException occurred while saving attributes", e);
589                        } catch (Throwable t) {
590                            log.error("Error occurred during execution of attributeChanged TimerTask", t);
591                        }
592                    }
593                };
594                timer.schedule(currentTask, SAVE_BUFFER_MS);
595            }
596        }
597    
598        private static Map<String, Object> loadAllConfigSubstitutions(Properties configSubstitutions, String prefix) {
599            Map<String, Object> vars = new HashMap<String, Object>();
600            //most significant are the command line system properties
601            addGeronimoSubstitutions(vars, System.getProperties(), prefix);
602            //environment variables are next
603            addGeronimoSubstitutions(vars, System.getenv(), prefix);
604            //properties file is least significant
605            if (configSubstitutions != null) {
606                addGeronimoSubstitutions(vars, configSubstitutions, "");
607            }
608            ParserUtils.addDefaultVariables(vars);
609            return vars;
610        }
611    
612        private static Properties loadConfigSubstitutions(File configSubstitutionsFile) {
613            Properties properties = new Properties();
614            if (configSubstitutionsFile != null) {
615                if (!configSubstitutionsFile.exists()) {
616                    //write out empty file with instructions as a hint to users.
617                    storeConfigSubstitutions(configSubstitutionsFile, properties);
618                } else {
619                    try {
620                        FileInputStream in = new FileInputStream(configSubstitutionsFile);
621                        try {
622                            properties.load(in);
623                        } finally {
624                            in.close();
625                        }
626                    } catch (Exception e) {
627                        log.error("Caught exception " + e
628                                + " trying to read properties file " + configSubstitutionsFile.getAbsolutePath());
629                    }
630                }
631            }
632            return properties;
633        }
634    
635        private static void storeConfigSubstitutions(File configSubstitutionsFile, Properties properties) {
636            if (configSubstitutionsFile != null) {
637                try {
638                    FileOutputStream out = new FileOutputStream(configSubstitutionsFile);
639                    try {
640                        out.write(INSTRUCTION);                    
641                        properties.store(out, null);
642                    } finally {
643                        out.close();
644                    }
645                } catch (Exception e) {
646                    log.error("Caught exception " + e
647                            + " trying to write properties file " + configSubstitutionsFile.getAbsolutePath());
648                }
649            }
650        }
651    
652        private static void addGeronimoSubstitutions(Map<String, Object> vars, Map props, String prefix) {
653            if (prefix != null) {
654                int start = prefix.length();
655                for (Object o : props.entrySet()) {
656                    Map.Entry entry = (Map.Entry) o;
657                    if (((String) entry.getKey()).startsWith(prefix)) {
658                        String key = ((String) entry.getKey()).substring(start);
659                        if (!vars.containsKey(key)) {
660                            vars.put(key, entry.getValue());
661                        }
662                    }
663                }
664            }
665        }
666    
667        public static final GBeanInfo GBEAN_INFO;
668    
669        static {
670            GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic(LocalAttributeManager.class, "AttributeStore");
671            infoFactory.addReference("ServerInfo", ServerInfo.class, "GBean");
672            infoFactory.addAttribute("configFile", String.class, true);
673            infoFactory.addAttribute("readOnly", boolean.class, true);
674            infoFactory.addAttribute("substitutionsFile", String.class, true);
675            infoFactory.addAttribute("substitutionPrefix", String.class, true);
676            infoFactory.addInterface(ManageableAttributeStore.class);
677            infoFactory.addInterface(PersistentConfigurationList.class);
678    
679            infoFactory.setConstructor(new String[]{"configFile", "substitutionsFile", "substitutionPrefix", "readOnly", "ServerInfo"});
680    
681            GBEAN_INFO = infoFactory.getBeanInfo();
682        }
683    
684        public static GBeanInfo getGBeanInfo() {
685            return GBEAN_INFO;
686        }
687    }