001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.configuration2.builder; 018 019import java.util.Map; 020 021import org.apache.commons.configuration2.FileBasedConfiguration; 022import org.apache.commons.configuration2.ex.ConfigurationException; 023import org.apache.commons.configuration2.io.FileHandler; 024import org.apache.commons.configuration2.reloading.ReloadingController; 025import org.apache.commons.configuration2.reloading.ReloadingControllerSupport; 026import org.apache.commons.configuration2.reloading.ReloadingDetector; 027 028/** 029 * <p> 030 * A specialized {@code ConfigurationBuilder} implementation which can handle 031 * configurations read from a {@link FileHandler} and supports reloading. 032 * </p> 033 * <p> 034 * This builder class exposes a {@link ReloadingController} object controlling 035 * reload operations on the file-based configuration produced as result object. 036 * For the {@code FileHandler} defining the location of the configuration a 037 * configurable {@link ReloadingDetector} is created and associated with the 038 * controller. So changes on the source file can be detected. When ever such a 039 * change occurs, the result object of this builder is reset. This means that 040 * the next time {@code getConfiguration()} is called a new 041 * {@code Configuration} object is created which is loaded from the modified 042 * file. 043 * </p> 044 * <p> 045 * Client code interested in notifications can register a listener at this 046 * builder to receive reset events. When such an event is received the new 047 * result object can be requested. This way client applications can be sure to 048 * work with an up-to-date configuration. It is also possible to register a 049 * listener directly at the {@code ReloadingController}. 050 * </p> 051 * <p> 052 * This builder does not actively trigger the {@code ReloadingController} to 053 * perform a reload check. This has to be done by an external component, e.g. a 054 * timer. 055 * </p> 056 * 057 * @since 2.0 058 * @param <T> the concrete type of {@code Configuration} objects created by this 059 * builder 060 */ 061public class ReloadingFileBasedConfigurationBuilder<T extends FileBasedConfiguration> 062 extends FileBasedConfigurationBuilder<T> implements ReloadingControllerSupport 063{ 064 /** The default factory for creating reloading detector objects. */ 065 private static final ReloadingDetectorFactory DEFAULT_DETECTOR_FACTORY = 066 new DefaultReloadingDetectorFactory(); 067 068 /** The reloading controller associated with this object. */ 069 private final ReloadingController reloadingController; 070 071 /** 072 * The reloading detector which does the actual reload check for the current 073 * result object. A new instance is created whenever a new result object 074 * (and thus a new current file handler) becomes available. The field must 075 * be volatile because it is accessed by the reloading controller probably 076 * from within another thread. 077 */ 078 private volatile ReloadingDetector resultReloadingDetector; 079 080 /** 081 * Creates a new instance of {@code ReloadingFileBasedConfigurationBuilder} 082 * which produces result objects of the specified class and sets 083 * initialization parameters. 084 * 085 * @param resCls the result class (must not be <b>null</b> 086 * @param params a map with initialization parameters 087 * @throws IllegalArgumentException if the result class is <b>null</b> 088 */ 089 public ReloadingFileBasedConfigurationBuilder(final Class<? extends T> resCls, 090 final Map<String, Object> params) 091 { 092 super(resCls, params); 093 reloadingController = createReloadingController(); 094 } 095 096 /** 097 * Creates a new instance of {@code ReloadingFileBasedConfigurationBuilder} 098 * which produces result objects of the specified class and sets 099 * initialization parameters and the <em>allowFailOnInit</em> flag. 100 * 101 * @param resCls the result class (must not be <b>null</b> 102 * @param params a map with initialization parameters 103 * @param allowFailOnInit the <em>allowFailOnInit</em> flag 104 * @throws IllegalArgumentException if the result class is <b>null</b> 105 */ 106 public ReloadingFileBasedConfigurationBuilder(final Class<? extends T> resCls, 107 final Map<String, Object> params, final boolean allowFailOnInit) 108 { 109 super(resCls, params, allowFailOnInit); 110 reloadingController = createReloadingController(); 111 } 112 113 /** 114 * Creates a new instance of {@code ReloadingFileBasedConfigurationBuilder} 115 * which produces result objects of the specified class. 116 * 117 * @param resCls the result class (must not be <b>null</b> 118 * @throws IllegalArgumentException if the result class is <b>null</b> 119 */ 120 public ReloadingFileBasedConfigurationBuilder(final Class<? extends T> resCls) 121 { 122 super(resCls); 123 reloadingController = createReloadingController(); 124 } 125 126 /** 127 * Returns the {@code ReloadingController} associated with this builder. 128 * This controller is directly created. However, it becomes active (i.e. 129 * associated with a meaningful reloading detector) not before a result 130 * object was created. 131 * 132 * @return the {@code ReloadingController} 133 */ 134 @Override 135 public ReloadingController getReloadingController() 136 { 137 return reloadingController; 138 } 139 140 /** 141 * {@inheritDoc} This method is overridden here to change the result type. 142 */ 143 @Override 144 public ReloadingFileBasedConfigurationBuilder<T> configure( 145 final BuilderParameters... params) 146 { 147 super.configure(params); 148 return this; 149 } 150 151 /** 152 * Creates a {@code ReloadingDetector} which monitors the passed in 153 * {@code FileHandler}. This method is called each time a new result object 154 * is created with the current {@code FileHandler}. This implementation 155 * checks whether a {@code ReloadingDetectorFactory} is specified in the 156 * current parameters. If this is the case, it is invoked. Otherwise, a 157 * default factory is used to create a {@code FileHandlerReloadingDetector} 158 * object. Note: This method is called from a synchronized block. 159 * 160 * @param handler the current {@code FileHandler} 161 * @param fbparams the object with parameters related to file-based builders 162 * @return a {@code ReloadingDetector} for this {@code FileHandler} 163 * @throws ConfigurationException if an error occurs 164 */ 165 protected ReloadingDetector createReloadingDetector(final FileHandler handler, 166 final FileBasedBuilderParametersImpl fbparams) 167 throws ConfigurationException 168 { 169 return fetchDetectorFactory(fbparams).createReloadingDetector(handler, 170 fbparams); 171 } 172 173 /** 174 * {@inheritDoc} This implementation also takes care that a new 175 * {@code ReloadingDetector} for the new current {@code FileHandler} is 176 * created. Also, the reloading controller's reloading state has to be 177 * reset; after the creation of a new result object changes in the 178 * underlying configuration source have to be monitored again. 179 */ 180 @Override 181 protected void initFileHandler(final FileHandler handler) 182 throws ConfigurationException 183 { 184 super.initFileHandler(handler); 185 186 resultReloadingDetector = 187 createReloadingDetector(handler, 188 FileBasedBuilderParametersImpl.fromParameters( 189 getParameters(), true)); 190 } 191 192 /** 193 * Creates the {@code ReloadingController} associated with this object. The 194 * controller is assigned a specialized reloading detector which delegates 195 * to the detector for the current result object. ( 196 * {@code FileHandlerReloadingDetector} does not support changing the file 197 * handler, and {@code ReloadingController} does not support changing the 198 * reloading detector; therefore, this level of indirection is needed to 199 * change the monitored file dynamically.) 200 * 201 * @return the new {@code ReloadingController} 202 */ 203 private ReloadingController createReloadingController() 204 { 205 final ReloadingDetector ctrlDetector = createReloadingDetectorForController(); 206 final ReloadingController ctrl = new ReloadingController(ctrlDetector); 207 connectToReloadingController(ctrl); 208 return ctrl; 209 } 210 211 /** 212 * Creates a {@code ReloadingDetector} wrapper to be passed to the 213 * associated {@code ReloadingController}. This detector wrapper simply 214 * delegates to the current {@code ReloadingDetector} if it is available. 215 * 216 * @return the wrapper {@code ReloadingDetector} 217 */ 218 private ReloadingDetector createReloadingDetectorForController() 219 { 220 return new ReloadingDetector() 221 { 222 @Override 223 public void reloadingPerformed() 224 { 225 final ReloadingDetector detector = resultReloadingDetector; 226 if (detector != null) 227 { 228 detector.reloadingPerformed(); 229 } 230 } 231 232 @Override 233 public boolean isReloadingRequired() 234 { 235 final ReloadingDetector detector = resultReloadingDetector; 236 return detector != null && detector.isReloadingRequired(); 237 } 238 }; 239 } 240 241 /** 242 * Returns a {@code ReloadingDetectorFactory} either from the passed in 243 * parameters or a default factory. 244 * 245 * @param params the current parameters object 246 * @return the {@code ReloadingDetectorFactory} to be used 247 */ 248 private static ReloadingDetectorFactory fetchDetectorFactory( 249 final FileBasedBuilderParametersImpl params) 250 { 251 final ReloadingDetectorFactory factory = params.getReloadingDetectorFactory(); 252 return factory != null ? factory : DEFAULT_DETECTOR_FACTORY; 253 } 254}