001    /*
002     * #%L
003     * License Maven Plugin
004     *
005     * $Id: AddThirdPartyMojo.java 14420 2011-08-11 10:00:24Z tchemit $
006     * $HeadURL: http://svn.codehaus.org/mojo/tags/license-maven-plugin-1.0/src/main/java/org/codehaus/mojo/license/AddThirdPartyMojo.java $
007     * %%
008     * Copyright (C) 2008 - 2011 CodeLutin, Codehaus, Tony Chemit
009     * %%
010     * This program is free software: you can redistribute it and/or modify
011     * it under the terms of the GNU Lesser General Public License as
012     * published by the Free Software Foundation, either version 3 of the
013     * License, or (at your option) any later version.
014     *
015     * This program is distributed in the hope that it will be useful,
016     * but WITHOUT ANY WARRANTY; without even the implied warranty of
017     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
018     * GNU General Lesser Public License for more details.
019     *
020     * You should have received a copy of the GNU General Lesser Public
021     * License along with this program.  If not, see
022     * <http://www.gnu.org/licenses/lgpl-3.0.html>.
023     * #L%
024     */
025    
026    package org.codehaus.mojo.license;
027    
028    import java.io.File;
029    import java.io.FileOutputStream;
030    import java.io.IOException;
031    import java.util.ArrayList;
032    import java.util.Collection;
033    import java.util.List;
034    import java.util.Map;
035    import java.util.SortedMap;
036    import java.util.SortedSet;
037    
038    import org.apache.commons.collections.CollectionUtils;
039    import org.apache.commons.collections.MapUtils;
040    import org.apache.maven.artifact.repository.ArtifactRepository;
041    import org.apache.maven.plugin.MojoFailureException;
042    import org.apache.maven.plugin.logging.Log;
043    import org.apache.maven.project.MavenProject;
044    import org.apache.maven.project.ProjectBuildingException;
045    import org.codehaus.mojo.license.model.LicenseMap;
046    
047    /**
048     * Goal to generate the third-party license file.
049     * <p/>
050     * This file contains a list of the dependencies and their licenses. Each dependency and it's license is displayed on a
051     * single line in the format <br/>
052     *
053     * <pre>
054     *   (&lt;license-name&gt;) &lt;project-name&gt; &lt;groupId&gt;:&lt;artifactId&gt;:&lt;version&gt; - &lt;project-url&gt;
055     * </pre>
056     *
057     * It will also copy it in the class-path (says add the generated directory as a resource of the build).
058     *
059     * @author tchemit <chemit@codelutin.com>
060     * @goal add-third-party
061     * @phase generate-resources
062     * @requiresDependencyResolution test
063     * @requiresProject true
064     * @since 1.0
065     */
066    public class AddThirdPartyMojo extends AbstractAddThirdPartyMojo implements MavenProjectDependenciesConfigurator {
067    
068        /**
069         * Local Repository.
070         *
071         * @parameter expression="${localRepository}"
072         * @required
073         * @readonly
074         * @since 1.0.0
075         */
076        protected ArtifactRepository localRepository;
077    
078        /**
079         * Remote repositories used for the project.
080         *
081         * @parameter expression="${project.remoteArtifactRepositories}"
082         * @required
083         * @readonly
084         * @since 1.0.0
085         */
086        protected List remoteRepositories;
087    
088        /**
089         * Deploy the third party missing file in maven repository.
090         *
091         * @parameter expression="${license.deployMissingFile}" default-value="true"
092         * @since 1.0
093         */
094        protected boolean deployMissingFile;
095    
096        /**
097         * Load from repositories third party missing files.
098         *
099         * @parameter expression="${license.useRepositoryMissingFiles}" default-value="true"
100         * @since 1.0
101         */
102        protected boolean useRepositoryMissingFiles;
103    
104        /**
105         * dependencies tool.
106         *
107         * @component
108         * @readonly
109         * @since 1.0
110         */
111        private DependenciesTool dependenciesTool;
112    
113        /**
114         * Controls if THIRD-PARTY.properties gets created or not
115         *
116         * @parameter expression="${license.doGenerateMissing}" default-value="false"
117         */
118        private boolean doGenerateMissing;
119    
120        @Override
121        protected boolean checkPackaging() {
122            return rejectPackaging("pom");
123        }
124    
125        @Override
126        protected SortedMap<String, MavenProject> loadDependencies() {
127            return dependenciesTool.loadProjectDependencies(getProject(), this, localRepository, remoteRepositories,
128                    getArtifactCache());
129        }
130    
131        @Override
132        protected SortedProperties createUnsafeMapping() throws ProjectBuildingException, IOException,
133                ThirdPartyToolException {
134    
135            SortedProperties unsafeMappings = getThridPartyTool().loadUnsafeMapping(getLicenseMap(), getArtifactCache(),
136                    getEncoding(), getMissingFile());
137    
138            SortedSet<MavenProject> unsafeDependencies = getUnsafeDependencies();
139    
140            if (CollectionUtils.isNotEmpty(unsafeDependencies)) {
141    
142                // there is some unresolved license
143    
144                if (isUseRepositoryMissingFiles()) {
145    
146                    // try to load missing third party files from dependencies
147    
148                    Collection<MavenProject> projects = new ArrayList<MavenProject>(getProjectDependencies().values());
149                    projects.remove(getProject());
150                    projects.removeAll(unsafeDependencies);
151    
152                    SortedProperties resolvedUnsafeMapping = getThridPartyTool().loadThirdPartyDescriptorsForUnsafeMapping(
153                            getEncoding(), projects, unsafeDependencies, getLicenseMap(), localRepository,
154                            remoteRepositories);
155    
156                    // push back resolved unsafe mappings
157                    unsafeMappings.putAll(resolvedUnsafeMapping);
158    
159                }
160            }
161            if (isVerbose()) {
162                getLog().info("found " + unsafeMappings.size() + " unsafe mappings");
163            }
164    
165            // compute if missing file should be (re)-generate
166            boolean generateMissingfile = doGenerateMissing
167                    && computeDoGenerateMissingFile(unsafeMappings, unsafeDependencies);
168    
169            setDoGenerateMissing(generateMissingfile);
170    
171            if (generateMissingfile && isVerbose()) {
172                StringBuilder sb = new StringBuilder();
173                sb.append("Will use from missing file ");
174                sb.append(unsafeMappings.size());
175                sb.append(" dependencies :");
176                for (Map.Entry<Object, Object> entry : unsafeMappings.entrySet()) {
177                    String id = (String) entry.getKey();
178                    String license = (String) entry.getValue();
179                    sb.append("\n - ").append(id).append(" - ").append(license);
180                }
181                getLog().info(sb.toString());
182            } else {
183                if (isUseMissingFile() && !unsafeMappings.isEmpty()) {
184                    getLog().debug("Missing file " + getMissingFile() + " is up-to-date.");
185                }
186            }
187            return unsafeMappings;
188        }
189    
190        /**
191         * @param unsafeMappings
192         *            the unsafe mapping coming from the missing file
193         * @param unsafeDependencies
194         *            the unsafe dependencies from the project
195         * @return {@code true} if missing ifle should be (re-)generated, {@code false} otherwise
196         * @throws IOException
197         *             if any IO problem
198         * @since 1.0
199         */
200        protected boolean computeDoGenerateMissingFile(SortedProperties unsafeMappings,
201                SortedSet<MavenProject> unsafeDependencies) throws IOException {
202    
203            if (!isUseMissingFile()) {
204    
205                // never use the missing file
206                return false;
207            }
208    
209            if (isForce()) {
210    
211                // the mapping for missing file is not empty, regenerate it
212                return !CollectionUtils.isEmpty(unsafeMappings.keySet());
213            }
214    
215            if (!CollectionUtils.isEmpty(unsafeDependencies)) {
216    
217                // there is some unsafe dependencies from the project, must
218                // regenerate missing file
219                return true;
220            }
221    
222            File missingFile = getMissingFile();
223    
224            if (!missingFile.exists()) {
225    
226                // the missing file does not exists, this happens when
227                // using remote missing file from dependencies
228                return true;
229            }
230    
231            // check if the missing file has changed
232            SortedProperties oldUnsafeMappings = new SortedProperties(getEncoding());
233            oldUnsafeMappings.load(missingFile);
234            return !unsafeMappings.equals(oldUnsafeMappings);
235        }
236    
237        @Override
238        protected boolean checkSkip() {
239            if (!isDoGenerate() && !isDoGenerateBundle() && !isDoGenerateMissing()) {
240    
241                getLog().info("All files are up to date, skip goal execution.");
242                return false;
243            }
244            return true;
245        }
246    
247        @Override
248        protected void doAction() throws Exception {
249            boolean unsafe = checkUnsafeDependencies();
250    
251            writeThirdPartyFile();
252    
253            if (isDoGenerateMissing()) {
254    
255                writeMissingFile();
256            }
257    
258            if (unsafe && isFailIfWarning()) {
259                throw new MojoFailureException("There is some dependencies with no license, please fill the file "
260                        + getMissingFile());
261            }
262    
263            if (!unsafe && isUseMissingFile() && MapUtils.isEmpty(getUnsafeMappings()) && getMissingFile().exists()) {
264    
265                // there is no missing dependencies, but still a missing file, delete it
266                getLog().debug("There is no dependency to put in missing file, delete it at " + getMissingFile());
267                FileUtil.deleteFile(getMissingFile());
268            }
269    
270            if (!unsafe && isDeployMissingFile() && MapUtils.isNotEmpty(getUnsafeMappings())) {
271    
272                // can deploy missing file
273                File file = getMissingFile();
274    
275                getLog().debug("Will deploy third party file from " + file);
276                getThridPartyTool().attachThirdPartyDescriptor(getProject(), file);
277            }
278    
279            addResourceDir(getOutputDirectory(), "**/*.txt");
280        }
281    
282        protected void writeMissingFile() throws IOException {
283    
284            Log log = getLog();
285            LicenseMap licenseMap = getLicenseMap();
286            File file = getMissingFile();
287    
288            FileUtil.createDirectoryIfNecessary(file.getParentFile());
289            log.info("Regenerate missing license file " + file);
290    
291            FileOutputStream writer = new FileOutputStream(file);
292            try {
293                StringBuilder sb = new StringBuilder(" Generated by " + getClass().getName());
294                List<String> licenses = new ArrayList<String>(licenseMap.keySet());
295                licenses.remove(LicenseMap.getUnknownLicenseMessage());
296                if (!licenses.isEmpty()) {
297                    sb.append("\n-------------------------------------------------------------------------------");
298                    sb.append("\n Already used licenses in project :");
299                    for (String license : licenses) {
300                        sb.append("\n - ").append(license);
301                    }
302                }
303                sb.append("\n-------------------------------------------------------------------------------");
304                sb.append("\n Please fill the missing licenses for dependencies :\n\n");
305                getUnsafeMappings().store(writer, sb.toString());
306            } finally {
307                writer.close();
308            }
309        }
310    
311        public boolean isDoGenerateMissing() {
312            return doGenerateMissing;
313        }
314    
315        public void setDoGenerateMissing(boolean doGenerateMissing) {
316            this.doGenerateMissing = doGenerateMissing;
317        }
318    
319        public ArtifactRepository getLocalRepository() {
320            return localRepository;
321        }
322    
323        public List getRemoteRepositories() {
324            return remoteRepositories;
325        }
326    
327        /**
328         * {@inheritDoc}
329         */
330        @Override
331        public boolean isIncludeTransitiveDependencies() {
332            return includeTransitiveDependencies;
333        }
334    
335        // /**
336        // * {@inheritDoc}
337        // */
338        // public List<String> getExcludedScopes()
339        // {
340        // return Arrays.asList( Artifact.SCOPE_SYSTEM );
341        // }
342    
343        public boolean isDeployMissingFile() {
344            return deployMissingFile;
345        }
346    
347        public boolean isUseRepositoryMissingFiles() {
348            return useRepositoryMissingFiles;
349        }
350    
351    }