View Javadoc
1   package org.argeo.slc.repo;
2   
3   import java.io.ByteArrayInputStream;
4   import java.io.ByteArrayOutputStream;
5   import java.io.File;
6   import java.io.FileWriter;
7   import java.io.IOException;
8   import java.io.Writer;
9   import java.text.DateFormat;
10  import java.text.SimpleDateFormat;
11  import java.util.Date;
12  import java.util.Iterator;
13  import java.util.jar.Attributes;
14  import java.util.jar.JarEntry;
15  import java.util.jar.JarOutputStream;
16  import java.util.jar.Manifest;
17  
18  import javax.jcr.Node;
19  import javax.jcr.RepositoryException;
20  import javax.jcr.Session;
21  
22  import org.apache.commons.io.FileUtils;
23  import org.apache.commons.io.IOUtils;
24  import org.argeo.jcr.JcrUtils;
25  import org.argeo.slc.CategorizedNameVersion;
26  import org.argeo.slc.NameVersion;
27  import org.argeo.slc.SlcException;
28  import org.eclipse.aether.artifact.Artifact;
29  import org.eclipse.aether.artifact.DefaultArtifact;
30  import org.osgi.framework.Constants;
31  
32  /**
33   * Creates a jar bundle from an ArgeoOsgiDistribution. This jar is then
34   * persisted and indexed in a java repository using the OSGI Factory.
35   * 
36   * It does the following <list>
37   * <li>Creates a Manifest</li>
38   * <li>Creates files indexes (csv, feature.xml ...)</li>
39   * <li>Populate the corresponding jar</li>
40   * <li>Save it in the repository</li>
41   * <li>Index the node and creates corresponding sha1 and md5 files</li> </list>
42   * 
43   */
44  public class ModularDistributionFactory implements Runnable {
45  
46  	private OsgiFactory osgiFactory;
47  	private Session javaSession;
48  	private ArgeoOsgiDistribution osgiDistribution;
49  	private String modularDistributionSeparator = ",";
50  	private String artifactBasePath = RepoConstants.DEFAULT_ARTIFACTS_BASE_PATH;
51  	private String artifactType = "jar";
52  
53  	// Constants
54  	private final static String CSV_FILE_NAME = "modularDistribution.csv";
55  	private final DateFormat snapshotTimestamp = new SimpleDateFormat("YYYYMMddhhmm");
56  
57  	// private final static String FEATURE_FILE_NAME = "feature.xml";
58  	// private static int BUFFER_SIZE = 10240;
59  
60  	/** Convenience constructor with minimal configuration */
61  	public ModularDistributionFactory(OsgiFactory osgiFactory, ArgeoOsgiDistribution osgiDistribution) {
62  		this.osgiFactory = osgiFactory;
63  		this.osgiDistribution = osgiDistribution;
64  	}
65  
66  	@Override
67  	public void run() {
68  		byte[] distFile = null;
69  		try {
70  			javaSession = osgiFactory.openJavaSession();
71  
72  			if (artifactType == "jar")
73  				distFile = generateJarFile();
74  			else if (artifactType == "pom")
75  				distFile = generatePomFile();
76  			else
77  				throw new SlcException("Unimplemented distribution artifact type: " + artifactType + " for "
78  						+ osgiDistribution.toString());
79  
80  			// Save in java repository
81  			Artifact osgiArtifact = new DefaultArtifact(osgiDistribution.getCategory(), osgiDistribution.getName(),
82  					artifactType, osgiDistribution.getVersion());
83  
84  			Node distNode = RepoUtils.copyBytesAsArtifact(javaSession.getNode(artifactBasePath), osgiArtifact,
85  					distFile);
86  
87  			// index
88  			osgiFactory.indexNode(distNode);
89  
90  			// We use a specific session. Save before closing
91  			javaSession.save();
92  		} catch (RepositoryException e) {
93  			throw new SlcException(
94  					"JCR error while persisting modular distribution in JCR " + osgiDistribution.toString(), e);
95  		} finally {
96  			JcrUtils.logoutQuietly(javaSession);
97  		}
98  	}
99  
100 	private byte[] generateJarFile() {
101 		ByteArrayOutputStream byteOut = null;
102 		JarOutputStream jarOut = null;
103 		try {
104 			byteOut = new ByteArrayOutputStream();
105 			jarOut = new JarOutputStream(byteOut, createManifest());
106 			// Create various indexes
107 			addToJar(createCsvDescriptor(), CSV_FILE_NAME, jarOut);
108 			jarOut.close();
109 			return byteOut.toByteArray();
110 		} catch (IOException e) {
111 			throw new SlcException("IO error while generating modular distribution " + osgiDistribution.toString(), e);
112 		} finally {
113 			IOUtils.closeQuietly(byteOut);
114 			IOUtils.closeQuietly(jarOut);
115 		}
116 	}
117 
118 	// private void indexDistribution(Node distNode) throws RepositoryException
119 	// {
120 	// distNode.addMixin(SlcTypes.SLC_MODULAR_DISTRIBUTION);
121 	// distNode.addMixin(SlcTypes.SLC_CATEGORIZED_NAME_VERSION);
122 	// distNode.setProperty(SlcNames.SLC_CATEGORY,
123 	// osgiDistribution.getCategory());
124 	// distNode.setProperty(SlcNames.SLC_NAME, osgiDistribution.getName());
125 	// distNode.setProperty(SlcNames.SLC_VERSION,
126 	// osgiDistribution.getVersion());
127 	//
128 	// if (distNode.hasNode(SlcNames.SLC_MODULES))
129 	// distNode.getNode(SlcNames.SLC_MODULES).remove();
130 	// Node modules = distNode.addNode(SlcNames.SLC_MODULES,
131 	// NodeType.NT_UNSTRUCTURED);
132 	//
133 	// for (Iterator<? extends NameVersion> it = osgiDistribution
134 	// .nameVersions(); it.hasNext();)
135 	// addModule(modules, it.next());
136 	// }
137 
138 	private Manifest createManifest() {
139 		Manifest manifest = new Manifest();
140 
141 		// TODO make this configurable
142 		manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
143 //		addManifestAttribute(manifest, Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT, "JavaSE-1.8");
144 		addManifestAttribute(manifest, Constants.BUNDLE_VENDOR, "Argeo");
145 		addManifestAttribute(manifest, Constants.BUNDLE_MANIFESTVERSION, "2");
146 //		addManifestAttribute(manifest, "Bundle-License", "http://www.apache.org/licenses/LICENSE-2.0.txt");
147 
148 		// TODO define a user friendly name
149 		addManifestAttribute(manifest, Constants.BUNDLE_NAME, osgiDistribution.getName());
150 
151 		// Categorized name version
152 		addManifestAttribute(manifest, RepoConstants.SLC_CATEGORY_ID, osgiDistribution.getCategory());
153 		addManifestAttribute(manifest, Constants.BUNDLE_SYMBOLICNAME, osgiDistribution.getName());
154 		String version = osgiDistribution.getVersion();
155 		if (version.endsWith("-SNAPSHOT")) {
156 			version = version.substring(0, version.length() - "-SNAPSHOT".length());
157 			version = version + ".SNAPSHOT-r" + snapshotTimestamp.format(new Date());
158 		}
159 		addManifestAttribute(manifest, Constants.BUNDLE_VERSION, version);
160 
161 		return manifest;
162 	}
163 
164 	private void addManifestAttribute(Manifest manifest, String name, String value) {
165 		manifest.getMainAttributes().put(new Attributes.Name(name), value);
166 	}
167 
168 	private byte[] createCsvDescriptor() {
169 		Writer writer = null;
170 		try {
171 			// FIXME remove use of tmp file.
172 			File tmpFile = File.createTempFile("modularDistribution", "csv");
173 			tmpFile.deleteOnExit();
174 			writer = new FileWriter(tmpFile);
175 			// Populate the file
176 			for (Iterator<? extends NameVersion> it = osgiDistribution.nameVersions(); it.hasNext();)
177 				writer.write(getCsvLine(it.next()));
178 			writer.flush();
179 			return FileUtils.readFileToByteArray(tmpFile);
180 		} catch (Exception e) {
181 			throw new SlcException("unable to create csv distribution file for " + osgiDistribution.toString(), e);
182 		} finally {
183 			IOUtils.closeQuietly(writer);
184 		}
185 	}
186 
187 	@SuppressWarnings("unused")
188 	private byte[] createFeatureDescriptor() {
189 		// Directly retrieved from Argeo maven plugin
190 		// Does not work due to the lack of org.codehaus.plexus/plexus-archiver
191 		// third party dependency
192 
193 		throw new SlcException("Unimplemented method");
194 
195 		// // protected void writeFeatureDescriptor() throws
196 		// MojoExecutionException {
197 		// File featureDesc = File.createTempFile("feature", "xml");
198 		// featureDesc.deleteOnExit();
199 		//
200 		// Writer writer = null;
201 		// try {
202 		// writer = new FileWriter(featureDesc);
203 		// PrettyPrintXMLWriter xmlWriter = new PrettyPrintXMLWriter(writer);
204 		// xmlWriter.startElement("feature");
205 		// xmlWriter.addAttribute("id", project.getArtifactId());
206 		// xmlWriter.addAttribute("label", project.getName());
207 		//
208 		// // Version
209 		// String projectVersion = project.getVersion();
210 		// int indexSnapshot = projectVersion.indexOf("-SNAPSHOT");
211 		// if (indexSnapshot > -1)
212 		// projectVersion = projectVersion.substring(0, indexSnapshot);
213 		// projectVersion = projectVersion + ".qualifier";
214 		//
215 		// // project.
216 		// xmlWriter.addAttribute("version", projectVersion);
217 		//
218 		// Organization organization = project.getOrganization();
219 		// if (organization != null && organization.getName() != null)
220 		// xmlWriter.addAttribute("provider-name", organization.getName());
221 		//
222 		// if (project.getDescription() != null || project.getUrl() != null) {
223 		// xmlWriter.startElement("description");
224 		// if (project.getUrl() != null)
225 		// xmlWriter.addAttribute("url", project.getUrl());
226 		// if (project.getDescription() != null)
227 		// xmlWriter.writeText(project.getDescription());
228 		// xmlWriter.endElement();// description
229 		// }
230 		//
231 		// if (feature != null && feature.getCopyright() != null
232 		// || (organization != null && organization.getUrl() != null)) {
233 		// xmlWriter.startElement("copyright");
234 		// if (organization != null && organization.getUrl() != null)
235 		// xmlWriter.addAttribute("url", organization.getUrl());
236 		// if (feature.getCopyright() != null)
237 		// xmlWriter.writeText(feature.getCopyright());
238 		// xmlWriter.endElement();// copyright
239 		// }
240 		//
241 		// if (feature != null && feature.getUpdateSite() != null) {
242 		// xmlWriter.startElement("url");
243 		// xmlWriter.startElement("update");
244 		// xmlWriter.addAttribute("url", feature.getUpdateSite());
245 		// xmlWriter.endElement();// update
246 		// xmlWriter.endElement();// url
247 		// }
248 		//
249 		// List licenses = project.getLicenses();
250 		// if (licenses.size() > 0) {
251 		// // take the first one
252 		// License license = (License) licenses.get(0);
253 		// xmlWriter.startElement("license");
254 		//
255 		// if (license.getUrl() != null)
256 		// xmlWriter.addAttribute("url", license.getUrl());
257 		// if (license.getComments() != null)
258 		// xmlWriter.writeText(license.getComments());
259 		// else if (license.getName() != null)
260 		// xmlWriter.writeText(license.getName());
261 		// xmlWriter.endElement();// license
262 		// }
263 		//
264 		// // deploymentRepository.pathOf(null);
265 		// if (jarDirectory == null) {
266 		// Set dependencies = mavenDependencyManager
267 		// .getTransitiveProjectDependencies(project, remoteRepos,
268 		// local);
269 		// // // protected void writeFeatureDescriptor() throws
270 		// MojoExecutionException {
271 		// File featureDesc = File.createTempFile("feature", "xml");
272 		// featureDesc.deleteOnExit();
273 		//
274 		// Writer writer = null;
275 		// try {
276 		// writer = new FileWriter(featureDesc);
277 		// PrettyPrintXMLWriter xmlWriter = new PrettyPrintXMLWriter(writer);
278 		// xmlWriter.startElement("feature");
279 		// xmlWriter.addAttribute("id", project.getArtifactId());
280 		// xmlWriter.addAttribute("label", project.getName());
281 		//
282 		// // Version
283 		// String projectVersion = project.getVersion();
284 		// int indexSnapshot = projectVersion.indexOf("-SNAPSHOT");
285 		// if (indexSnapshot > -1)
286 		// projectVersion = projectVersion.substring(0, indexSnapshot);
287 		// projectVersion = projectVersion + ".qualifier";
288 		//
289 		// // project.
290 		// xmlWriter.addAttribute("version", projectVersion);
291 		//
292 		// Organization organization = project.getOrganization();
293 		// if (organization != null && organization.getName() != null)
294 		// xmlWriter.addAttribute("provider-name", organization.getName());
295 		//
296 		// if (project.getDescription() != null || project.getUrl() != null) {
297 		// xmlWriter.startElement("description");
298 		// if (project.getUrl() != null)
299 		// xmlWriter.addAttribute("url", project.getUrl());
300 		// if (project.getDescription() != null)
301 		// xmlWriter.writeText(project.getDescription());
302 		// xmlWriter.endElement();// description
303 		// }
304 		//
305 		// if (feature != null && feature.getCopyright() != null
306 		// || (organization != null && organization.getUrl() != null)) {
307 		// xmlWriter.startElement("copyright");
308 		// if (organization != null && organization.getUrl() != null)
309 		// xmlWriter.addAttribute("url", organization.getUrl());
310 		// if (feature.getCopyright() != null)
311 		// xmlWriter.writeText(feature.getCopyright());
312 		// xmlWriter.endElement();// copyright
313 		// }
314 		//
315 		// if (feature != null && feature.getUpdateSite() != null) {
316 		// xmlWriter.startElement("url");
317 		// xmlWriter.startElement("update");
318 		// xmlWriter.addAttribute("url", feature.getUpdateSite());
319 		// xmlWriter.endElement();// update
320 		// xmlWriter.endElement();// url
321 		// }
322 		//
323 		// List licenses = project.getLicenses();
324 		// if (licenses.size() > 0) {
325 		// // take the first one
326 		// License license = (License) licenses.get(0);
327 		// xmlWriter.startElement("license");
328 		//
329 		// if (license.getUrl() != null)
330 		// xmlWriter.addAttribute("url", license.getUrl());
331 		// if (license.getComments() != null)
332 		// xmlWriter.writeText(license.getComments());
333 		// else if (license.getName() != null)
334 		// xmlWriter.writeText(license.getName());
335 		// xmlWriter.endElement();// license
336 		// }
337 		//
338 		// // deploymentRepository.pathOf(null);
339 		// if (jarDirectory == null) {
340 		// Set dependencies = mavenDependencyManager
341 		// .getTransitiveProjectDependencies(project, remoteRepos,
342 		// local);
343 		// for (Iterator it = dependencies.iterator(); it.hasNext();) {
344 		// Artifact artifact = (Artifact) it.next();
345 		// writeFeaturePlugin(xmlWriter, artifact.getFile());
346 		// }
347 		// } else {
348 		// // TODO: filter jars
349 		// File[] jars = jarDirectory.listFiles();
350 		// if (jars == null)
351 		// throw new MojoExecutionException("No jar found in "
352 		// + jarDirectory);
353 		// for (int i = 0; i < jars.length; i++) {
354 		// writeFeaturePlugin(xmlWriter, jars[i]);
355 		// }
356 		// }
357 		//
358 		// xmlWriter.endElement();// feature
359 		//
360 		// if (getLog().isDebugEnabled())
361 		// getLog().debug("Wrote Eclipse feature descriptor.");
362 		// } catch (Exception e) {
363 		// throw new MojoExecutionException("Cannot write feature descriptor",
364 		// e);
365 		// } finally {
366 		// IOUtil.close(writer);
367 		// }for (Iterator it = dependencies.iterator(); it.hasNext();) {
368 		// Artifact artifact = (Artifact) it.next();
369 		// writeFeaturePlugin(xmlWriter, artifact.getFile());
370 		// }
371 		// } else {
372 		// // TODO: filter jars
373 		// File[] jars = jarDirectory.listFiles();
374 		// if (jars == null)
375 		// throw new MojoExecutionException("No jar found in "
376 		// + jarDirectory);
377 		// for (int i = 0; i < jars.length; i++) {
378 		// writeFeaturePlugin(xmlWriter, jars[i]);
379 		// }
380 		// }
381 		//
382 		// xmlWriter.endElement();// feature
383 		//
384 		// if (getLog().isDebugEnabled())
385 		// getLog().debug("Wrote Eclipse feature descriptor.");
386 		// } catch (Exception e) {
387 		// throw new MojoExecutionException("Cannot write feature descriptor",
388 		// e);
389 		// } finally {
390 		// IOUtil.close(writer);
391 		// }
392 	}
393 
394 	/** Create an Aether like distribution artifact */
395 	private byte[] generatePomFile() {
396 		StringBuilder b = new StringBuilder();
397 		// XML header
398 		b.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
399 		b.append(
400 				"<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd\">\n");
401 		b.append("<modelVersion>4.0.0</modelVersion>");
402 
403 		// Artifact
404 		b.append("<groupId>").append(osgiDistribution.getCategory()).append("</groupId>\n");
405 		b.append("<artifactId>").append(osgiDistribution.getName()).append("</artifactId>\n");
406 		b.append("<version>").append(osgiDistribution.getVersion()).append("</version>\n");
407 		b.append("<packaging>pom</packaging>\n");
408 		// p.append("<name>").append("Bundle Name").append("</name>\n");
409 		// p.append("<description>").append("Bundle
410 		// Description").append("</description>\n");
411 
412 		// Dependencies
413 		b.append("<dependencies>\n");
414 		for (Iterator<? extends NameVersion> it = osgiDistribution.nameVersions(); it.hasNext();) {
415 			NameVersion nameVersion = it.next();
416 			if (!(nameVersion instanceof CategorizedNameVersion))
417 				throw new SlcException("Unsupported type " + nameVersion.getClass());
418 			CategorizedNameVersion nv = (CategorizedNameVersion) nameVersion;
419 			b.append(getDependencySnippet(nv, false));
420 		}
421 		b.append("</dependencies>\n");
422 
423 		// Dependency management
424 		b.append("<dependencyManagement>\n");
425 		b.append("<dependencies>\n");
426 
427 		for (Iterator<? extends NameVersion> it = osgiDistribution.nameVersions(); it.hasNext();)
428 			b.append(getDependencySnippet((CategorizedNameVersion) it.next(), true));
429 		b.append("</dependencies>\n");
430 		b.append("</dependencyManagement>\n");
431 
432 		b.append("</project>\n");
433 		return b.toString().getBytes();
434 	}
435 
436 	private String getDependencySnippet(CategorizedNameVersion cnv, boolean includeVersion) { // , String type, String
437 																								// scope
438 		StringBuilder b = new StringBuilder();
439 		b.append("<dependency>\n");
440 		b.append("\t<groupId>").append(cnv.getCategory()).append("</groupId>\n");
441 		b.append("\t<artifactId>").append(cnv.getName()).append("</artifactId>\n");
442 		if (includeVersion)
443 			b.append("\t<version>").append(cnv.getVersion()).append("</version>\n");
444 		// if (type!= null)
445 		// p.append("\t<type>").append(type).append("</type>\n");
446 		// if (type!= null)
447 		// p.append("\t<scope>").append(scope).append("</scope>\n");
448 		b.append("</dependency>\n");
449 		return b.toString();
450 	}
451 
452 	// Helpers
453 	private void addToJar(byte[] content, String name, JarOutputStream target) throws IOException {
454 		ByteArrayInputStream in = null;
455 		try {
456 			target.putNextEntry(new JarEntry(name));
457 			in = new ByteArrayInputStream(content);
458 			byte[] buffer = new byte[1024];
459 			while (true) {
460 				int count = in.read(buffer);
461 				if (count == -1)
462 					break;
463 				target.write(buffer, 0, count);
464 			}
465 			target.closeEntry();
466 		} finally {
467 			IOUtils.closeQuietly(in);
468 		}
469 	}
470 
471 	private String getCsvLine(NameVersion nameVersion) throws RepositoryException {
472 		if (!(nameVersion instanceof CategorizedNameVersion))
473 			throw new SlcException("Unsupported type " + nameVersion.getClass());
474 		CategorizedNameVersion cnv = (CategorizedNameVersion) nameVersion;
475 		StringBuilder builder = new StringBuilder();
476 
477 		builder.append(cnv.getName());
478 		builder.append(modularDistributionSeparator);
479 		builder.append(nameVersion.getVersion());
480 		builder.append(modularDistributionSeparator);
481 		builder.append(cnv.getCategory().replace('.', '/'));
482 		// MavenConventionsUtils.groupPath("", cnv.getCategory());
483 		builder.append('/');
484 		builder.append(cnv.getName());
485 		builder.append('/');
486 		builder.append(cnv.getVersion());
487 		builder.append('/');
488 		builder.append(cnv.getName());
489 		builder.append('-');
490 		builder.append(cnv.getVersion());
491 		builder.append('.');
492 		// TODO make this dynamic
493 		builder.append("jar");
494 		builder.append("\n");
495 
496 		return builder.toString();
497 	}
498 
499 	/** Enable dependency injection */
500 	public void setOsgiFactory(OsgiFactory osgiFactory) {
501 		this.osgiFactory = osgiFactory;
502 	}
503 
504 	public void setOsgiDistribution(ArgeoOsgiDistribution osgiDistribution) {
505 		this.osgiDistribution = osgiDistribution;
506 	}
507 
508 	public void setModularDistributionSeparator(String modularDistributionSeparator) {
509 		this.modularDistributionSeparator = modularDistributionSeparator;
510 	}
511 
512 	public void setArtifactBasePath(String artifactBasePath) {
513 		this.artifactBasePath = artifactBasePath;
514 	}
515 
516 	public void setArtifactType(String artifactType) {
517 		this.artifactType = artifactType;
518 	}
519 }