001/** 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved. 004 * 005 * This program and the accompanying materials are dual-licensed under 006 * either the terms of the Eclipse Public License v1.0 as published by 007 * the Eclipse Foundation 008 * 009 * or (per the licensee's choosing) 010 * 011 * under the terms of the GNU Lesser General Public License version 2.1 012 * as published by the Free Software Foundation. 013 */ 014package ch.qos.logback.core.joran.spi; 015 016import ch.qos.logback.core.spi.ContextAwareBase; 017import ch.qos.logback.core.util.MD5Util; 018 019import java.io.File; 020import java.net.HttpURLConnection; 021import java.net.URL; 022import java.net.URLDecoder; 023import java.security.NoSuchAlgorithmException; 024import java.util.ArrayList; 025import java.util.Arrays; 026import java.util.List; 027import java.util.stream.Collectors; 028 029import static ch.qos.logback.core.CoreConstants.PROPERTIES_FILE_EXTENSION; 030 031/** 032 * This class manages the list of files and/or urls that are watched for changes. 033 * 034 * @author Ceki Gülcü 035 */ 036public class ConfigurationWatchList extends ContextAwareBase { 037 038 public static final String HTTPS_PROTOCOL_STR = "https"; 039 public static final String HTTP_PROTOCOL_STR = "http"; 040 public static final String FILE_PROTOCOL_STR = "file"; 041 042 static final String[] WATCHABLE_PROTOCOLS = new String[] { FILE_PROTOCOL_STR, HTTPS_PROTOCOL_STR, HTTP_PROTOCOL_STR }; 043 044 static final byte[] BUF_ZERO = new byte[] { 0 }; 045 046 URL mainURL; 047 List<File> fileWatchList = new ArrayList<>(); 048 List<URL> urlWatchList = new ArrayList<>(); 049 List<byte[]> lastHashList = new ArrayList<>(); 050 051 List<Long> lastModifiedList = new ArrayList<>(); 052 053 public ConfigurationWatchList buildClone() { 054 ConfigurationWatchList out = new ConfigurationWatchList(); 055 out.mainURL = this.mainURL; 056 out.fileWatchList = new ArrayList<File>(this.fileWatchList); 057 out.lastModifiedList = new ArrayList<Long>(this.lastModifiedList); 058 out.lastHashList = new ArrayList<>(this.lastHashList); 059 return out; 060 } 061 062 public void clear() { 063 this.mainURL = null; 064 lastModifiedList.clear(); 065 fileWatchList.clear(); 066 urlWatchList.clear(); 067 lastHashList.clear(); 068 } 069 070 /** 071 * The mainURL for the configuration file. Null values are allowed. 072 * 073 * @param mainURL 074 */ 075 public void setMainURL(URL mainURL) { 076 // main url can be null 077 this.mainURL = mainURL; 078 if (mainURL != null) 079 addAsFileToWatch(mainURL); 080 } 081 082 public boolean watchPredicateFulfilled() { 083 if (hasMainURLAndNonEmptyFileList()) { 084 return true; 085 } 086 087 if(urlListContainsProperties()) { 088 return true; 089 } 090 091 return fileWatchListContainsProperties(); 092 093 } 094 095 private boolean urlListContainsProperties() { 096 return urlWatchList.stream().anyMatch(url -> url.toString().endsWith(PROPERTIES_FILE_EXTENSION)); 097 } 098 099 private boolean hasMainURLAndNonEmptyFileList() { 100 return mainURL != null && !fileWatchList.isEmpty(); 101 } 102 103 private boolean fileWatchListContainsProperties() { 104 return fileWatchList.stream().anyMatch(file -> file.getName().endsWith(PROPERTIES_FILE_EXTENSION)); 105 106 } 107 108 private void addAsFileToWatch(URL url) { 109 File file = convertToFile(url); 110 if (file != null) { 111 fileWatchList.add(file); 112 lastModifiedList.add(file.lastModified()); 113 } 114 } 115 116 117 private boolean isHTTP_Or_HTTPS(URL url) { 118 String protocolStr = url.getProtocol(); 119 return isHTTP_Or_HTTPS(protocolStr); 120 } 121 122 private boolean isHTTP_Or_HTTPS(String protocolStr) { 123 return (protocolStr.equals(HTTP_PROTOCOL_STR) || protocolStr.equals(HTTPS_PROTOCOL_STR)); 124 } 125 126 private void addAsHTTP_or_HTTPS_URLToWatch(URL url) { 127 if(isHTTP_Or_HTTPS(url)) { 128 urlWatchList.add(url); 129 lastHashList.add(BUF_ZERO); 130 } 131 } 132 133 /** 134 * Add the url but only if it is file:// or http(s):// 135 * @param url should be a file or http(s) 136 */ 137 public void addToWatchList(URL url) { 138 // assume that the caller has checked that the protocol is one of {file, https, http}. 139 String protocolStr = url.getProtocol(); 140 if (protocolStr.equals(FILE_PROTOCOL_STR)) { 141 addAsFileToWatch(url); 142 } else if (isHTTP_Or_HTTPS(protocolStr)) { 143 addAsHTTP_or_HTTPS_URLToWatch(url); 144 } 145 } 146 147 public URL getMainURL() { 148 return mainURL; 149 } 150 151 public List<File> getCopyOfFileWatchList() { 152 return new ArrayList<File>(fileWatchList); 153 } 154 155 156 public boolean emptyWatchLists() { 157 if(fileWatchList != null && !fileWatchList.isEmpty()) { 158 return false; 159 } 160 161 if(urlWatchList != null && !urlWatchList.isEmpty()) { 162 return false; 163 } 164 return true; 165 } 166 167 168 /** 169 * 170 * @deprecated replaced by {@link #changeDetectedInFile()} 171 */ 172 public File changeDetected() { 173 return changeDetectedInFile(); 174 } 175 176 /** 177 * Has a changed been detected in one of the files being watched? 178 * @return 179 */ 180 public File changeDetectedInFile() { 181 int len = fileWatchList.size(); 182 183 for (int i = 0; i < len; i++) { 184 long lastModified = lastModifiedList.get(i); 185 File file = fileWatchList.get(i); 186 long actualModificationDate = file.lastModified(); 187 188 if (lastModified != actualModificationDate) { 189 // update modification date in case this instance is reused 190 lastModifiedList.set(i, actualModificationDate); 191 return file; 192 } 193 } 194 return null; 195 } 196 197 public URL changeDetectedInURL() { 198 int len = urlWatchList.size(); 199 200 for (int i = 0; i < len; i++) { 201 byte[] lastHash = this.lastHashList.get(i); 202 URL url = urlWatchList.get(i); 203 204 HttpUtil httpGetUtil = new HttpUtil(HttpUtil.RequestMethod.GET, url); 205 HttpURLConnection getConnection = httpGetUtil.connectTextTxt(); 206 String response = httpGetUtil.readResponse(getConnection); 207 208 byte[] hash = computeHash(response); 209 if (lastHash == BUF_ZERO) { 210 this.lastHashList.set(i, hash); 211 return null; 212 } 213 214 if (Arrays.equals(lastHash, hash)) { 215 return null; 216 } else { 217 this.lastHashList.set(i, hash); 218 return url; 219 } 220 } 221 return null; 222 } 223 224 private byte[] computeHash(String response) { 225 if (response == null || response.trim().length() == 0) { 226 return null; 227 } 228 229 try { 230 MD5Util md5Util = new MD5Util(); 231 byte[] hashBytes = md5Util.md5Hash(response); 232 return hashBytes; 233 } catch (NoSuchAlgorithmException e) { 234 addError("missing MD5 algorithm", e); 235 return null; 236 } 237 } 238 239 @SuppressWarnings("deprecation") 240 File convertToFile(URL url) { 241 String protocol = url.getProtocol(); 242 if ("file".equals(protocol)) { 243 return new File(URLDecoder.decode(url.getFile())); 244 } else { 245 addInfo("URL [" + url + "] is not of type file"); 246 return null; 247 } 248 } 249 250 /** 251 * Returns true if there are watchable files, false otherwise. 252 * @return true if there are watchable files, false otherwise. 253 * @since 1.5.8 254 */ 255 public boolean hasAtLeastOneWatchableFile() { 256 return !fileWatchList.isEmpty(); 257 } 258 259 /** 260 * Is protocol for the given URL a protocol that we can watch for. 261 * 262 * @param url 263 * @return true if watchable, false otherwise 264 * @since 1.5.9 265 */ 266 static public boolean isWatchableProtocol(URL url) { 267 if (url == null) { 268 return false; 269 } 270 String protocolStr = url.getProtocol(); 271 return isWatchableProtocol(protocolStr); 272 } 273 274 /** 275 * Is the given protocol a protocol that we can watch for. 276 * 277 * @param protocolStr 278 * @return true if watchable, false otherwise 279 * @since 1.5.9 280 */ 281 static public boolean isWatchableProtocol(String protocolStr) { 282 return Arrays.stream(WATCHABLE_PROTOCOLS).anyMatch(protocol -> protocol.equalsIgnoreCase(protocolStr)); 283 } 284 285 /** 286 * Returns the urlWatchList field as a String 287 * @return the urlWatchList field as a String 288 * @since 1.5.19 289 */ 290 public String getUrlWatchListAsStr() { 291 String urlWatchListStr = urlWatchList.stream().map(URL::toString).collect(Collectors.joining(", ")); 292 return urlWatchListStr; 293 } 294 295 /** 296 * Returns the fileWatchList field as a String 297 * @return the fileWatchList field as a String 298 * @since 1.5.19 299 */ 300 public String getFileWatchListAsStr() { 301 return fileWatchList.stream().map(File::getPath).collect(Collectors.joining(", ")); 302 } 303 304}