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 for (int i = 0; i < includes.length; i++) { 142 if (matchPath(includes[i], name, isCaseSensitive)) { 143 return true; 144 } 145 } 146 return false; 147 } 148 149 /** 150 * Tests whether or not a name matches against at least one exclude pattern. 151 * 152 * @param name 153 * The name to match. Must not be <code>null</code>. 154 * @return <code>true</code> when the name matches against at least one exclude pattern, or <code>false</code> 155 * otherwise. 156 */ 157 protected boolean isExcluded(String name) { 158 for (int i = 0; i < excludes.length; i++) { 159 if (matchPath(excludes[i], name, isCaseSensitive)) { 160 return true; 161 } 162 } 163 return false; 164 } 165 166 /** 167 * Tests whether or not a name matches the start of at least one include pattern. 168 * 169 * @param name 170 * The name to match. Must not be <code>null</code>. 171 * @return <code>true</code> when the name matches against the start of at least one include pattern, or 172 * <code>false</code> otherwise. 173 */ 174 protected boolean couldHoldIncluded(String name) { 175 for (int i = 0; i < includes.length; i++) { 176 if (matchPatternStart(includes[i], name, isCaseSensitive)) { 177 return true; 178 } 179 } 180 return false; 181 } 182 183 /** 184 * Tests whether or not a given path matches the start of a given pattern up to the first "**". 185 * <p> 186 * This is not a general purpose test and should only be used if you can live with false positives. For example, 187 * <code>pattern=**\a</code> and <code>str=b</code> will yield <code>true</code>. 188 * 189 * @param pattern 190 * The pattern to match against. Must not be <code>null</code>. 191 * @param str 192 * The path to match, as a String. Must not be <code>null</code>. 193 * @param isCaseSensitive 194 * Whether or not matching should be performed case sensitively. 195 * 196 * @return whether or not a given path matches the start of a given pattern up to the first "**". 197 */ 198 protected static boolean matchPatternStart(String pattern, String str, boolean isCaseSensitive) { 199 return SelectorUtils.matchPatternStart(pattern, str, isCaseSensitive); 200 } 201 202 /** 203 * Tests whether or not a given path matches a given pattern. 204 * 205 * @param pattern 206 * The pattern to match against. Must not be <code>null</code>. 207 * @param str 208 * The path to match, as a String. Must not be <code>null</code>. 209 * @param isCaseSensitive 210 * Whether or not matching should be performed case sensitively. 211 * 212 * @return <code>true</code> if the pattern matches against the string, or <code>false</code> otherwise. 213 */ 214 private static boolean matchPath(String pattern, String str, boolean isCaseSensitive) { 215 return SelectorUtils.matchPath(pattern, str, isCaseSensitive); 216 } 217 218 public void scan() throws WagonException { 219 if (wagon == null) { 220 throw new IllegalStateException("No wagon set"); 221 } 222 223 if (StringUtils.isBlank(directory)) { 224 directory = ""; 225 } 226 227 if (includes == null) { 228 // No includes supplied, so set it to 'matches all' 229 includes = new String[1]; 230 includes[0] = "**"; 231 } 232 233 if (excludes == null) { 234 excludes = new String[0]; 235 } 236 237 filesIncluded = new ArrayList<String>(); 238 239 scandir(directory, ""); 240 241 Collections.sort(filesIncluded); 242 243 } 244 245 /** 246 * Adds default exclusions to the current exclusions set. 247 */ 248 public void addDefaultExcludes() { 249 int excludesLength = excludes == null ? 0 : excludes.length; 250 String[] newExcludes; 251 newExcludes = new String[excludesLength + DEFAULTEXCLUDES.length]; 252 if (excludesLength > 0) { 253 System.arraycopy(excludes, 0, newExcludes, 0, excludesLength); 254 } 255 for (int i = 0; i < DEFAULTEXCLUDES.length; i++) { 256 newExcludes[i + excludesLength] = DEFAULTEXCLUDES[i]; 257 } 258 excludes = newExcludes; 259 } 260 261 /** 262 * Jenkins, if nothing else, will return pathnames with * characters in them that lead to infinite recursion down 263 * here. Given the impoverished API to the wagons, some ad-hoc filtration is called for. The filters in here are 264 * just culled from strange stuff we see from Jenkins. 265 * 266 * @param fileName 267 * supposed file name 268 * @return true if it seems like a bad idea. 269 */ 270 private boolean isRidiculousFile(String fileName) { 271 return fileName.endsWith(".") || fileName.contains("*") || fileName.startsWith("?") || fileName.startsWith("#"); 272 } 273 274 // ////////////////////////////////////////////////////////////////////////////////// 275 /** 276 * Scans the given directory for files and directories. Found files are placed in a collection, based on the 277 * matching of includes, excludes, and the selectors. When a directory is found, it is scanned recursively. 278 * 279 * @throws WagonException 280 * 281 * @see #filesIncluded 282 */ 283 private void scandir(String dir, String vpath) throws WagonException { 284 logger.debug("scandir: dir: " + dir + " vpath: " + vpath); 285 List<?> files = wagon.getFileList(dir); 286 287 for (Iterator<?> iterator = files.iterator(); iterator.hasNext();) { 288 String fileName = (String) iterator.next(); 289 290 if (isRidiculousFile(fileName)) { 291 continue; 292 } 293 294 String file = fileName; 295 296 if (!StringUtils.isBlank(dir)) { 297 if (dir.endsWith("/")) { 298 file = dir + fileName; 299 } else { 300 file = dir + "/" + fileName; 301 } 302 } 303 304 String name = vpath + fileName; 305 306 if (this.isDirectory(file)) { 307 308 if (!name.endsWith("/")) { 309 name += "/"; 310 } 311 312 if (isIncluded(name)) { 313 if (!isExcluded(name)) { 314 scandir(file, name); 315 } else { 316 if (couldHoldIncluded(name)) { 317 scandir(file, name); 318 } 319 } 320 } else { 321 if (couldHoldIncluded(name)) { 322 scandir(file, name); 323 } 324 } 325 326 } else { 327 328 if (isIncluded(name)) { 329 if (!isExcluded(name)) { 330 filesIncluded.add(name); 331 } 332 } 333 } 334 } 335 } 336 337 private boolean isDirectory(String existedRemotePath) throws WagonException { 338 for (int x = 0; x < NOT_DIRECTORIES.length; x++) { 339 if (existedRemotePath.endsWith(NOT_DIRECTORIES[x])) { 340 return false; 341 } 342 } 343 if (existedRemotePath.endsWith("/")) { 344 return true; 345 } 346 347 return wagon.resourceExists(existedRemotePath + "/"); 348 } 349 350 // /////////////////////////////////////////////////////////////////////////////// 351 public List<String> getFilesIncluded() { 352 return filesIncluded; 353 } 354 355 public void setWagon(Wagon wagon) { 356 this.wagon = wagon; 357 } 358 359 public void setCaseSensitive(boolean isCaseSensitive) { 360 this.isCaseSensitive = isCaseSensitive; 361 } 362 363 public void setDirectory(String basePath) { 364 this.directory = basePath; 365 } 366 367 public Log getLogger() { 368 return logger; 369 } 370 371 public void setLogger(Log logger) { 372 this.logger = logger; 373 } 374 375 }