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 * @version $Id: ReloadingFileBasedConfigurationBuilder.java 1700567 2015-09-01 14:24:09Z oheger $ 058 * @since 2.0 059 * @param <T> the concrete type of {@code Configuration} objects created by this 060 * builder 061 */ 062public class ReloadingFileBasedConfigurationBuilder<T extends FileBasedConfiguration> 063 extends FileBasedConfigurationBuilder<T> implements ReloadingControllerSupport 064{ 065 /** The default factory for creating reloading detector objects. */ 066 private static final ReloadingDetectorFactory DEFAULT_DETECTOR_FACTORY = 067 new DefaultReloadingDetectorFactory(); 068 069 /** The reloading controller associated with this object. */ 070 private final ReloadingController reloadingController; 071 072 /** 073 * The reloading detector which does the actual reload check for the current 074 * result object. A new instance is created whenever a new result object 075 * (and thus a new current file handler) becomes available. The field must 076 * be volatile because it is accessed by the reloading controller probably 077 * from within another thread. 078 */ 079 private volatile ReloadingDetector resultReloadingDetector; 080 081 /** 082 * Creates a new instance of {@code ReloadingFileBasedConfigurationBuilder} 083 * which produces result objects of the specified class and sets 084 * initialization parameters. 085 * 086 * @param resCls the result class (must not be <b>null</b> 087 * @param params a map with initialization parameters 088 * @throws IllegalArgumentException if the result class is <b>null</b> 089 */ 090 public ReloadingFileBasedConfigurationBuilder(Class<? extends T> resCls, 091 Map<String, Object> params) 092 { 093 super(resCls, params); 094 reloadingController = createReloadingController(); 095 } 096 097 /** 098 * Creates a new instance of {@code ReloadingFileBasedConfigurationBuilder} 099 * which produces result objects of the specified class and sets 100 * initialization parameters and the <em>allowFailOnInit</em> flag. 101 * 102 * @param resCls the result class (must not be <b>null</b> 103 * @param params a map with initialization parameters 104 * @param allowFailOnInit the <em>allowFailOnInit</em> flag 105 * @throws IllegalArgumentException if the result class is <b>null</b> 106 */ 107 public ReloadingFileBasedConfigurationBuilder(Class<? extends T> resCls, 108 Map<String, Object> params, boolean allowFailOnInit) 109 { 110 super(resCls, params, allowFailOnInit); 111 reloadingController = createReloadingController(); 112 } 113 114 /** 115 * Creates a new instance of {@code ReloadingFileBasedConfigurationBuilder} 116 * which produces result objects of the specified class. 117 * 118 * @param resCls the result class (must not be <b>null</b> 119 * @throws IllegalArgumentException if the result class is <b>null</b> 120 */ 121 public ReloadingFileBasedConfigurationBuilder(Class<? extends T> resCls) 122 { 123 super(resCls); 124 reloadingController = createReloadingController(); 125 } 126 127 /** 128 * Returns the {@code ReloadingController} associated with this builder. 129 * This controller is directly created. However, it becomes active (i.e. 130 * associated with a meaningful reloading detector) not before a result 131 * object was created. 132 * 133 * @return the {@code ReloadingController} 134 */ 135 @Override 136 public ReloadingController getReloadingController() 137 { 138 return reloadingController; 139 } 140 141 /** 142 * {@inheritDoc} This method is overridden here to change the result type. 143 */ 144 @Override 145 public ReloadingFileBasedConfigurationBuilder<T> configure( 146 BuilderParameters... params) 147 { 148 super.configure(params); 149 return this; 150 } 151 152 /** 153 * Creates a {@code ReloadingDetector} which monitors the passed in 154 * {@code FileHandler}. This method is called each time a new result object 155 * is created with the current {@code FileHandler}. This implementation 156 * checks whether a {@code ReloadingDetectorFactory} is specified in the 157 * current parameters. If this is the case, it is invoked. Otherwise, a 158 * default factory is used to create a {@code FileHandlerReloadingDetector} 159 * object. Note: This method is called from a synchronized block. 160 * 161 * @param handler the current {@code FileHandler} 162 * @param fbparams the object with parameters related to file-based builders 163 * @return a {@code ReloadingDetector} for this {@code FileHandler} 164 * @throws ConfigurationException if an error occurs 165 */ 166 protected ReloadingDetector createReloadingDetector(FileHandler handler, 167 FileBasedBuilderParametersImpl fbparams) 168 throws ConfigurationException 169 { 170 return fetchDetectorFactory(fbparams).createReloadingDetector(handler, 171 fbparams); 172 } 173 174 /** 175 * {@inheritDoc} This implementation also takes care that a new 176 * {@code ReloadingDetector} for the new current {@code FileHandler} is 177 * created. Also, the reloading controller's reloading state has to be 178 * reset; after the creation of a new result object changes in the 179 * underlying configuration source have to be monitored again. 180 */ 181 @Override 182 protected void initFileHandler(FileHandler handler) 183 throws ConfigurationException 184 { 185 super.initFileHandler(handler); 186 187 resultReloadingDetector = 188 createReloadingDetector(handler, 189 FileBasedBuilderParametersImpl.fromParameters( 190 getParameters(), true)); 191 } 192 193 /** 194 * Creates the {@code ReloadingController} associated with this object. The 195 * controller is assigned a specialized reloading detector which delegates 196 * to the detector for the current result object. ( 197 * {@code FileHandlerReloadingDetector} does not support changing the file 198 * handler, and {@code ReloadingController} does not support changing the 199 * reloading detector; therefore, this level of indirection is needed to 200 * change the monitored file dynamically.) 201 * 202 * @return the new {@code ReloadingController} 203 */ 204 private ReloadingController createReloadingController() 205 { 206 ReloadingDetector ctrlDetector = createReloadingDetectorForController(); 207 ReloadingController ctrl = new ReloadingController(ctrlDetector); 208 connectToReloadingController(ctrl); 209 return ctrl; 210 } 211 212 /** 213 * Creates a {@code ReloadingDetector} wrapper to be passed to the 214 * associated {@code ReloadingController}. This detector wrapper simply 215 * delegates to the current {@code ReloadingDetector} if it is available. 216 * 217 * @return the wrapper {@code ReloadingDetector} 218 */ 219 private ReloadingDetector createReloadingDetectorForController() 220 { 221 return new ReloadingDetector() 222 { 223 @Override 224 public void reloadingPerformed() 225 { 226 ReloadingDetector detector = resultReloadingDetector; 227 if (detector != null) 228 { 229 detector.reloadingPerformed(); 230 } 231 } 232 233 @Override 234 public boolean isReloadingRequired() 235 { 236 ReloadingDetector detector = resultReloadingDetector; 237 return (detector != null) && detector.isReloadingRequired(); 238 } 239 }; 240 } 241 242 /** 243 * Returns a {@code ReloadingDetectorFactory} either from the passed in 244 * parameters or a default factory. 245 * 246 * @param params the current parameters object 247 * @return the {@code ReloadingDetectorFactory} to be used 248 */ 249 private static ReloadingDetectorFactory fetchDetectorFactory( 250 FileBasedBuilderParametersImpl params) 251 { 252 ReloadingDetectorFactory factory = params.getReloadingDetectorFactory(); 253 return (factory != null) ? factory : DEFAULT_DETECTOR_FACTORY; 254 } 255}