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}