View Javadoc

1   /*
2    * Copyright 2006-2008 the original author or authors.
3    * 
4    * Licensed under the Apache 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.apache.org/licenses/LICENSE-2.0
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  
17  package org.springframework.osgi.test.internal.util.jar;
18  
19  import java.io.IOException;
20  import java.net.MalformedURLException;
21  import java.net.URL;
22  import java.util.Collection;
23  import java.util.Iterator;
24  import java.util.Map;
25  import java.util.Set;
26  import java.util.TreeMap;
27  import java.util.TreeSet;
28  import java.util.jar.Manifest;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.springframework.core.io.ByteArrayResource;
33  import org.springframework.core.io.DefaultResourceLoader;
34  import org.springframework.core.io.Resource;
35  import org.springframework.core.io.ResourceLoader;
36  import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
37  import org.springframework.core.io.support.ResourcePatternResolver;
38  import org.springframework.osgi.test.internal.util.jar.storage.MemoryStorage;
39  import org.springframework.osgi.test.internal.util.jar.storage.Storage;
40  import org.springframework.util.StringUtils;
41  
42  /**
43   * Helper class for creating Jar files. Note that this class is stateful and the
44   * same instance should not be reused.
45   * 
46   * @author Costin Leau
47   * 
48   */
49  public class JarCreator {
50  
51  	private static final Log log = LogFactory.getLog(JarCreator.class);
52  
53  	public static final String CLASS_PATTERN = "/**/*.class";
54  
55  	public static final String XML_PATTERN = "/**/*.xml";
56  
57  	public static final String PROPS_PATTERN = "/**/*.properties";
58  
59  	public static final String EVERYTHING_PATTERN = "/**/*";
60  
61  	private static final String[] LIMITED_PATTERN = new String[] { CLASS_PATTERN, XML_PATTERN, PROPS_PATTERN };
62  
63  	private static final String CLASS_EXT = ".class";
64  
65  	private static final String TEST_CLASSES_DIR = "test-classes";
66  
67  	private String[] contentPattern = new String[] { EVERYTHING_PATTERN };
68  
69  	private ResourcePatternResolver patternResolver = new PathMatchingResourcePatternResolver();
70  
71  	private Storage storage = new MemoryStorage();
72  
73  	private String rootPath = determineRootPath();
74  
75  	private boolean addFolders = true;
76  
77  	/** collection of packages contained by this jar */
78  	private Collection containedPackages = new TreeSet();
79  
80  
81  	/**
82  	 * Resources' root path (the root path does not become part of the jar).
83  	 * 
84  	 * @return the root path
85  	 */
86  	public String determineRootPath() {
87  		// load file using absolute path. This seems to be necessary in IntelliJ
88  		try {
89  			ResourceLoader fileLoader = new DefaultResourceLoader();
90  			Resource res = fileLoader.getResource(getClass().getName().replace('.', '/').concat(".class"));
91  			String fileLocation = "file://" + res.getFile().getAbsolutePath();
92  			fileLocation = fileLocation.substring(0, fileLocation.indexOf(TEST_CLASSES_DIR)) + TEST_CLASSES_DIR;
93  			if (res.exists()) {
94  				return fileLocation;
95  			}
96  		}
97  		catch (Exception e) {
98  		}
99  
100 		return "file:./target/" + TEST_CLASSES_DIR;
101 	}
102 
103 	/**
104 	 * Actual jar creation.
105 	 * 
106 	 * @param manifest to use
107 	 * @param content array of resource to include in the jar
108 	 * @return the number of bytes written to the underlying stream.
109 	 * 
110 	 * @throws IOException
111 	 */
112 	protected int addJarContent(Manifest manifest, Map entries) throws IOException {
113 		// load manifest
114 		// add it to the jar
115 		if (log.isTraceEnabled()) {
116 			if (manifest != null)
117 				log.trace("Adding MANIFEST.MF [" + manifest.getMainAttributes().entrySet() + "]");
118 			log.trace("Adding entries:");
119 			Set key = entries.keySet();
120 			for (Iterator iter = key.iterator(); iter.hasNext();) {
121 				log.trace(iter.next());
122 			}
123 		}
124 
125 		return JarUtils.createJar(manifest, entries, storage.getOutputStream());
126 	}
127 
128 	/**
129 	 * Create a jar using the current settings and return a {@link Resource}
130 	 * pointing to the jar.
131 	 * 
132 	 * @param manifest
133 	 */
134 	public Resource createJar(Manifest manifest) {
135 		return createJar(manifest, resolveContent());
136 	}
137 
138 	/**
139 	 * Create a jar using the current settings and return a {@link Resource}
140 	 * pointing to the jar.
141 	 * 
142 	 * @param manifest
143 	 */
144 	public Resource createJar(Manifest manifest, Map content) {
145 		try {
146 			addJarContent(manifest, content);
147 			return storage.getResource();
148 		}
149 		catch (IOException ex) {
150 			throw (RuntimeException) new IllegalStateException("Cannot create jar").initCause(ex);
151 		}
152 	}
153 
154 	/**
155 	 * Small utility method used for determining the file name by striping the
156 	 * root path from the file full path.
157 	 * 
158 	 * @param rootPath
159 	 * @param resource
160 	 * @return
161 	 */
162 	private String determineRelativeName(String rootPath, Resource resource) {
163 		try {
164 			String path = StringUtils.cleanPath(resource.getURL().toExternalForm());
165 			return path.substring(path.indexOf(rootPath) + rootPath.length());
166 		}
167 		catch (IOException ex) {
168 			throw (RuntimeException) new IllegalArgumentException("illegal resource " + resource.toString()).initCause(ex);
169 		}
170 	}
171 
172 	/**
173 	 * Transform the pattern and rootpath into actual resources.
174 	 * 
175 	 * @return
176 	 * @throws Exception
177 	 */
178 	private Resource[][] resolveResources() {
179 		ResourcePatternResolver resolver = getPatternResolver();
180 
181 		String[] patterns = getContentPattern();
182 		Resource[][] resources = new Resource[patterns.length][];
183 
184 		// transform Strings into Resources
185 		for (int i = 0; i < patterns.length; i++) {
186 			StringBuffer buffer = new StringBuffer(rootPath);
187 
188 			// do checking on lost slashes
189 			if (!rootPath.endsWith(JarUtils.SLASH) && !patterns[i].startsWith(JarUtils.SLASH))
190 				buffer.append(JarUtils.SLASH);
191 
192 			buffer.append(patterns[i]);
193 			try {
194 				resources[i] = resolver.getResources(buffer.toString());
195 			}
196 			catch (IOException ex) {
197 				IllegalStateException re = new IllegalStateException("cannot resolve pattern " + buffer.toString());
198 				re.initCause(ex);
199 				throw re;
200 			}
201 		}
202 
203 		return resources;
204 	}
205 
206 	/**
207 	 * Resolve the jar content based on its path. Will return a map containing
208 	 * the entries relative to the jar root path as keys and Spring Resource
209 	 * pointing to the actual resources as values. It will also determine the
210 	 * packages contained by this package.
211 	 * 
212 	 * @return
213 	 */
214 	public Map resolveContent() {
215 		Resource[][] resources = resolveResources();
216 
217 		URL rootURL;
218 		String rootP = getRootPath();
219 		try {
220 			rootURL = new URL(rootP);
221 		}
222 		catch (MalformedURLException ex) {
223 			throw (RuntimeException) new IllegalArgumentException("illegal root path given " + rootP).initCause(ex);
224 		}
225 		String rootPath = StringUtils.cleanPath(rootURL.getPath());
226 
227 		// remove duplicates
228 		Map entries = new TreeMap();
229 		// save contained bundle packages
230 		containedPackages.clear();
231 
232 		// empty stream used for folders
233 		Resource folderResource = new ByteArrayResource(new byte[0]);
234 
235 		// add folder entries also
236 		for (int i = 0; i < resources.length; i++) {
237 			for (int j = 0; j < resources[i].length; j++) {
238 				String relativeName = determineRelativeName(rootPath, resources[i][j]);
239 				// be consistent when adding resources to jar
240 				if (!relativeName.startsWith("/"))
241 					relativeName = "/" + relativeName;
242 				entries.put(relativeName, resources[i][j]);
243 
244 				// look for class entries
245 				if (relativeName.endsWith(CLASS_EXT)) {
246 
247 					// determine package (exclude first char)
248 					String clazzName = relativeName.substring(1, relativeName.length() - CLASS_EXT.length()).replace(
249 						'/', '.');
250 					// remove class name
251 					int index = clazzName.lastIndexOf('.');
252 					if (index > 0)
253 						clazzName = clazzName.substring(0, index);
254 					// add it to the collection
255 					containedPackages.add(clazzName);
256 				}
257 
258 				String token = relativeName;
259 				// get folder and walk up to the root
260 				if (addFolders) {
261 					// add META-INF
262 					entries.put("/META-INF/", folderResource);
263 					int slashIndex;
264 					// stop at root folder
265 					while ((slashIndex = token.lastIndexOf('/')) > 1) {
266 						// add the folder with trailing /
267 						entries.put(token.substring(0, slashIndex + 1), folderResource);
268 						// walk the tree
269 						token = token.substring(0, slashIndex);
270 					}
271 					// add root folder
272 					//entries.put("/", folderResource);
273 				}
274 			}
275 		}
276 
277 		if (log.isTraceEnabled())
278 			log.trace("The following packages were discovered in the bundle: " + containedPackages);
279 
280 		return entries;
281 	}
282 
283 	public Collection getContainedPackages() {
284 		return containedPackages;
285 	}
286 
287 	/**
288 	 * @return Returns the contentPattern.
289 	 */
290 	public String[] getContentPattern() {
291 		return contentPattern;
292 	}
293 
294 	/**
295 	 * Pattern for content matching. Note that using {@link #EVERYTHING_PATTERN}
296 	 * can become problematic on windows due to file system locking.
297 	 * 
298 	 * @param contentPattern The contentPattern to set.
299 	 */
300 	public void setContentPattern(String[] contentPattern) {
301 		this.contentPattern = contentPattern;
302 	}
303 
304 	/**
305 	 * @return Returns the patternResolver.
306 	 */
307 	public ResourcePatternResolver getPatternResolver() {
308 		return patternResolver;
309 	}
310 
311 	/**
312 	 * @param patternResolver The patternResolver to set.
313 	 */
314 	public void setPatternResolver(ResourcePatternResolver patternResolver) {
315 		this.patternResolver = patternResolver;
316 	}
317 
318 	/**
319 	 * @return Returns the jarStorage.
320 	 */
321 	public Storage getStorage() {
322 		return storage;
323 	}
324 
325 	/**
326 	 * @param jarStorage The jarStorage to set.
327 	 */
328 	public void setStorage(Storage jarStorage) {
329 		this.storage = jarStorage;
330 	}
331 
332 	/**
333 	 * @param
334 	 */
335 	public String getRootPath() {
336 		return rootPath;
337 	}
338 
339 	/**
340 	 * @param rootPath The rootPath to set.
341 	 */
342 	public void setRootPath(String rootPath) {
343 		this.rootPath = rootPath;
344 	}
345 
346 	/**
347 	 * @return Returns the addFolders.
348 	 */
349 	public boolean isAddFolders() {
350 		return addFolders;
351 	}
352 
353 	/**
354 	 * Whether the folders in which the files reside, should be added to the
355 	 * archive. Default is true since otherwise, the archive will contains only
356 	 * files and no folders.
357 	 * 
358 	 * @param addFolders The addFolders to set.
359 	 */
360 	public void setAddFolders(boolean addFolders) {
361 		this.addFolders = addFolders;
362 	}
363 }