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: 889880 $ $Date: 2009-12-11 21:45:24 -0500 (Fri, 11 Dec 2009) $
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, value, 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        void write(Writer writer) throws XMLStreamException, JAXBException,
394                IOException {
395            AttributesType attributes = serverOverride.writeXml();
396            AttributesXmlUtil.writeAttributes(attributes, writer);
397            writer.flush();
398        }
399    
400        private static void saveXmlToFile(File file, ServerOverride serverOverride) {
401            try {
402                Writer fileOut = new FileWriter(file);
403                try {
404                    Writer writer = new BufferedWriter(fileOut);
405                    write(serverOverride, writer);
406                } catch (JAXBException e) {
407                    log.error("Unable to write config.xml", e);
408                } catch (XMLStreamException e) {
409                    log.error("Unable to write config.xml", e);
410                } finally {
411                    fileOut.close();
412                }
413            } catch (IOException e) {
414                log.error("Unable to write config.xml", e);
415            }
416       }
417    
418        static void write(ServerOverride serverOverride, Writer writer) throws XMLStreamException, JAXBException, IOException {
419            AttributesType attributes = serverOverride.writeXml();
420            AttributesXmlUtil.writeAttributes(attributes, writer);
421            writer.flush();
422        }
423    
424        //PersistentConfigurationList
425        public synchronized boolean isKernelFullyStarted() {
426            return kernelFullyStarted;
427        }
428    
429        public synchronized void setKernelFullyStarted(boolean kernelFullyStarted) {
430            this.kernelFullyStarted = kernelFullyStarted;
431        }
432    
433        public synchronized List<Artifact> restore() throws IOException {
434            List<Artifact> configs = new ArrayList<Artifact>();
435            for (Map.Entry<Artifact, ConfigurationOverride> entry : serverOverride.getConfigurations().entrySet()) {
436                ConfigurationOverride configuration = entry.getValue();
437                if (configuration.isLoad()) {
438                    Artifact configID = entry.getKey();
439                    configs.add(configID);
440                }
441            }
442            return configs;
443        }
444    
445        public void startConfiguration(Artifact configurationName) {
446            if (readOnly) {
447                return;
448            }
449            ConfigurationOverride configuration = serverOverride.getConfiguration(configurationName, false);
450            if (configuration == null) {
451                return;
452            }
453            configuration.setLoad(true);
454            attributeChanged();
455        }
456    
457        public synchronized void addConfiguration(Artifact configurationName) {
458            if (readOnly) {
459                return;
460            }
461            // Check whether we have it already
462            ConfigurationOverride configuration = serverOverride.getConfiguration(configurationName, false);
463            // If not, initialize it
464            if (configuration == null) {
465                configuration = serverOverride.getConfiguration(configurationName, true);
466                configuration.setLoad(false);
467                attributeChanged();
468            }
469        }
470    
471        public synchronized void removeConfiguration(Artifact configName) {
472            if (readOnly) {
473                return;
474            }
475            ConfigurationOverride configuration = serverOverride.getConfiguration(configName);
476            if (configuration == null) {
477                return;
478            }
479            serverOverride.removeConfiguration(configName);
480            attributeChanged();
481        }
482    
483        public Artifact[] getListedConfigurations(Artifact query) {
484            return serverOverride.queryConfigurations(query);
485        }
486    
487        public void stopConfiguration(Artifact configName) {
488            if (readOnly) {
489                return;
490            }
491            ConfigurationOverride configuration = serverOverride.getConfiguration(configName);
492            if (configuration == null) {
493                return;
494            }
495            configuration.setLoad(false);
496            attributeChanged();
497        }
498    
499        public void migrateConfiguration(Artifact oldName, Artifact newName, Configuration configuration) {
500            if (readOnly) {
501                return;
502            }
503            ConfigurationOverride configInfo = serverOverride.getConfiguration(oldName);
504            if (configInfo == null) {
505                throw new IllegalArgumentException("Trying to migrate unknown configuration: " + oldName);
506            }
507            serverOverride.removeConfiguration(oldName);
508            configInfo = new ConfigurationOverride(configInfo, newName);
509            //todo: check whether all the attributes are still valid for the new configuration
510            serverOverride.addConfiguration(configInfo);
511            attributeChanged();
512        }
513    
514        /**
515         * This method checks if there are any custom gbean attributes in the configuration.
516         *
517         * @param configName Name of the configuration
518         * @return true if the configuration contains any custom gbean attributes
519         */
520        public boolean hasGBeanAttributes(Artifact configName) {
521            ConfigurationOverride configInfo = serverOverride.getConfiguration(configName);
522            return configInfo != null && !configInfo.getGBeans().isEmpty();
523        }
524    
525        //GBeanLifeCycle
526        public synchronized void doStart() throws Exception {
527            load();
528            if (!readOnly) {
529                timer = new Timer(true);
530            }
531            log.debug("Started LocalAttributeManager with data on " + serverOverride.getConfigurations().size() + " configurations");
532        }
533    
534        public synchronized void doStop() throws Exception {
535            boolean doSave = false;
536            synchronized (this) {
537                if (timer != null) {
538                    timer.cancel();
539                    if (currentTask != null) {
540                        currentTask.cancel();
541                        doSave = true;
542                    }
543                }
544            }
545            if (doSave) {
546                save();
547            }
548            log.debug("Stopped LocalAttributeManager with data on " + serverOverride.getConfigurations().size() + " configurations");
549            serverOverride = new ServerOverride();
550        }
551    
552        public synchronized void doFail() {
553            synchronized (this) {
554                if (timer != null) {
555                    timer.cancel();
556                    if (currentTask != null) {
557                        currentTask.cancel();
558                    }
559                }
560            }
561            serverOverride = new ServerOverride();
562        }
563    
564        private synchronized void ensureParentDirectory() throws IOException {
565            if (attributeFile == null) {
566                attributeFile = serverInfo.resolveServer(configFile);
567                tempFile = new File(attributeFile.getAbsolutePath() + TEMP_EXTENSION);
568                backupFile = new File(attributeFile.getAbsolutePath() + BACKUP_EXTENSION);
569            }
570            File parent = attributeFile.getParentFile();
571            if (!parent.isDirectory()) {
572                if (!parent.mkdirs()) {
573                    throw new IOException("Unable to create directory for list:" + parent);
574                }
575            }
576            if (!parent.canRead()) {
577                throw new IOException("Unable to read manageable attribute files in directory " + parent.getAbsolutePath());
578            }
579            if (!readOnly && !parent.canWrite()) {
580                throw new IOException("Unable to write manageable attribute files to directory " + parent.getAbsolutePath());
581            }
582        }
583    
584        private synchronized void attributeChanged() {
585            if (currentTask != null) {
586                currentTask.cancel();
587            }
588            if (timer != null) {
589                currentTask = new TimerTask() {
590    
591                    public void run() {
592                        try {
593                            LocalAttributeManager.this.save();
594                        } catch (IOException e) {
595                            log.error("IOException occurred while saving attributes", e);
596                        } catch (Throwable t) {
597                            log.error("Error occurred during execution of attributeChanged TimerTask", t);
598                        }
599                    }
600                };
601                timer.schedule(currentTask, SAVE_BUFFER_MS);
602            }
603        }
604    
605        private static Map<String, Object> loadAllConfigSubstitutions(Properties configSubstitutions, String prefix) {
606            Map<String, Object> vars = new HashMap<String, Object>();
607            //most significant are the command line system properties
608            addGeronimoSubstitutions(vars, System.getProperties(), prefix);
609            //environment variables are next
610            addGeronimoSubstitutions(vars, System.getenv(), prefix);
611            //properties file is least significant
612            if (configSubstitutions != null) {
613                addGeronimoSubstitutions(vars, configSubstitutions, "");
614            }
615            ParserUtils.addDefaultVariables(vars);
616            return vars;
617        }
618    
619        private static Properties loadConfigSubstitutions(File configSubstitutionsFile) {
620            Properties properties = new Properties();
621            if (configSubstitutionsFile != null) {
622                if (!configSubstitutionsFile.exists()) {
623                    //write out empty file with instructions as a hint to users.
624                    storeConfigSubstitutions(configSubstitutionsFile, properties);
625                } else {
626                    try {
627                        FileInputStream in = new FileInputStream(configSubstitutionsFile);
628                        try {
629                            properties.load(in);
630                        } finally {
631                            in.close();
632                        }
633                    } catch (Exception e) {
634                        log.error("Caught exception " + e
635                                + " trying to read properties file " + configSubstitutionsFile.getAbsolutePath());
636                    }
637                }
638            }
639            return properties;
640        }
641    
642        private static void storeConfigSubstitutions(File configSubstitutionsFile, Properties properties) {
643            if (configSubstitutionsFile != null) {
644                try {
645                    FileOutputStream out = new FileOutputStream(configSubstitutionsFile);
646                    try {
647                        out.write(INSTRUCTION);                    
648                        properties.store(out, null);
649                    } finally {
650                        out.close();
651                    }
652                } catch (Exception e) {
653                    log.error("Caught exception " + e
654                            + " trying to write properties file " + configSubstitutionsFile.getAbsolutePath());
655                }
656            }
657        }
658    
659        private static void addGeronimoSubstitutions(Map<String, Object> vars, Map props, String prefix) {
660            if (prefix != null) {
661                int start = prefix.length();
662                for (Object o : props.entrySet()) {
663                    Map.Entry entry = (Map.Entry) o;
664                    if (((String) entry.getKey()).startsWith(prefix)) {
665                        String key = ((String) entry.getKey()).substring(start);
666                        if (!vars.containsKey(key)) {
667                            vars.put(key, entry.getValue());
668                        }
669                    }
670                }
671            }
672        }
673    
674        public static final GBeanInfo GBEAN_INFO;
675    
676        static {
677            GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic(LocalAttributeManager.class, "AttributeStore");
678            infoFactory.addReference("ServerInfo", ServerInfo.class, "GBean");
679            infoFactory.addAttribute("configFile", String.class, true);
680            infoFactory.addAttribute("readOnly", boolean.class, true);
681            infoFactory.addAttribute("substitutionsFile", String.class, true);
682            infoFactory.addAttribute("substitutionPrefix", String.class, true);
683            infoFactory.addInterface(ManageableAttributeStore.class);
684            infoFactory.addInterface(PersistentConfigurationList.class);
685    
686            infoFactory.setConstructor(new String[]{"configFile", "substitutionsFile", "substitutionPrefix", "readOnly", "ServerInfo"});
687    
688            GBEAN_INFO = infoFactory.getBeanInfo();
689        }
690    
691        public static GBeanInfo getGBeanInfo() {
692            return GBEAN_INFO;
693        }
694    }