1 /**
2 * Copyright 2008-2012 The Kuali Foundation
3 *
4 * Licensed under the Educational Community License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.opensource.org/licenses/ecl2.php
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package org.codehaus.mojo.wagon.shared;
17
18 /*
19 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
20 * agreements. See the NOTICE file distributed with this work for additional information regarding
21 * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
22 * "License"); you may not use this file except in compliance with the License. You may obtain a
23 * copy of the License at
24 *
25 * http://www.apache.org/licenses/LICENSE-2.0
26 *
27 * Unless required by applicable law or agreed to in writing, software distributed under the License
28 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
29 * or implied. See the License for the specific language governing permissions and limitations under
30 * the License.
31 */
32
33 import java.util.ArrayList;
34 import java.util.Collections;
35 import java.util.Iterator;
36 import java.util.List;
37
38 import org.apache.maven.plugin.logging.Log;
39 import org.apache.maven.wagon.Wagon;
40 import org.apache.maven.wagon.WagonException;
41 import org.codehaus.plexus.util.StringUtils;
42
43 public class WagonDirectoryScanner {
44 private final static String[] NOT_DIRECTORIES = new String[] { ".jar", ".zip", ".md5", ".sha1", ".pom", ".xml",
45 ".war" };
46 /**
47 * Patterns which should be excluded by default.
48 *
49 * @see #addDefaultExcludes()
50 */
51 public static final String[] DEFAULTEXCLUDES = org.codehaus.plexus.util.DirectoryScanner.DEFAULTEXCLUDES;
52
53 /**
54 * The wagon
55 */
56 private Wagon wagon;
57
58 /**
59 * Relative to wagon url
60 */
61 private String directory;
62
63 /** The patterns for the wagon files to be included. */
64 private String[] includes;
65
66 /** The patterns for the wagon files to be excluded. */
67 private String[] excludes;
68
69 /**
70 * Whether or not the file system should be treated as a case sensitive one.
71 */
72 private boolean isCaseSensitive = true;
73
74 /**
75 * The files which matched at least one include and at least one exclude and relative to directory
76 */
77 private List<String> filesIncluded = new ArrayList<String>();
78
79 private Log logger;
80
81 /**
82 * Sets the list of include patterns to use. All '/' and '\' characters are replaced by
83 * <code>File.separatorChar</code>, so the separator used need not match <code>File.separatorChar</code>.
84 * <p>
85 * When a pattern ends with a '/' or '\', "**" is appended.
86 *
87 * @param includes
88 * A list of include patterns. May be <code>null</code>, indicating that all files should be included. If
89 * a non-<code>null</code> list is given, all elements must be non-<code>null</code>.
90 */
91 public void setIncludes(String[] includes) {
92 if (includes == null) {
93 this.includes = null;
94 } else {
95 this.includes = new String[includes.length];
96 for (int i = 0; i < includes.length; i++) {
97 String pattern = includes[i].trim();
98
99 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 }