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