View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.configuration2.builder;
18  
19  import org.apache.commons.configuration2.event.ConfigurationEvent;
20  import org.apache.commons.configuration2.event.EventListener;
21  import org.apache.commons.configuration2.ex.ConfigurationException;
22  import org.apache.commons.configuration2.io.FileHandler;
23  import org.apache.commons.configuration2.io.FileHandlerListenerAdapter;
24  import org.apache.commons.logging.Log;
25  import org.apache.commons.logging.LogFactory;
26  
27  /**
28   * <p>
29   * A listener class implementing an auto save mechanism for file-based configurations.
30   * </p>
31   * <p>
32   * Instances of this class are used by {@link FileBasedConfigurationBuilder} to save their managed configuration
33   * instances when they are changed. Objects are registered at {@code Configuration} objects as event listeners and thus
34   * can trigger save operations whenever a change event is received.
35   * </p>
36   * <p>
37   * There is one complication however: Some configuration implementations fire change events during a load operation.
38   * Such events must be ignored to prevent corruption of the source file. This is achieved by monitoring the associated
39   * {@code FileHandler}: during load operations no auto-save is performed.
40   * </p>
41   *
42   * @since 2.0
43   */
44  final class AutoSaveListener extends FileHandlerListenerAdapter implements EventListener<ConfigurationEvent> {
45      /** The logger. */
46      private final Log log = LogFactory.getLog(getClass());
47  
48      /** The associated builder. */
49      private final FileBasedConfigurationBuilder<?> builder;
50  
51      /** Stores the file handler monitored by this listener. */
52      private FileHandler handler;
53  
54      /**
55       * A counter to keep track whether a load operation is currently in progress.
56       */
57      private int loading;
58  
59      /**
60       * Creates a new instance of {@code AutoSaveListener} and initializes it with the associated builder.
61       *
62       * @param bldr the associated builder
63       */
64      public AutoSaveListener(final FileBasedConfigurationBuilder<?> bldr) {
65          builder = bldr;
66      }
67  
68      /**
69       * Checks whether an auto save operation has to be performed based on the passed in event and the current state of this
70       * object.
71       *
72       * @param event the configuration change event
73       * @return <strong>true</strong> if a save operation should be performed, <strong>false</strong> otherwise
74       */
75      private boolean autoSaveRequired(final ConfigurationEvent event) {
76          return !event.isBeforeUpdate() && !inLoadOperation();
77      }
78  
79      /**
80       * Returns a flag whether a load operation is currently in progress.
81       *
82       * @return a flag whether a load operation is in progress
83       */
84      private synchronized boolean inLoadOperation() {
85          return loading > 0;
86      }
87  
88      /**
89       * {@inheritDoc} This implementation decrements the counter for load operations in progress.
90       */
91      @Override
92      public synchronized void loaded(final FileHandler handler) {
93          loading--;
94      }
95  
96      /**
97       * {@inheritDoc} This implementation increments the counter for load operations in progress.
98       */
99      @Override
100     public synchronized void loading(final FileHandler handler) {
101         loading++;
102     }
103 
104     /**
105      * {@inheritDoc} This implementation checks whether an auto-safe operation should be performed. This is the case if the
106      * event indicates that an update of the configuration has been performed and currently no load operation is in
107      * progress.
108      */
109     @Override
110     public void onEvent(final ConfigurationEvent event) {
111         if (autoSaveRequired(event)) {
112             try {
113                 builder.save();
114             } catch (final ConfigurationException ce) {
115                 log.warn("Auto save failed!", ce);
116             }
117         }
118     }
119 
120     /**
121      * Updates the {@code FileHandler}. This method is called by the builder when a new configuration instance was created
122      * which is associated with a new file handler. It updates the internal file handler reference and performs necessary
123      * listener registrations.
124      *
125      * @param fh the new {@code FileHandler} (can be <strong>null</strong>)
126      */
127     public synchronized void updateFileHandler(final FileHandler fh) {
128         if (handler != null) {
129             handler.removeFileHandlerListener(this);
130         }
131 
132         if (fh != null) {
133             fh.addFileHandlerListener(this);
134         }
135         handler = fh;
136     }
137 }