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 }