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 }