001    /**
002     *
003     *  Licensed to the Apache Software Foundation (ASF) under one or more
004     *  contributor license agreements.  See the NOTICE file distributed with
005     *  this work for additional information regarding copyright ownership.
006     *  The ASF licenses this file to You under the Apache License, Version 2.0
007     *  (the "License"); you may not use this file except in compliance with
008     *  the License.  You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     *  Unless required by applicable law or agreed to in writing, software
013     *  distributed under the License is distributed on an "AS IS" BASIS,
014     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     *  See the License for the specific language governing permissions and
016     *  limitations under the License.
017     */
018    package org.apache.geronimo.system.configuration;
019    
020    import org.apache.commons.logging.Log;
021    import org.apache.commons.logging.LogFactory;
022    import org.apache.geronimo.common.propertyeditor.PropertyEditors;
023    import org.apache.geronimo.gbean.AbstractName;
024    import org.apache.geronimo.gbean.GAttributeInfo;
025    import org.apache.geronimo.gbean.GBeanData;
026    import org.apache.geronimo.gbean.GBeanInfo;
027    import org.apache.geronimo.gbean.GBeanInfoBuilder;
028    import org.apache.geronimo.gbean.GBeanLifecycle;
029    import org.apache.geronimo.gbean.GReferenceInfo;
030    import org.apache.geronimo.gbean.ReferencePatterns;
031    import org.apache.geronimo.kernel.config.InvalidConfigException;
032    import org.apache.geronimo.kernel.config.ManageableAttributeStore;
033    import org.apache.geronimo.kernel.config.PersistentConfigurationList;
034    import org.apache.geronimo.kernel.config.Configuration;
035    import org.apache.geronimo.kernel.repository.Artifact;
036    import org.apache.geronimo.kernel.InvalidGBeanException;
037    import org.apache.geronimo.kernel.util.XmlUtil;
038    import org.apache.geronimo.system.serverinfo.ServerInfo;
039    import org.w3c.dom.Document;
040    import org.w3c.dom.Element;
041    import org.xml.sax.ErrorHandler;
042    import org.xml.sax.InputSource;
043    import org.xml.sax.SAXException;
044    import org.xml.sax.SAXParseException;
045    
046    import javax.xml.parsers.DocumentBuilder;
047    import javax.xml.parsers.DocumentBuilderFactory;
048    import javax.xml.parsers.ParserConfigurationException;
049    import javax.xml.transform.TransformerFactory;
050    import javax.xml.transform.Transformer;
051    import javax.xml.transform.OutputKeys;
052    import javax.xml.transform.TransformerException;
053    import javax.xml.transform.stream.StreamResult;
054    import javax.xml.transform.dom.DOMSource;
055    
056    import java.beans.PropertyEditor;
057    
058    import java.io.File;
059    import java.io.FileInputStream;
060    import java.io.InputStream;
061    import java.io.BufferedInputStream;
062    import java.io.FileNotFoundException;
063    import java.io.FileOutputStream;
064    import java.io.IOException;
065    import java.io.OutputStream;
066    import java.io.BufferedOutputStream;
067    
068    import java.util.ArrayList;
069    import java.util.Collection;
070    import java.util.HashMap;
071    import java.util.Iterator;
072    import java.util.List;
073    import java.util.Map;
074    import java.util.Timer;
075    import java.util.TimerTask;
076    
077    /**
078     * Stores managed attributes in an XML file on the local filesystem.
079     *
080     * @version $Rev: 470597 $ $Date: 2006-11-02 15:30:55 -0800 (Thu, 02 Nov 2006) $
081     */
082    public class LocalAttributeManager implements PluginAttributeStore, PersistentConfigurationList, GBeanLifecycle {
083        private static final Log log = LogFactory.getLog(LocalAttributeManager.class);
084    
085        private static final String CONFIG_FILE_PROPERTY = "org.apache.geronimo.config.file";
086    
087        private static final String BACKUP_EXTENSION = ".bak";
088        private static final String TEMP_EXTENSION = ".working";
089        private static final int SAVE_BUFFER_MS = 5000;
090    
091        private final ServerInfo serverInfo;
092        private final String configFile;
093        private final boolean readOnly;
094    
095        private File attributeFile;
096        private File backupFile;
097        private File tempFile;
098        private ServerOverride serverOverride;
099    
100        private Timer timer;
101        private TimerTask currentTask;
102    
103        private boolean kernelFullyStarted;
104    
105        public LocalAttributeManager(String configFile, boolean readOnly, ServerInfo serverInfo) {
106            this.configFile = System.getProperty(CONFIG_FILE_PROPERTY, configFile);
107            this.readOnly = readOnly;
108            this.serverInfo = serverInfo;
109            serverOverride = new ServerOverride();
110        }
111    
112        public boolean isReadOnly() {
113            return readOnly;
114        }
115    
116        public synchronized Collection applyOverrides(Artifact configName, Collection gbeanDatas, ClassLoader classLoader) throws InvalidConfigException {
117            // clone the datas since we will be modifying this collection
118            gbeanDatas = new ArrayList(gbeanDatas);
119    
120            ConfigurationOverride configuration = serverOverride.getConfiguration(configName);
121            if (configuration == null) {
122                return gbeanDatas;
123            }
124    
125            // index the incoming datas
126            Map datasByName = new HashMap();
127            for (Iterator iterator = gbeanDatas.iterator(); iterator.hasNext();) {
128                GBeanData gbeanData = (GBeanData) iterator.next();
129                datasByName.put(gbeanData.getAbstractName(), gbeanData);
130                datasByName.put(gbeanData.getAbstractName().getName().get("name"), gbeanData);
131            }
132    
133            // add the new GBeans
134            for (Iterator iterator = configuration.getGBeans().entrySet().iterator(); iterator.hasNext();) {
135                Map.Entry entry = (Map.Entry) iterator.next();
136                Object name = entry.getKey();
137                GBeanOverride gbean = (GBeanOverride) entry.getValue();
138                if (!datasByName.containsKey(name) && gbean.isLoad()) {
139                    if (gbean.getGBeanInfo() == null || !(name instanceof AbstractName)) {
140                        String sep = "";
141                        StringBuffer message = new StringBuffer("New GBeans must be specified with ");
142                        if (gbean.getGBeanInfo() == null) {
143                            message.append("a GBeanInfo ");
144                            sep = "and ";
145                        }
146                        if (!(name instanceof AbstractName)) {
147                            message.append(sep).append("a full AbstractName ");
148                        }
149                        message.append("configuration=").append(configName);
150                        message.append(" gbeanName=").append(name);
151                        throw new InvalidConfigException(message.toString());
152                    }
153                    GBeanInfo gbeanInfo = GBeanInfo.getGBeanInfo(gbean.getGBeanInfo(), classLoader);
154                    AbstractName abstractName = (AbstractName)name;
155                    GBeanData gBeanData = new GBeanData(abstractName, gbeanInfo);
156                    gbeanDatas.add(gBeanData);
157                }
158            }
159    
160            // set the attributes
161            for (Iterator iterator = gbeanDatas.iterator(); iterator.hasNext();) {
162                GBeanData data = (GBeanData) iterator.next();
163                boolean load = setAttributes(data, configuration, configName, classLoader);
164                if (!load) {
165                    iterator.remove();
166                }
167            }
168            return gbeanDatas;
169        }
170    
171        /**
172         * Set the attributes from the attribute store on a single gbean, and return whether or not to load the gbean.
173         *
174         * @param data
175         * @param configuration
176         * @param configName
177         * @param classLoader
178         * @return true if the gbean should be loaded, false otherwise.
179         * @throws org.apache.geronimo.kernel.config.InvalidConfigException
180         *
181         */
182        private synchronized boolean setAttributes(GBeanData data, ConfigurationOverride configuration, Artifact configName, ClassLoader classLoader) throws InvalidConfigException {
183            AbstractName gbeanName = data.getAbstractName();
184            GBeanOverride gbean = configuration.getGBean(gbeanName);
185            if (gbean == null) {
186                gbean = configuration.getGBean((String) gbeanName.getName().get("name"));
187            }
188    
189            if (gbean == null) {
190                //no attr info, load by default
191                return true;
192            }
193    
194            if (!gbean.isLoad()) {
195                return false;
196            }
197    
198            GBeanInfo gbeanInfo = data.getGBeanInfo();
199    
200            // set attributes
201            for (Iterator iterator = gbean.getAttributes().entrySet().iterator(); iterator.hasNext();) {
202                Map.Entry entry = (Map.Entry) iterator.next();
203                String attributeName = (String) entry.getKey();
204                GAttributeInfo attributeInfo = gbeanInfo.getAttribute(attributeName);
205                if (attributeInfo == null) {
206                    throw new InvalidConfigException("No attribute: " + attributeName + " for gbean: " + data.getAbstractName());
207                }
208                String valueString = (String) entry.getValue();
209                Object value = getValue(attributeInfo, valueString, configName, gbeanName, classLoader);
210                data.setAttribute(attributeName, value);
211            }
212    
213            //Clear attributes
214            for (Iterator iterator = gbean.getClearAttributes().iterator(); iterator.hasNext();){
215               String attribute = (String) iterator.next();
216               if (gbean.getClearAttribute(attribute)){
217                   data.clearAttribute(attribute);
218               }
219            }
220    
221            //Null attributes
222            for (Iterator iterator = gbean.getNullAttributes().iterator(); iterator.hasNext();){
223               String attribute = (String) iterator.next();
224               if (gbean.getNullAttribute(attribute)){
225                   data.setAttribute(attribute, null);
226               }
227            }
228    
229            // set references
230            for (Iterator iterator = gbean.getReferences().entrySet().iterator(); iterator.hasNext();) {
231                Map.Entry entry = (Map.Entry) iterator.next();
232    
233                String referenceName = (String) entry.getKey();
234                GReferenceInfo referenceInfo = gbeanInfo.getReference(referenceName);
235                if (referenceInfo == null) {
236                    throw new InvalidConfigException("No reference: " + referenceName + " for gbean: " + data.getAbstractName());
237                }
238    
239                ReferencePatterns referencePatterns = (ReferencePatterns) entry.getValue();
240    
241                data.setReferencePatterns(referenceName, referencePatterns);
242            }
243    
244            //Clear references
245            for (Iterator iterator = gbean.getClearReferences().iterator(); iterator.hasNext();){
246               String reference = (String) iterator.next();
247               if (gbean.getClearReference(reference)){
248                   data.clearReference(reference);
249               }
250            }
251    
252            return true;
253        }
254    
255    
256        private synchronized Object getValue(GAttributeInfo attribute, String value, Artifact configurationName, AbstractName gbeanName, ClassLoader classLoader) {
257            if (value == null) {
258                return null;
259            }
260    
261            try {
262                PropertyEditor editor = PropertyEditors.findEditor(attribute.getType(), classLoader);
263                if (editor == null) {
264                    log.debug("Unable to parse attribute of type " + attribute.getType() + "; no editor found");
265                    return null;
266                }
267                editor.setAsText(value);
268                log.debug("Setting value for " + configurationName + "/" + gbeanName + "/" + attribute.getName() + " to value " + value);
269                return editor.getValue();
270            } catch (ClassNotFoundException e) {
271                log.error("Unable to load attribute type " + attribute.getType());
272                return null;
273            }
274        }
275    
276        public void setModuleGBeans(Artifact moduleName, GBeanOverride[] gbeans) {
277            if (readOnly) {
278                return;
279            }
280            ConfigurationOverride configuration = serverOverride.getConfiguration(moduleName, true);
281            for (int i = 0; i < gbeans.length; i++) {
282                GBeanOverride gbean = gbeans[i];
283                configuration.addGBean(gbean);
284            }
285            attributeChanged();
286        }
287    
288        public synchronized void setValue(Artifact configurationName, AbstractName gbeanName, GAttributeInfo attribute, Object value) {
289            if (readOnly) {
290                return;
291            }
292            ConfigurationOverride configuration = serverOverride.getConfiguration(configurationName, true);
293            GBeanOverride gbean = configuration.getGBean(gbeanName);
294            if (gbean == null) {
295                gbean = configuration.getGBean((String) gbeanName.getName().get("name"));
296                if (gbean == null) {
297                    gbean = new GBeanOverride(gbeanName, true);
298                    configuration.addGBean(gbeanName, gbean);
299                }
300            }
301    
302            try {
303                gbean.setAttribute(attribute.getName(), value, attribute.getType());
304                attributeChanged();
305            } catch (InvalidAttributeException e) {
306                // attribute can not be represented as a string
307                log.error(e.getMessage());
308            }
309        }
310    
311        public synchronized void setReferencePatterns(Artifact configurationName, AbstractName gbeanName, GReferenceInfo reference, ReferencePatterns patterns) {
312            if (readOnly) {
313                return;
314            }
315    
316            ConfigurationOverride configuration = serverOverride.getConfiguration(configurationName, true);
317            GBeanOverride gbean = configuration.getGBean(gbeanName);
318            if (gbean == null) {
319                gbean = configuration.getGBean((String)gbeanName.getName().get("name"));
320                if (gbean == null) {
321                    gbean = new GBeanOverride(gbeanName, true);
322                    configuration.addGBean(gbeanName, gbean);
323                }
324            }
325            gbean.setReferencePatterns(reference.getName(), patterns);
326            attributeChanged();
327        }
328    
329        public synchronized void setShouldLoad(Artifact configurationName, AbstractName gbeanName, boolean load) {
330            if (readOnly) {
331                return;
332            }
333            ConfigurationOverride configuration = serverOverride.getConfiguration(configurationName, true);
334    
335            GBeanOverride gbean = configuration.getGBean(gbeanName);
336            if (gbean == null) {
337                // attempt to lookup by short name
338                gbean = configuration.getGBean((String)gbeanName.getName().get("name"));
339            }
340    
341            if (gbean == null) {
342                gbean = new GBeanOverride(gbeanName, load);
343                configuration.addGBean(gbeanName, gbean);
344            } else {
345                gbean.setLoad(load);
346            }
347            attributeChanged();
348        }
349    
350        public void addGBean(Artifact configurationName, GBeanData gbeanData) {
351            if (readOnly) {
352                return;
353            }
354            ConfigurationOverride configuration = serverOverride.getConfiguration(configurationName);
355            if (configuration == null) {
356                log.debug("Can not add GBean; Configuration not found " + configurationName);
357                return;
358            }
359            try {
360                GBeanOverride gbean = new GBeanOverride(gbeanData);
361                configuration.addGBean(gbean);
362                attributeChanged();
363            } catch (InvalidAttributeException e) {
364                // attribute can not be represented as a string
365                log.error(e.getMessage());
366            }
367        }
368    
369        public synchronized void load() throws IOException {
370            ensureParentDirectory();
371            if (!attributeFile.exists()) {
372                return;
373            }
374            
375            InputStream input = new BufferedInputStream(new FileInputStream(attributeFile));
376            InputSource source = new InputSource(input);
377            source.setSystemId(attributeFile.toString());
378            DocumentBuilderFactory dFactory = XmlUtil.newDocumentBuilderFactory();
379            
380            try {
381                dFactory.setValidating(true);
382                dFactory.setNamespaceAware(true);
383                dFactory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaLanguage",
384                                     "http://www.w3.org/2001/XMLSchema");
385                
386                dFactory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaSource",
387                                     LocalAttributeManager.class.getResourceAsStream("/META-INF/schema/attributes-1.1.xsd"));
388    
389                DocumentBuilder builder = dFactory.newDocumentBuilder();
390                builder.setErrorHandler(new ErrorHandler() {
391                    public void error(SAXParseException e) {
392                        log.error("Unable to read saved manageable attributes. " +
393                            "SAX parse error: " + e.getMessage() +
394                            " at line " + e.getLineNumber() +
395                            ", column " + e.getColumnNumber() +
396                            " in entity " + e.getSystemId());
397    
398                        if (log.isTraceEnabled()) {
399                            log.trace("Exception deatils", e);
400                        }
401    
402                        // TODO throw an exception here?
403                    }
404    
405                    public void fatalError(SAXParseException e) {
406                        log.error("Unable to read saved manageable attributes. " +
407                                "Fatal SAX parse error: " + e.getMessage() +
408                                " at line " + e.getLineNumber() +
409                                ", column " + e.getColumnNumber() +
410                                " in entity " + e.getSystemId());
411                        
412                        if (log.isTraceEnabled()) {
413                            log.trace("Exception deatils", e);
414                        }
415                        
416                        // TODO throw an exception here?
417                    }
418    
419                    public void warning(SAXParseException e) {
420                        log.error("SAX parse warning whilst reading saved manageable attributes: " +
421                                e.getMessage() +
422                                " at line " + e.getLineNumber() +
423                                ", column " + e.getColumnNumber() +
424                                " in entity " + e.getSystemId());
425                        
426                        if (log.isTraceEnabled()) {
427                            log.trace("Exception deatils", e);
428                        }
429                    }
430                });
431                
432                Document doc = builder.parse(source);
433                Element root = doc.getDocumentElement();
434                serverOverride = new ServerOverride(root);
435            } catch (SAXException e) {
436                log.error("Unable to read saved manageable attributes", e);
437            } catch (ParserConfigurationException e) {
438                log.error("Unable to read saved manageable attributes", e);
439            } catch (InvalidGBeanException e) {
440                log.error("Unable to read saved manageable attributes", e);
441            } finally {
442                // input is always non-null
443                input.close();
444            }
445        }
446    
447        public synchronized void save() throws IOException {
448            if (readOnly) {
449                return;
450            }
451            ensureParentDirectory();
452            if (!tempFile.exists() && !tempFile.createNewFile()) {
453                throw new IOException("Unable to create manageable attribute working file for save " + tempFile.getAbsolutePath());
454            }
455            if (!tempFile.canWrite()) {
456                throw new IOException("Unable to write to manageable attribute working file for save " + tempFile.getAbsolutePath());
457            }
458    
459            // write the new configuration to the temp file
460            saveXmlToFile(tempFile, serverOverride);
461    
462            // delete the current backup file
463            if (backupFile.exists()) {
464                if (!backupFile.delete()) {
465                    throw new IOException("Unable to delete old backup file in order to back up current manageable attribute working file for save");
466                }
467            }
468    
469            // rename the existing configuration file to the backup file
470            if (attributeFile.exists()) {
471                if (!attributeFile.renameTo(backupFile)) {
472                    throw new IOException("Unable to rename " + attributeFile.getAbsolutePath() + " to " + backupFile.getAbsolutePath() + " in order to back up manageable attribute save file");
473                }
474            }
475    
476            // rename the temp file the the configuration file
477            if (!tempFile.renameTo(attributeFile)) {
478                throw new IOException("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() + ")");
479            }
480        }
481    
482        private static void saveXmlToFile(File file, ServerOverride serverOverride) {
483            DocumentBuilderFactory dFactory = XmlUtil.newDocumentBuilderFactory();
484            dFactory.setValidating(true);
485            dFactory.setNamespaceAware(true);
486            dFactory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaLanguage",
487                                 "http://www.w3.org/2001/XMLSchema");
488            dFactory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaSource",
489                                 LocalAttributeManager.class.getResourceAsStream("/META-INF/schema/attributes-1.1.xsd"));
490    
491            OutputStream output = null;
492            try {
493                Document doc = dFactory.newDocumentBuilder().newDocument();
494                serverOverride.writeXml(doc);
495                TransformerFactory xfactory = XmlUtil.newTransformerFactory();
496                Transformer xform = xfactory.newTransformer();
497                xform.setOutputProperty(OutputKeys.INDENT, "yes");
498                xform.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
499                output = new BufferedOutputStream(new FileOutputStream(file));
500    
501                // use a FileOutputStream instead of a File on the StreamResult 
502                // constructor as problems were encountered with the file not being closed.
503                StreamResult sr = new StreamResult(output);
504                xform.transform(new DOMSource(doc), sr);
505    
506                output.flush();
507            } catch (FileNotFoundException e) {
508                // file is directory or cannot be created/opened
509                log.error("Unable to write config.xml", e);
510            } catch (ParserConfigurationException e) {
511                log.error("Unable to write config.xml", e);
512            } catch (TransformerException e) {
513                log.error("Unable to write config.xml", e);
514            } catch (IOException e) {
515                log.error("Unable to write config.xml", e);
516            } finally {
517                if (output != null) {
518                    try {
519                        output.close();
520                    } catch (IOException ignored) {
521                        // ignored
522                    }
523                }
524            }
525        }
526    
527        //PersistentConfigurationList
528        public synchronized boolean isKernelFullyStarted() {
529            return kernelFullyStarted;
530        }
531    
532        public synchronized void setKernelFullyStarted(boolean kernelFullyStarted) {
533            this.kernelFullyStarted = kernelFullyStarted;
534        }
535    
536        public synchronized List restore() throws IOException {
537            List configs = new ArrayList();
538            for (Iterator iterator = serverOverride.getConfigurations().entrySet().iterator(); iterator.hasNext();) {
539                Map.Entry entry = (Map.Entry) iterator.next();
540                ConfigurationOverride configuration = (ConfigurationOverride) entry.getValue();
541                if (configuration.isLoad()) {
542                    Artifact configID = (Artifact) entry.getKey();
543                    configs.add(configID);
544                }
545            }
546            return configs;
547        }
548    
549        public void startConfiguration(Artifact configurationName) {
550            ConfigurationOverride configuration = serverOverride.getConfiguration(configurationName, false);
551            if(configuration == null) {
552                return;
553            }
554            configuration.setLoad(true);
555            attributeChanged();
556        }
557    
558        public synchronized void addConfiguration(Artifact configurationName) {
559            // Check whether we have it already
560            ConfigurationOverride configuration = serverOverride.getConfiguration(configurationName, false);
561            // If not, initialize it
562            if(configuration == null) {
563                configuration = serverOverride.getConfiguration(configurationName, true);
564                configuration.setLoad(false);
565                attributeChanged();
566            }
567        }
568    
569        public synchronized void removeConfiguration(Artifact configName) {
570            ConfigurationOverride configuration = serverOverride.getConfiguration(configName);
571            if (configuration == null) {
572                return;
573            }
574            serverOverride.removeConfiguration(configName);
575            attributeChanged();
576        }
577    
578        public Artifact[] getListedConfigurations(Artifact query) {
579            return serverOverride.queryConfigurations(query);
580        }
581    
582        public void stopConfiguration(Artifact configName) {
583            ConfigurationOverride configuration = serverOverride.getConfiguration(configName);
584            if (configuration == null) {
585                return;
586            }
587            configuration.setLoad(false);
588            attributeChanged();
589        }
590    
591        public void migrateConfiguration(Artifact oldName, Artifact newName, Configuration configuration) {
592            ConfigurationOverride configInfo = serverOverride.getConfiguration(oldName);
593            if(configInfo == null) {
594                throw new IllegalArgumentException("Trying to migrate unknown configuration: " + oldName);
595            }
596            serverOverride.removeConfiguration(oldName);
597            configInfo = new ConfigurationOverride(configInfo, newName);
598            //todo: check whether all the attributes are still valid for the new configuration
599            serverOverride.addConfiguration(configInfo);
600            attributeChanged();
601        }
602    
603        //GBeanLifeCycle
604        public synchronized void doStart() throws Exception {
605            load();
606            if (!readOnly) {
607                timer = new Timer();
608            }
609            log.debug("Started LocalAttributeManager with data on " + serverOverride.getConfigurations().size() + " configurations");
610        }
611    
612        public synchronized void doStop() throws Exception {
613            boolean doSave = false;
614            synchronized (this) {
615                if (timer != null) {
616                    timer.cancel();
617                    if (currentTask != null) {
618                        currentTask.cancel();
619                        doSave = true;
620                    }
621                }
622            }
623            if (doSave) {
624                save();
625            }
626            log.debug("Stopped LocalAttributeManager with data on " + serverOverride.getConfigurations().size() + " configurations");
627            serverOverride = new ServerOverride();
628        }
629    
630        public synchronized void doFail() {
631            synchronized (this) {
632                if (timer != null) {
633                    timer.cancel();
634                    if (currentTask != null) {
635                        currentTask.cancel();
636                    }
637                }
638            }
639            serverOverride = new ServerOverride();
640        }
641    
642        private synchronized void ensureParentDirectory() throws IOException {
643            if (attributeFile == null) {
644                attributeFile = serverInfo.resolveServer(configFile);
645                tempFile = new File(attributeFile.getAbsolutePath() + TEMP_EXTENSION);
646                backupFile = new File(attributeFile.getAbsolutePath() + BACKUP_EXTENSION);
647            }
648            File parent = attributeFile.getParentFile();
649            if (!parent.isDirectory()) {
650                if (!parent.mkdirs()) {
651                    throw new IOException("Unable to create directory for list:" + parent);
652                }
653            }
654            if (!parent.canRead() || !parent.canWrite()) {
655                throw new IOException("Unable to write manageable attribute files to directory " + parent.getAbsolutePath());
656            }
657        }
658    
659        private synchronized void attributeChanged() {
660            if (currentTask != null) {
661                currentTask.cancel();
662            }
663            if (timer != null) {
664                currentTask = new TimerTask() {
665    
666                    public void run() {
667                        try {
668                            LocalAttributeManager.this.save();
669                        } catch (IOException e) {
670                            log.error("Error saving attributes", e);
671                        }
672                    }
673                };
674                timer.schedule(currentTask, SAVE_BUFFER_MS);
675            }
676        }
677    
678        public static final GBeanInfo GBEAN_INFO;
679    
680        static {
681            GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic(LocalAttributeManager.class, "AttributeStore");
682            infoFactory.addReference("ServerInfo", ServerInfo.class, "GBean");
683            infoFactory.addAttribute("configFile", String.class, true);
684            infoFactory.addAttribute("readOnly", boolean.class, true);
685            infoFactory.addInterface(ManageableAttributeStore.class);
686            infoFactory.addInterface(PersistentConfigurationList.class);
687    
688            infoFactory.setConstructor(new String[]{"configFile", "readOnly", "ServerInfo"});
689    
690            GBEAN_INFO = infoFactory.getBeanInfo();
691        }
692    
693        public static GBeanInfo getGBeanInfo() {
694            return GBEAN_INFO;
695        }
696    }