1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
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
114
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
185 for (int i = 0; i < patterns.length; i++) {
186 StringBuffer buffer = new StringBuffer(rootPath);
187
188
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
228 Map entries = new TreeMap();
229
230 containedPackages.clear();
231
232
233 Resource folderResource = new ByteArrayResource(new byte[0]);
234
235
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
240 if (!relativeName.startsWith("/"))
241 relativeName = "/" + relativeName;
242 entries.put(relativeName, resources[i][j]);
243
244
245 if (relativeName.endsWith(CLASS_EXT)) {
246
247
248 String clazzName = relativeName.substring(1, relativeName.length() - CLASS_EXT.length()).replace(
249 '/', '.');
250
251 int index = clazzName.lastIndexOf('.');
252 if (index > 0)
253 clazzName = clazzName.substring(0, index);
254
255 containedPackages.add(clazzName);
256 }
257
258 String token = relativeName;
259
260 if (addFolders) {
261
262 entries.put("/META-INF/", folderResource);
263 int slashIndex;
264
265 while ((slashIndex = token.lastIndexOf('/')) > 1) {
266
267 entries.put(token.substring(0, slashIndex + 1), folderResource);
268
269 token = token.substring(0, slashIndex);
270 }
271
272
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 }