001 /** 002 * Copyright 2008-2012 The Kuali Foundation 003 * 004 * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php 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 */ 016 package org.codehaus.mojo.wagon.shared; 017 018 /* 019 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license 020 * agreements. See the NOTICE file distributed with this work for additional information regarding 021 * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the 022 * "License"); you may not use this file except in compliance with the License. You may obtain a 023 * copy of the License at 024 * 025 * http://www.apache.org/licenses/LICENSE-2.0 026 * 027 * Unless required by applicable law or agreed to in writing, software distributed under the License 028 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 029 * or implied. See the License for the specific language governing permissions and limitations under 030 * the License. 031 */ 032 033 import java.util.ArrayList; 034 import java.util.Collections; 035 import java.util.Iterator; 036 import java.util.List; 037 038 import org.apache.maven.plugin.logging.Log; 039 import org.apache.maven.wagon.Wagon; 040 import org.apache.maven.wagon.WagonException; 041 import org.codehaus.plexus.util.StringUtils; 042 043 public class WagonDirectoryScanner { 044 private final static String[] NOT_DIRECTORIES = new String[] { ".jar", ".zip", ".md5", ".sha1", ".pom", ".xml", 045 ".war" }; 046 /** 047 * Patterns which should be excluded by default. 048 * 049 * @see #addDefaultExcludes() 050 */ 051 public static final String[] DEFAULTEXCLUDES = org.codehaus.plexus.util.DirectoryScanner.DEFAULTEXCLUDES; 052 053 /** 054 * The wagon 055 */ 056 private Wagon wagon; 057 058 /** 059 * Relative to wagon url 060 */ 061 private String directory; 062 063 /** The patterns for the wagon files to be included. */ 064 private String[] includes; 065 066 /** The patterns for the wagon files to be excluded. */ 067 private String[] excludes; 068 069 /** 070 * Whether or not the file system should be treated as a case sensitive one. 071 */ 072 private boolean isCaseSensitive = true; 073 074 /** 075 * The files which matched at least one include and at least one exclude and relative to directory 076 */ 077 private List<String> filesIncluded = new ArrayList<String>(); 078 079 private Log logger; 080 081 /** 082 * Sets the list of include patterns to use. All '/' and '\' characters are replaced by 083 * <code>File.separatorChar</code>, so the separator used need not match <code>File.separatorChar</code>. 084 * <p> 085 * When a pattern ends with a '/' or '\', "**" is appended. 086 * 087 * @param includes 088 * A list of include patterns. May be <code>null</code>, indicating that all files should be included. If 089 * a non-<code>null</code> list is given, all elements must be non-<code>null</code>. 090 */ 091 public void setIncludes(String[] includes) { 092 if (includes == null) { 093 this.includes = null; 094 } else { 095 this.includes = new String[includes.length]; 096 for (int i = 0; i < includes.length; i++) { 097 String pattern = includes[i].trim(); 098 099 if (pattern.endsWith("/")) { 100 pattern += "**"; 101 } 102 this.includes[i] = pattern; 103 } 104 } 105 } 106 107 /** 108 * Sets the list of exclude patterns to use. All '\' characters are replaced by '/' 109 * <p> 110 * When a pattern ends with a '/' or '\', "**" is appended. 111 * 112 * @param excludes 113 * A list of exclude patterns. May be <code>null</code>, indicating that no files should be excluded. If 114 * a non-<code>null</code> list is given, all elements must be non-<code>null</code>. 115 */ 116 public void setExcludes(String[] excludes) { 117 if (excludes == null) { 118 this.excludes = null; 119 } else { 120 this.excludes = new String[excludes.length]; 121 for (int i = 0; i < excludes.length; i++) { 122 String pattern = excludes[i].trim(); 123 124 if (pattern.endsWith("/")) { 125 pattern += "**"; 126 } 127 this.excludes[i] = pattern; 128 } 129 } 130 } 131 132 /** 133 * Tests whether or not a name matches against at least one include pattern. 134 * 135 * @param name 136 * The name to match. Must not be <code>null</code>. 137 * @return <code>true</code> when the name matches against at least one include pattern, or <code>false</code> 138 * otherwise. 139 */ 140 private boolean isIncluded(String name) { 141 logger.debug("includes.length=" + includes.length); 142 for (int i = 0; i < includes.length; i++) { 143 logger.debug("includes[" + i + "]=" + includes[i]); 144 if (matchPath(includes[i], name, isCaseSensitive)) { 145 return true; 146 } 147 } 148 return false; 149 } 150 151 /** 152 * Tests whether or not a name matches against at least one exclude pattern. 153 * 154 * @param name 155 * The name to match. Must not be <code>null</code>. 156 * @return <code>true</code> when the name matches against at least one exclude pattern, or <code>false</code> 157 * otherwise. 158 */ 159 protected boolean isExcluded(String name) { 160 for (int i = 0; i < excludes.length; i++) { 161 if (matchPath(excludes[i], name, isCaseSensitive)) { 162 return true; 163 } 164 } 165 return false; 166 } 167 168 /** 169 * Tests whether or not a name matches the start of at least one include pattern. 170 * 171 * @param name 172 * The name to match. Must not be <code>null</code>. 173 * @return <code>true</code> when the name matches against the start of at least one include pattern, or 174 * <code>false</code> otherwise. 175 */ 176 protected boolean couldHoldIncluded(String name) { 177 for (int i = 0; i < includes.length; i++) { 178 if (matchPatternStart(includes[i], name, isCaseSensitive)) { 179 return true; 180 } 181 } 182 return false; 183 } 184 185 /** 186 * Tests whether or not a given path matches the start of a given pattern up to the first "**". 187 * <p> 188 * This is not a general purpose test and should only be used if you can live with false positives. For example, 189 * <code>pattern=**\a</code> and <code>str=b</code> will yield <code>true</code>. 190 * 191 * @param pattern 192 * The pattern to match against. Must not be <code>null</code>. 193 * @param str 194 * The path to match, as a String. Must not be <code>null</code>. 195 * @param isCaseSensitive 196 * Whether or not matching should be performed case sensitively. 197 * 198 * @return whether or not a given path matches the start of a given pattern up to the first "**". 199 */ 200 protected static boolean matchPatternStart(String pattern, String str, boolean isCaseSensitive) { 201 return SelectorUtils.matchPatternStart(pattern, str, isCaseSensitive); 202 } 203 204 /** 205 * Tests whether or not a given path matches a given pattern. 206 * 207 * @param pattern 208 * The pattern to match against. Must not be <code>null</code>. 209 * @param str 210 * The path to match, as a String. Must not be <code>null</code>. 211 * @param isCaseSensitive 212 * Whether or not matching should be performed case sensitively. 213 * 214 * @return <code>true</code> if the pattern matches against the string, or <code>false</code> otherwise. 215 */ 216 private static boolean matchPath(String pattern, String str, boolean isCaseSensitive) { 217 return SelectorUtils.matchPath(pattern, str, isCaseSensitive); 218 } 219 220 public void scan() throws WagonException { 221 if (wagon == null) { 222 throw new IllegalStateException("No wagon set"); 223 } 224 225 if (StringUtils.isBlank(directory)) { 226 directory = ""; 227 } 228 229 // logger.info("directory=" + directory); 230 231 if (includes == null) { 232 // No includes supplied, so set it to 'matches all' 233 includes = new String[1]; 234 includes[0] = "**"; 235 } 236 237 if (excludes == null) { 238 excludes = new String[0]; 239 } 240 241 filesIncluded = new ArrayList<String>(); 242 243 scandir(directory); 244 245 Collections.sort(filesIncluded); 246 247 } 248 249 /** 250 * Adds default exclusions to the current exclusions set. 251 */ 252 public void addDefaultExcludes() { 253 int excludesLength = excludes == null ? 0 : excludes.length; 254 String[] newExcludes; 255 newExcludes = new String[excludesLength + DEFAULTEXCLUDES.length]; 256 if (excludesLength > 0) { 257 System.arraycopy(excludes, 0, newExcludes, 0, excludesLength); 258 } 259 for (int i = 0; i < DEFAULTEXCLUDES.length; i++) { 260 newExcludes[i + excludesLength] = DEFAULTEXCLUDES[i]; 261 } 262 excludes = newExcludes; 263 } 264 265 /** 266 * Jenkins, if nothing else, will return pathnames with * characters in them that lead to infinite recursion down 267 * here. Given the impoverished API to the wagons, some ad-hoc filtration is called for. The filters in here are 268 * just culled from strange stuff we see from Jenkins. 269 * 270 * @param fileName 271 * supposed file name 272 * @return true if it seems like a bad idea. 273 */ 274 private boolean isRidiculousFile(String fileName) { 275 return fileName.endsWith(".") || fileName.contains("*") || fileName.startsWith("?") || fileName.startsWith("#"); 276 } 277 278 /** 279 * Scans the given directory for files and directories. Files are placed in a collection, based on the matching of 280 * includes, excludes, and the selectors. When a directory is found, it is scanned recursively. 281 * 282 * @throws WagonException 283 * 284 * @see #filesIncluded 285 */ 286 protected void scandir(String dir) throws WagonException { 287 // logger.info("dir: " + dir); 288 if (StringUtils.isBlank(dir)) { 289 logger.info("Scanning '" + dir + "'"); 290 } else { 291 logger.info("Scanning " + dir); 292 } 293 List<?> files = wagon.getFileList(dir); 294 // logger.info("files.size=" + files.size()); 295 296 for (Iterator<?> itr = files.iterator(); itr.hasNext();) { 297 String fileName = (String) itr.next(); 298 if (isRidiculousFile(fileName)) { 299 continue; 300 } 301 302 boolean directory = isDirectory(fileName); 303 boolean included = isIncluded(fileName); 304 boolean excluded = isExcluded(fileName); 305 boolean chi = directory && couldHoldIncluded(fileName); 306 boolean include = included && !excluded || chi && !excluded; 307 308 if (!include) { 309 logger.debug("Skipping " + fileName); 310 logger.debug("fileName=" + fileName + " included=" + included + " excluded=" + excluded + " chi=" + chi); 311 continue; 312 } 313 314 if (directory) { 315 scandir(fileName); 316 } else { 317 filesIncluded.add(fileName); 318 } 319 } 320 } 321 322 private boolean isDirectory(String existingRemotePath) throws WagonException { 323 for (int x = 0; x < NOT_DIRECTORIES.length; x++) { 324 if (existingRemotePath.endsWith(NOT_DIRECTORIES[x])) { 325 return false; 326 } 327 } 328 return existingRemotePath.endsWith("/"); 329 } 330 331 public List<String> getFilesIncluded() { 332 return filesIncluded; 333 } 334 335 public void setWagon(Wagon wagon) { 336 this.wagon = wagon; 337 } 338 339 public void setCaseSensitive(boolean isCaseSensitive) { 340 this.isCaseSensitive = isCaseSensitive; 341 } 342 343 public void setDirectory(String basePath) { 344 this.directory = basePath; 345 } 346 347 public Log getLogger() { 348 return logger; 349 } 350 351 public void setLogger(Log logger) { 352 this.logger = logger; 353 } 354 355 }