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       * {@inheritDoc} This implementation checks whether an auto-safe operation should be performed. This is the case if the
70       * event indicates that an update of the configuration has been performed and currently no load operation is in
71       * progress.
72       */
73      @Override
74      public void onEvent(final ConfigurationEvent event) {
75          if (autoSaveRequired(event)) {
76              try {
77                  builder.save();
78              } catch (final ConfigurationException ce) {
79                  log.warn("Auto save failed!", ce);
80              }
81          }
82      }
83  
84      /**
85       * {@inheritDoc} This implementation increments the counter for load operations in progress.
86       */
87      @Override
88      public synchronized void loading(final FileHandler handler) {
89          loading++;
90      }
91  
92      /**
93       * {@inheritDoc} This implementation decrements the counter for load operations in progress.
94       */
95      @Override
96      public synchronized void loaded(final FileHandler handler) {
97          loading--;
98      }
99  
100     /**
101      * Updates the {@code FileHandler}. This method is called by the builder when a new configuration instance was created
102      * which is associated with a new file handler. It updates the internal file handler reference and performs necessary
103      * listener registrations.
104      *
105      * @param fh the new {@code FileHandler} (can be <b>null</b>)
106      */
107     public synchronized void updateFileHandler(final FileHandler fh) {
108         if (handler != null) {
109             handler.removeFileHandlerListener(this);
110         }
111 
112         if (fh != null) {
113             fh.addFileHandlerListener(this);
114         }
115         handler = fh;
116     }
117 
118     /**
119      * Returns a flag whether a load operation is currently in progress.
120      *
121      * @return a flag whether a load operation is in progress
122      */
123     private synchronized boolean inLoadOperation() {
124         return loading > 0;
125     }
126 
127     /**
128      * Checks whether an auto save operation has to be performed based on the passed in event and the current state of this
129      * object.
130      *
131      * @param event the configuration change event
132      * @return <b>true</b> if a save operation should be performed, <b>false</b> otherwise
133      */
134     private boolean autoSaveRequired(final ConfigurationEvent event) {
135         return !event.isBeforeUpdate() && !inLoadOperation();
136     }
137 }