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.time.Instant; 020import java.time.temporal.ChronoUnit; 021import java.util.HashMap; 022import java.util.Map; 023 024import org.apache.avalon.framework.activity.Initializable; 025import org.apache.avalon.framework.component.Component; 026import org.apache.avalon.framework.service.ServiceException; 027import org.apache.avalon.framework.service.ServiceManager; 028import org.apache.avalon.framework.service.Serviceable; 029import org.apache.excalibur.source.Source; 030import org.apache.excalibur.source.SourceNotFoundException; 031import org.apache.excalibur.source.SourceResolver; 032 033import org.ametys.runtime.plugin.component.AbstractLogEnabled; 034 035/** 036 * a helper to track modification of a configuration file 037 */ 038public class FileReloaderUtils extends AbstractLogEnabled implements Component, Serviceable, Initializable 039{ 040 /** The avalon role */ 041 public static final String ROLE = FileReloaderUtils.class.getName(); 042 043 /** The source resolver */ 044 protected SourceResolver _resolver; 045 046 private Map<String, FileMetadatas> _files; 047 048 public void initialize() throws Exception 049 { 050 this._files = new HashMap<>(); 051 } 052 053 @Override 054 public void service(ServiceManager manager) throws ServiceException 055 { 056 _resolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); 057 } 058 059 /** 060 * Request to read/update the file 061 * @param sourceUrl url of the file to read 062 * @param fileReloader FileReloader that will be called to get an ID and read the file 063 * @throws Exception the source can not be read 064 */ 065 public void updateFile(String sourceUrl, FileReloader fileReloader) throws Exception 066 { 067 updateFile(sourceUrl, null, fileReloader); 068 } 069 070 /** 071 * Request to read/update the file 072 * @param sourceUrl url of the file to read 073 * @param forceRead true to force reload even if the file was not modified 074 * @param fileReloader FileReloader that will be called to get an ID and read the file 075 * @throws Exception the source can not be read 076 */ 077 public void updateFile(String sourceUrl, boolean forceRead, FileReloader fileReloader) throws Exception 078 { 079 updateFile(sourceUrl, null, forceRead, fileReloader); 080 } 081 082 /** 083 * Request to read/update the file 084 * @param sourceUrl url of the file to read 085 * @param parameters parameters passed to the resolver to get the file via it's url (can be null) 086 * @param fileReloader FileReloader that will be called to get an ID and read the file 087 * @throws Exception the source can not be read 088 */ 089 public void updateFile(String sourceUrl, Map parameters, FileReloader fileReloader) throws Exception 090 { 091 updateFile(sourceUrl, parameters, false, fileReloader); 092 } 093 094 /** 095 * Request to read/update the file 096 * @param sourceUrl url of the file to read 097 * @param parameters parameters passed to the resolver to get the file via it's url (can be null) 098 * @param forceRead true to force reload even if the file was not modified 099 * @param fileReloader FileReloader that will be called to get an ID and read the file 100 * @throws Exception the source can not be read 101 */ 102 public void updateFile(String sourceUrl, Map parameters, boolean forceRead, FileReloader fileReloader) throws Exception 103 { 104 String id = fileReloader.getId(sourceUrl); 105 if (!_files.containsKey(id)) 106 { 107 _files.put(id, new FileMetadatas(sourceUrl, parameters)); 108 } 109 110 FileMetadatas fileMetadatas = _files.get(id); 111 112 synchronized (fileMetadatas) 113 { 114 Source source = null; 115 try 116 { 117 source = _resolver.resolveURI(fileMetadatas.getSourceUrl(), null, fileMetadatas.getParameters()); 118 if (!source.exists()) 119 { 120 throw new SourceNotFoundException("Source '" + fileMetadatas.getSourceUrl() + "' not found"); 121 } 122 123 long sourceLastModified = (source.getLastModified() / 1000) * 1000; // hack for file systems only returning seconds 124 if (forceRead || sourceLastModified >= fileMetadatas.getLastFileReading()) 125 { 126 getLogger().debug("Reading configuration file at {}", fileMetadatas.getSourceUrl()); 127 128 long lastFileReading = Instant.now().truncatedTo(ChronoUnit.SECONDS).toEpochMilli(); 129 130 try (InputStream is = source.getInputStream()) 131 { 132 fileReloader.updateFile(fileMetadatas.getSourceUrl(), is); 133 fileMetadatas.setLastFileReading(lastFileReading); 134 getLogger().debug("Configuration file read. at {}", fileMetadatas.getSourceUrl()); 135 } 136 137 } 138 139 } 140 catch (SourceNotFoundException e) 141 { 142 getLogger().debug("File at {} could not be found", fileMetadatas.getSourceUrl(), e); 143 if (forceRead || fileMetadatas.getLastFileReading() < 0) 144 { 145 fileReloader.updateFile(fileMetadatas.getSourceUrl(), null); 146 } 147 fileMetadatas.setLastFileReading(0); // It was initialized with -1 so the update will be forced only once 148 } 149 finally 150 { 151 _resolver.release(source); 152 } 153 } 154 } 155 156 private static class FileMetadatas 157 { 158 private long _lastFileReading; 159 private String _sourceUrl; 160 private Map _parameters; 161 162 public FileMetadatas(String sourceUrl, Map parameters) 163 { 164 this._sourceUrl = sourceUrl; 165 this._lastFileReading = -1; 166 this._parameters = parameters; 167 } 168 169 public long getLastFileReading() 170 { 171 return this._lastFileReading; 172 } 173 public void setLastFileReading(long lastFileReading) 174 { 175 this._lastFileReading = lastFileReading; 176 } 177 178 public String getSourceUrl() 179 { 180 return this._sourceUrl; 181 } 182 183 public Map getParameters() 184 { 185 return this._parameters; 186 } 187 } 188 189}