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