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.wicket.resource; 018 019import java.io.BufferedInputStream; 020import java.io.IOException; 021import java.util.ArrayList; 022import java.util.Enumeration; 023import java.util.Iterator; 024import java.util.List; 025import java.util.Map; 026import java.util.concurrent.ConcurrentHashMap; 027 028import org.apache.wicket.core.util.resource.locator.IResourceStreamLocator; 029import org.apache.wicket.util.io.IOUtils; 030import org.apache.wicket.util.listener.IChangeListener; 031import org.apache.wicket.util.resource.IResourceStream; 032import org.apache.wicket.util.resource.ResourceStreamNotFoundException; 033import org.apache.wicket.util.value.ValueMap; 034import org.apache.wicket.util.watch.IModifiable; 035import org.apache.wicket.util.watch.IModificationWatcher; 036import org.apache.wicket.util.watch.ModificationWatcher; 037import org.slf4j.Logger; 038import org.slf4j.LoggerFactory; 039 040 041/** 042 * Default implementation of {@link IPropertiesFactory} which uses the 043 * {@link IResourceStreamLocator} as defined by 044 * {@link org.apache.wicket.settings.ResourceSettings#getResourceStreamLocator()} 045 * to load the {@link Properties} objects. Depending on the settings, it will assign 046 * {@link ModificationWatcher}s to the loaded resources to support reloading. 047 * 048 * @see org.apache.wicket.settings.ResourceSettings#getPropertiesFactory() 049 * 050 * @author Juergen Donnerstag 051 */ 052public class PropertiesFactory implements IPropertiesFactory 053{ 054 /** Log. */ 055 private static final Logger log = LoggerFactory.getLogger(PropertiesFactory.class); 056 057 /** Listeners will be invoked after changes to property file have been detected */ 058 private final List<IPropertiesChangeListener> afterReloadListeners = new ArrayList<>(); 059 060 /** Cache for all property files loaded */ 061 private final Map<String, Properties> propertiesCache = newPropertiesCache(); 062 063 /** Provides the environment for properties factory */ 064 private final IPropertiesFactoryContext context; 065 066 /** List of Properties Loader */ 067 private final List<IPropertiesLoader> propertiesLoader; 068 069 /** 070 * Construct. 071 * 072 * @param context 073 * context for properties factory 074 */ 075 public PropertiesFactory(final IPropertiesFactoryContext context) 076 { 077 this.context = context; 078 this.propertiesLoader = new ArrayList<>(); 079 this.propertiesLoader.add(new IsoPropertiesFilePropertiesLoader("properties")); 080 this.propertiesLoader.add(new UtfPropertiesFilePropertiesLoader("utf8.properties", "utf-8")); 081 this.propertiesLoader.add(new XmlFilePropertiesLoader("properties.xml")); 082 } 083 084 /** 085 * Gets the {@link List} of properties loader. You may add or remove properties loaders at your 086 * will. 087 * 088 * @return the {@link List} of properties loader 089 */ 090 public List<IPropertiesLoader> getPropertiesLoaders() 091 { 092 return propertiesLoader; 093 } 094 095 /** 096 * @return new Cache implementation 097 */ 098 protected Map<String, Properties> newPropertiesCache() 099 { 100 return new ConcurrentHashMap<>(); 101 } 102 103 /** 104 * @see org.apache.wicket.resource.IPropertiesFactory#addListener(org.apache.wicket.resource.IPropertiesChangeListener) 105 */ 106 @Override 107 public void addListener(final IPropertiesChangeListener listener) 108 { 109 // Make sure listeners are added only once 110 if (afterReloadListeners.contains(listener) == false) 111 { 112 afterReloadListeners.add(listener); 113 } 114 } 115 116 /** 117 * @see org.apache.wicket.resource.IPropertiesFactory#clearCache() 118 */ 119 @Override 120 public final void clearCache() 121 { 122 if (propertiesCache != null) 123 { 124 propertiesCache.clear(); 125 } 126 127 // clear the localizer cache as well 128 context.getLocalizer().clearCache(); 129 } 130 131 @Override 132 public Properties load(final Class<?> clazz, final String path) 133 { 134 // Check the cache 135 Properties properties = null; 136 if (propertiesCache != null) 137 { 138 properties = propertiesCache.get(path); 139 } 140 141 if (properties == null) 142 { 143 Iterator<IPropertiesLoader> iter = propertiesLoader.iterator(); 144 while ((properties == null) && iter.hasNext()) 145 { 146 IPropertiesLoader loader = iter.next(); 147 String fullPath = path + "." + loader.getFileExtension(); 148 149 // If not in the cache than try to load properties 150 IResourceStream resourceStream = context.getResourceStreamLocator() 151 .locate(clazz, fullPath); 152 if (resourceStream == null) 153 { 154 continue; 155 } 156 157 // Watch file modifications 158 final IModificationWatcher watcher = context.getResourceWatcher(true); 159 if (watcher != null) 160 { 161 addToWatcher(path, resourceStream, watcher); 162 } 163 164 ValueMap props = loadFromLoader(loader, resourceStream); 165 if (props != null) 166 { 167 properties = new Properties(path, props); 168 } 169 } 170 171 // Cache the lookup 172 if (propertiesCache != null) 173 { 174 if (properties == null) 175 { 176 // Could not locate properties, store a placeholder 177 propertiesCache.put(path, Properties.EMPTY_PROPERTIES); 178 } 179 else 180 { 181 propertiesCache.put(path, properties); 182 } 183 } 184 } 185 186 if (properties == Properties.EMPTY_PROPERTIES) 187 { 188 // Translate empty properties placeholder to null prior to returning 189 properties = null; 190 } 191 192 return properties; 193 } 194 195 /** 196 * 197 * @param loader 198 * @param resourceStream 199 * @return properties 200 */ 201 protected ValueMap loadFromLoader(final IPropertiesLoader loader, 202 final IResourceStream resourceStream) 203 { 204 if (log.isDebugEnabled()) 205 { 206 log.debug("Loading properties files from '{}' with loader '{}'", resourceStream, loader); 207 } 208 209 BufferedInputStream in = null; 210 211 try 212 { 213 // Get the InputStream 214 in = new BufferedInputStream(resourceStream.getInputStream()); 215 ValueMap data = loader.loadWicketProperties(in); 216 if (data == null) 217 { 218 java.util.Properties props = loader.loadJavaProperties(in); 219 if (props != null) 220 { 221 // Copy the properties into the ValueMap 222 data = new ValueMap(); 223 Enumeration<?> enumeration = props.propertyNames(); 224 while (enumeration.hasMoreElements()) 225 { 226 String property = (String)enumeration.nextElement(); 227 data.put(property, props.getProperty(property)); 228 } 229 } 230 } 231 return data; 232 } 233 catch (ResourceStreamNotFoundException | IOException e) 234 { 235 log.warn("Unable to find resource " + resourceStream, e); 236 } 237 finally 238 { 239 IOUtils.closeQuietly(in); 240 IOUtils.closeQuietly(resourceStream); 241 } 242 243 return null; 244 } 245 246 /** 247 * Add the resource stream to the file being watched 248 * 249 * @param path 250 * @param resourceStream 251 * @param watcher 252 */ 253 protected void addToWatcher(final String path, final IResourceStream resourceStream, 254 final IModificationWatcher watcher) 255 { 256 watcher.add(resourceStream, new IChangeListener<IModifiable>() 257 { 258 @Override 259 public void onChange(IModifiable modifiable) 260 { 261 log.info("A properties files has changed. Removing all entries " + 262 "from the cache. Resource: " + resourceStream); 263 264 // Clear the whole cache as associated localized files may 265 // be affected and may need reloading as well. 266 clearCache(); 267 268 // Inform all listeners 269 for (IPropertiesChangeListener listener : afterReloadListeners) 270 { 271 try 272 { 273 listener.propertiesChanged(path); 274 } 275 catch (Exception ex) 276 { 277 PropertiesFactory.log.error("PropertiesReloadListener has thrown an exception: " + 278 ex.getMessage()); 279 } 280 } 281 } 282 }); 283 } 284 285 /** 286 * For subclasses to get access to the cache 287 * 288 * @return Map 289 */ 290 protected final Map<String, Properties> getCache() 291 { 292 return propertiesCache; 293 } 294}