View Javadoc

1   /**
2    *
3    *  Licensed to the Apache Software Foundation (ASF) under one or more
4    *  contributor license agreements.  See the NOTICE file distributed with
5    *  this work for additional information regarding copyright ownership.
6    *  The ASF licenses this file to You under the Apache License, Version 2.0
7    *  (the "License"); you may not use this file except in compliance with
8    *  the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing, software
13   *  distributed under the License is distributed on an "AS IS" BASIS,
14   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   *  See the License for the specific language governing permissions and
16   *  limitations under the License.
17   */
18  package org.apache.geronimo.system.configuration;
19  
20  import org.apache.commons.logging.Log;
21  import org.apache.commons.logging.LogFactory;
22  import org.apache.geronimo.common.propertyeditor.PropertyEditors;
23  import org.apache.geronimo.gbean.AbstractName;
24  import org.apache.geronimo.gbean.GAttributeInfo;
25  import org.apache.geronimo.gbean.GBeanData;
26  import org.apache.geronimo.gbean.GBeanInfo;
27  import org.apache.geronimo.gbean.GBeanInfoBuilder;
28  import org.apache.geronimo.gbean.GBeanLifecycle;
29  import org.apache.geronimo.gbean.GReferenceInfo;
30  import org.apache.geronimo.gbean.ReferencePatterns;
31  import org.apache.geronimo.kernel.config.InvalidConfigException;
32  import org.apache.geronimo.kernel.config.ManageableAttributeStore;
33  import org.apache.geronimo.kernel.config.PersistentConfigurationList;
34  import org.apache.geronimo.kernel.config.Configuration;
35  import org.apache.geronimo.kernel.repository.Artifact;
36  import org.apache.geronimo.kernel.InvalidGBeanException;
37  import org.apache.geronimo.kernel.util.XmlUtil;
38  import org.apache.geronimo.system.serverinfo.ServerInfo;
39  import org.w3c.dom.Document;
40  import org.w3c.dom.Element;
41  import org.xml.sax.ErrorHandler;
42  import org.xml.sax.InputSource;
43  import org.xml.sax.SAXException;
44  import org.xml.sax.SAXParseException;
45  
46  import javax.xml.parsers.DocumentBuilder;
47  import javax.xml.parsers.DocumentBuilderFactory;
48  import javax.xml.parsers.ParserConfigurationException;
49  import javax.xml.transform.TransformerFactory;
50  import javax.xml.transform.Transformer;
51  import javax.xml.transform.OutputKeys;
52  import javax.xml.transform.TransformerException;
53  import javax.xml.transform.stream.StreamResult;
54  import javax.xml.transform.dom.DOMSource;
55  
56  import java.beans.PropertyEditor;
57  
58  import java.io.File;
59  import java.io.FileInputStream;
60  import java.io.InputStream;
61  import java.io.BufferedInputStream;
62  import java.io.FileNotFoundException;
63  import java.io.FileOutputStream;
64  import java.io.IOException;
65  import java.io.OutputStream;
66  import java.io.BufferedOutputStream;
67  
68  import java.util.ArrayList;
69  import java.util.Collection;
70  import java.util.HashMap;
71  import java.util.Iterator;
72  import java.util.List;
73  import java.util.Map;
74  import java.util.Timer;
75  import java.util.TimerTask;
76  
77  /**
78   * Stores managed attributes in an XML file on the local filesystem.
79   *
80   * @version $Rev: 470597 $ $Date: 2006-11-02 15:30:55 -0800 (Thu, 02 Nov 2006) $
81   */
82  public class LocalAttributeManager implements PluginAttributeStore, PersistentConfigurationList, GBeanLifecycle {
83      private static final Log log = LogFactory.getLog(LocalAttributeManager.class);
84  
85      private static final String CONFIG_FILE_PROPERTY = "org.apache.geronimo.config.file";
86  
87      private static final String BACKUP_EXTENSION = ".bak";
88      private static final String TEMP_EXTENSION = ".working";
89      private static final int SAVE_BUFFER_MS = 5000;
90  
91      private final ServerInfo serverInfo;
92      private final String configFile;
93      private final boolean readOnly;
94  
95      private File attributeFile;
96      private File backupFile;
97      private File tempFile;
98      private ServerOverride serverOverride;
99  
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 }