001/* 002 * Copyright 2018 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.core.util.filereloader; 017 018import java.io.InputStream; 019import java.util.Map; 020import java.util.concurrent.ConcurrentHashMap; 021 022import org.apache.avalon.framework.activity.Initializable; 023import org.apache.avalon.framework.component.Component; 024import org.apache.avalon.framework.service.ServiceException; 025import org.apache.avalon.framework.service.ServiceManager; 026import org.apache.avalon.framework.service.Serviceable; 027import org.apache.excalibur.source.Source; 028import org.apache.excalibur.source.SourceNotFoundException; 029import org.apache.excalibur.source.SourceResolver; 030 031import org.ametys.core.cache.AbstractCacheManager; 032import org.ametys.core.cache.Cache; 033import org.ametys.runtime.i18n.I18nizableText; 034import org.ametys.runtime.plugin.component.AbstractLogEnabled; 035 036/** 037 * a helper to track modification of a configuration file 038 */ 039public class FileReloaderUtils extends AbstractLogEnabled implements Component, Serviceable, Initializable 040{ 041 /** The avalon role */ 042 public static final String ROLE = FileReloaderUtils.class.getName(); 043 044 private static final String __FILERELOADER_CACHE = FileReloaderUtils.class.getName() + "$Cache"; 045 046 /** The source resolver */ 047 protected SourceResolver _resolver; 048 049 private AbstractCacheManager _cacheManager; 050 private Map<String, FileMetadatas> _files; 051 052 @Override 053 public void service(ServiceManager manager) throws ServiceException 054 { 055 _resolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); 056 _cacheManager = (AbstractCacheManager) manager.lookup(AbstractCacheManager.ROLE); 057 } 058 059 public void initialize() throws Exception 060 { 061 _files = new ConcurrentHashMap<>(); 062 063 _cacheManager.createRequestCache(__FILERELOADER_CACHE, 064 new I18nizableText("plugin.core", "PLUGINS_CORE_FILERELOADER_CACHE_LABEL"), 065 new I18nizableText("plugin.core", "PLUGINS_CORE_FILERELOADER_CACHE_DESCRIPTION"), 066 true); 067 } 068 069 /** 070 * Request to read/update the file 071 * @param sourceUrl url of the file to read 072 * @param fileReloader FileReloader that will be called to get an ID and read the file 073 * @throws Exception the source can not be read 074 */ 075 public void updateFile(String sourceUrl, FileReloader fileReloader) throws Exception 076 { 077 updateFile(sourceUrl, null, fileReloader); 078 } 079 080 /** 081 * Request to read/update the file 082 * @param sourceUrl url of the file to read 083 * @param forceRead true to force reload even if the file was not modified 084 * @param fileReloader FileReloader that will be called to get an ID and read the file 085 * @throws Exception the source can not be read 086 */ 087 public void updateFile(String sourceUrl, boolean forceRead, FileReloader fileReloader) throws Exception 088 { 089 updateFile(sourceUrl, null, forceRead, fileReloader); 090 } 091 092 /** 093 * Request to read/update the file 094 * @param sourceUrl url of the file to read 095 * @param parameters parameters passed to the resolver to get the file via it's url (can be null) 096 * @param fileReloader FileReloader that will be called to get an ID and read the file 097 * @throws Exception the source can not be read 098 */ 099 public void updateFile(String sourceUrl, Map parameters, FileReloader fileReloader) throws Exception 100 { 101 updateFile(sourceUrl, parameters, false, fileReloader); 102 } 103 104 /** 105 * Request to read/update the file 106 * @param sourceUrl url of the file to read 107 * @param parameters parameters passed to the resolver to get the file via it's url (can be null) 108 * @param forceRead true to force reload even if the file was not modified 109 * @param fileReloader FileReloader that will be called to get an ID and read the file 110 * @throws Exception the source can not be read 111 */ 112 public void updateFile(String sourceUrl, Map parameters, boolean forceRead, FileReloader fileReloader) throws Exception 113 { 114 String id = fileReloader.getId(sourceUrl); 115 116 Cache<String, Object> cache = _cacheManager.get(__FILERELOADER_CACHE); 117 if (!forceRead && cache.get(id) != null) // cache#get used here instead of cache#hasKey to trigger hit/miss stats 118 { 119 // already been handled in this request 120 return; 121 } 122 123 FileMetadatas fileMetadatas = _files.computeIfAbsent(id, i -> new FileMetadatas(sourceUrl, parameters)); 124 synchronized (fileMetadatas) 125 { 126 Source source = null; 127 try 128 { 129 source = _resolver.resolveURI(fileMetadatas.getSourceUrl(), null, fileMetadatas.getParameters()); 130 if (!source.exists()) 131 { 132 throw new SourceNotFoundException("Source '" + fileMetadatas.getSourceUrl() + "' not found"); 133 } 134 135 long sourceLastModified = (source.getLastModified() / 1000) * 1000; // hack for file systems only returning seconds 136 if (forceRead || sourceLastModified != fileMetadatas.getLastModified()) 137 { 138 getLogger().debug("Reading configuration file at {}", fileMetadatas.getSourceUrl()); 139 140 try (InputStream is = source.getInputStream()) 141 { 142 fileReloader.updateFile(fileMetadatas.getSourceUrl(), source, is); 143 fileMetadatas.setLastModified(sourceLastModified); 144 getLogger().debug("Configuration file read. at {}", fileMetadatas.getSourceUrl()); 145 } 146 } 147 } 148 catch (SourceNotFoundException e) 149 { 150 if (forceRead || fileMetadatas.getLastModified() != FileMetadatas.FILE_DOES_NOT_EXIST) 151 { 152 getLogger().debug("File at {} could not be found", fileMetadatas.getSourceUrl(), e); 153 fileReloader.updateFile(fileMetadatas.getSourceUrl(), null, null); 154 } 155 156 fileMetadatas.setLastModified(FileMetadatas.FILE_DOES_NOT_EXIST); 157 } 158 finally 159 { 160 _resolver.release(source); 161 cache.put(id, id); // no matter the value, we only test the presence of the key 162 } 163 } 164 } 165 166 private static class FileMetadatas 167 { 168 /** When the date was not set yet */ 169 public static final long UNKNOWN = -1; 170 /** When there is no corresponding file */ 171 public static final long FILE_DOES_NOT_EXIST = 0; 172 173 private long _lastModified; 174 private String _sourceUrl; 175 private Map _parameters; 176 177 public FileMetadatas(String sourceUrl, Map parameters) 178 { 179 this._sourceUrl = sourceUrl; 180 this._lastModified = UNKNOWN; 181 this._parameters = parameters; 182 } 183 184 public long getLastModified() 185 { 186 return this._lastModified; 187 } 188 public void setLastModified(long lastFileReading) 189 { 190 this._lastModified = lastFileReading; 191 } 192 193 public String getSourceUrl() 194 { 195 return this._sourceUrl; 196 } 197 198 public Map getParameters() 199 { 200 return this._parameters; 201 } 202 } 203 204}