001/* 002 * Copyright 2011-2012 Stephen Connolly. 003 * 004 * Licensed under the Apache 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.apache.org/licenses/LICENSE-2.0 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 017package org.jszip.maven; 018 019import org.apache.maven.artifact.Artifact; 020import org.apache.maven.model.Contributor; 021import org.apache.maven.model.Developer; 022import org.apache.maven.model.License; 023import org.apache.maven.plugin.MojoExecutionException; 024import org.apache.maven.plugin.MojoFailureException; 025import org.apache.maven.plugins.annotations.Component; 026import org.apache.maven.plugins.annotations.LifecyclePhase; 027import org.apache.maven.plugins.annotations.Mojo; 028import org.apache.maven.plugins.annotations.Parameter; 029import org.apache.maven.project.MavenProject; 030import org.apache.maven.project.MavenProjectHelper; 031import org.apache.maven.shared.artifact.filter.collection.ArtifactFilterException; 032import org.apache.maven.shared.artifact.filter.collection.FilterArtifacts; 033import org.apache.maven.shared.artifact.filter.collection.ProjectTransitivityFilter; 034import org.apache.maven.shared.artifact.filter.collection.ScopeFilter; 035import org.apache.maven.shared.artifact.filter.collection.TypeFilter; 036import org.codehaus.jackson.map.ObjectMapper; 037import org.codehaus.jackson.map.SerializationConfig; 038import org.codehaus.plexus.archiver.zip.ZipArchiver; 039import org.codehaus.plexus.components.io.resources.AbstractPlexusIoResource; 040import org.codehaus.plexus.util.IOUtil; 041import org.codehaus.plexus.util.StringUtils; 042 043import java.io.ByteArrayInputStream; 044import java.io.ByteArrayOutputStream; 045import java.io.File; 046import java.io.IOException; 047import java.io.InputStream; 048import java.net.URL; 049import java.util.ArrayList; 050import java.util.LinkedHashMap; 051import java.util.List; 052import java.util.Map; 053import java.util.Properties; 054import java.util.Set; 055import java.util.TreeMap; 056 057/** 058 * Produces a JSZip formatted zip file. 059 */ 060@Mojo(name = "jszip", defaultPhase = LifecyclePhase.PACKAGE) 061public class JSZipMojo extends AbstractJSZipMojo { 062 063 /** 064 * Directory containing the classes. 065 */ 066 @Parameter(defaultValue = "src/main/js", required = true) 067 private File contentDirectory; 068 069 /** 070 * Directory containing the resources. 071 */ 072 @Parameter(defaultValue = "${project.build.outputDirectory}", required = true) 073 private File resourcesDirectory; 074 075 /** 076 * Directory containing the generated ZIP. 077 */ 078 @Parameter(defaultValue = "${project.build.directory}", required = true) 079 private File outputDirectory; 080 081 /** 082 * Name of the generated ZIP. 083 */ 084 @Parameter(property = "zip.finalName", defaultValue = "${project.build.finalName}", required = true) 085 private String finalName; 086 087 /** 088 * Classifier to add to the artifact generated. If given, the artifact will be an attachment instead. 089 */ 090 @Parameter 091 private String classifier; 092 093 /** 094 * The Jar archiver. 095 * 096 * @component role="org.codehaus.plexus.archiver.Archiver" roleHint="zip" 097 */ 098 @Component(role = org.codehaus.plexus.archiver.Archiver.class, hint = "zip") 099 private ZipArchiver zipArchiver; 100 101 /** 102 * Include or not empty directories 103 */ 104 @Parameter(property = "zip.includeEmptyDirs", defaultValue = "false") 105 private boolean includeEmptyDirs; 106 107 /** 108 * Whether creating the archive should be forced. 109 */ 110 @Parameter(property = "zip.forceCreation", defaultValue = "false") 111 private boolean forceCreation; 112 113 /** 114 * Adding pom.xml and pom.properties to the archive. 115 */ 116 @Parameter(property = "zip.addMavenDescriptor", defaultValue = "true") 117 private boolean addMavenDescriptor; 118 119 /** 120 * Maven ProjectHelper. 121 */ 122 @Component 123 private MavenProjectHelper projectHelper; 124 125 public File getContentDirectory() { 126 return contentDirectory; 127 } 128 129 public File getResourcesDirectory() { 130 return resourcesDirectory; 131 } 132 133 protected File getZipFile(File basedir, String finalName, String classifier) { 134 if (classifier == null) { 135 classifier = ""; 136 } else if (classifier.trim().length() > 0 && !classifier.startsWith("-")) { 137 classifier = "-" + classifier; 138 } 139 140 return new File(basedir, finalName + classifier + ".zip"); 141 } 142 143 /** 144 * @see org.apache.maven.plugin.Mojo#execute() 145 */ 146 public void execute() 147 throws MojoExecutionException, MojoFailureException { 148 try { 149 150 File zipFile = getZipFile(outputDirectory, finalName, classifier); 151 152 zipArchiver.setDestFile(zipFile); 153 zipArchiver.setIncludeEmptyDirs(includeEmptyDirs); 154 zipArchiver.setCompress(true); 155 zipArchiver.setForced(forceCreation); 156 157 if (addMavenDescriptor) { 158 if (project.getArtifact().isSnapshot()) { 159 project.setVersion(project.getArtifact().getVersion()); 160 } 161 162 String groupId = project.getGroupId(); 163 164 String artifactId = project.getArtifactId(); 165 166 zipArchiver.addFile(project.getFile(), "META-INF/maven/" + groupId + "/" + artifactId + "/pom.xml"); 167 zipArchiver.addResource(new PomPropertiesResource(project), 168 "META-INF/maven/" + groupId + "/" + artifactId + "/pom.properties", 169 zipArchiver.getOverrideFileMode()); 170 } 171 zipArchiver.addResource(new PackageJsonResource(project), "package.json", 172 zipArchiver.getOverrideFileMode()); 173 if (contentDirectory.isDirectory()) { 174 zipArchiver.addDirectory(contentDirectory); 175 } 176 if (resourcesDirectory.isDirectory()) { 177 zipArchiver.addDirectory(resourcesDirectory); 178 } 179 zipArchiver.createArchive(); 180 181 if (StringUtils.isEmpty(classifier)) { 182 project.getArtifact().setFile(zipFile); 183 } else { 184 boolean found = false; 185 for (Artifact artifact : project.getAttachedArtifacts()) { 186 if (StringUtils.equals(artifact.getGroupId(), project.getGroupId()) 187 && StringUtils.equals(artifact.getArtifactId(), project.getArtifactId()) 188 && StringUtils.equals(artifact.getVersion(), project.getVersion()) 189 && StringUtils.equals(artifact.getClassifier(), classifier) 190 && StringUtils.equals(artifact.getType(), JSZIP_TYPE)) { 191 artifact.setFile(zipFile); 192 found = true; 193 } 194 } 195 if (!found) { 196 projectHelper.attachArtifact(project, JSZIP_TYPE, classifier, zipFile); 197 } 198 } 199 200 } catch (Exception e) { 201 throw new MojoExecutionException("Error assembling ZIP", e); 202 } 203 204 } 205 206 private static class PackageJsonResource extends AbstractPlexusIoResource { 207 private final byte[] bytes; 208 private final long lastModified; 209 210 public PackageJsonResource(MavenProject project) throws IOException, MojoExecutionException { 211 this.lastModified = project.getFile().lastModified(); 212 Map<String, Object> p = new TreeMap<String, Object>(); 213 p.put("name", project.getGroupId() + "." + project.getArtifactId()); 214 p.put("version", project.getVersion()); 215 addFirstNotEmpty(p, "description", project.getDescription()); 216 addFirstNotEmpty(p, "homepage", project.getUrl()); 217 if (project.getDevelopers() != null && !project.getDevelopers().isEmpty()) { 218 List<Object> devs = new ArrayList<Object>(); 219 for (Developer d : (List<Developer>) project.getDevelopers()) { 220 Map<String, Object> dev = new TreeMap<String, Object>(); 221 addFirstNotEmpty(dev, "name", d.getName(), d.getId(), d.getEmail()); 222 addFirstNotEmpty(dev, "email", d.getEmail()); 223 addFirstNotEmpty(dev, "web", d.getUrl()); 224 if (dev.containsKey("name")) { 225 devs.add(dev); 226 } 227 } 228 p.put("maintainers", devs); 229 } 230 if (project.getContributors() != null && !project.getContributors().isEmpty()) { 231 List<Object> contribs = new ArrayList<Object>(); 232 for (Contributor c : (List<Contributor>) project.getContributors()) { 233 Map<String, Object> contrib = new TreeMap<String, Object>(); 234 addFirstNotEmpty(contrib, "name", c.getName(), c.getEmail()); 235 addFirstNotEmpty(contrib, "email", c.getEmail()); 236 addFirstNotEmpty(contrib, "web", c.getUrl()); 237 if (contrib.containsKey("name")) { 238 contribs.add(contrib); 239 } 240 } 241 p.put("contributors", contribs); 242 } 243 if (project.getIssueManagement() != null) { 244 addFirstNotEmpty(p, "bugs", project.getIssueManagement().getUrl()); 245 } 246 if (project.getLicenses() != null && !project.getLicenses().isEmpty()) { 247 List<Object> licenses = new ArrayList<Object>(); 248 for (License l : (List<License>) project.getLicenses()) { 249 Map<String, Object> license = new TreeMap<String, Object>(); 250 addFirstNotEmpty(license, "type", l.getName()); 251 addFirstNotEmpty(license, "url", l.getUrl()); 252 licenses.add(license); 253 } 254 p.put("licenses", licenses); 255 256 } 257 FilterArtifacts filter = new FilterArtifacts(); 258 259 filter.addFilter(new ProjectTransitivityFilter(project.getDependencyArtifacts(), true)); 260 261 filter.addFilter(new ScopeFilter("runtime", "")); 262 263 filter.addFilter(new TypeFilter(JSZIP_TYPE, "")); 264 265 // start with all artifacts. 266 Set<Artifact> artifacts = project.getArtifacts(); 267 268 // perform filtering 269 try { 270 artifacts = filter.filter(artifacts); 271 } catch (ArtifactFilterException e) { 272 throw new MojoExecutionException(e.getMessage(), e); 273 } 274 275 Map<String, String> dependencies = new LinkedHashMap<String, String>(); 276 for (Artifact artifact : artifacts) { 277 dependencies.put(artifact.getGroupId() + "." + artifact.getArtifactId(), artifact.getVersion()); 278 } 279 p.put("dependencies", dependencies); 280 281 ByteArrayOutputStream os = new ByteArrayOutputStream(); 282 try { 283 ObjectMapper m = new ObjectMapper(); 284 m.setSerializationConfig(m.getSerializationConfig().with(SerializationConfig.Feature.INDENT_OUTPUT)); 285 m.writeValue(os, p); 286 } finally { 287 IOUtil.close(os); 288 } 289 bytes = os.toByteArray(); 290 291 } 292 293 private void addFirstNotEmpty(Map<String, Object> map, String name, String... values) { 294 for (String value : values) { 295 if (StringUtils.isNotEmpty(value)) { 296 map.put(name, value); 297 return; 298 } 299 } 300 } 301 302 @Override 303 public boolean isFile() { 304 return true; 305 } 306 307 @Override 308 public boolean isExisting() { 309 return true; 310 } 311 312 @Override 313 public long getLastModified() { 314 return lastModified; 315 } 316 317 @Override 318 public long getSize() { 319 return bytes.length; 320 } 321 322 public URL getURL() throws IOException { 323 return null; 324 } 325 326 public InputStream getContents() throws IOException { 327 return new ByteArrayInputStream(bytes); 328 } 329 } 330 331 private static class PomPropertiesResource extends AbstractPlexusIoResource { 332 private static final String GENERATED_BY_MAVEN = "Generated by Maven"; 333 private final byte[] bytes; 334 private final MavenProject project; 335 336 public PomPropertiesResource(MavenProject project) throws IOException { 337 this.project = project; 338 339 Properties p = new Properties(); 340 341 p.setProperty("groupId", project.getGroupId()); 342 343 p.setProperty("artifactId", project.getArtifactId()); 344 345 p.setProperty("version", project.getVersion()); 346 347 ByteArrayOutputStream os = new ByteArrayOutputStream(); 348 try { 349 p.store(os, GENERATED_BY_MAVEN); 350 } finally { 351 IOUtil.close(os); 352 } 353 354 bytes = os.toByteArray(); 355 } 356 357 @Override 358 public boolean isFile() { 359 return true; 360 } 361 362 @Override 363 public boolean isExisting() { 364 return true; 365 } 366 367 @Override 368 public long getLastModified() { 369 return project.getFile().lastModified(); 370 } 371 372 @Override 373 public long getSize() { 374 return bytes.length; 375 } 376 377 public URL getURL() throws IOException { 378 return null; 379 } 380 381 public InputStream getContents() throws IOException { 382 return new ByteArrayInputStream(bytes); 383 } 384 } 385}