001    /**
002     * Copyright 2010-2012 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            return dependenciesTool.loadProjectDependencies(getProject(), this, localRepository, remoteRepositories,
118                    getArtifactCache());
119        }
120    
121        @Override
122        protected SortedProperties createUnsafeMapping() throws ProjectBuildingException, IOException,
123                ThirdPartyToolException {
124    
125            SortedProperties unsafeMappings = getThridPartyTool().loadUnsafeMapping(getLicenseMap(), getArtifactCache(),
126                    getEncoding(), getMissingFile());
127    
128            SortedSet<MavenProject> unsafeDependencies = getUnsafeDependencies();
129    
130            getLog().debug("1.0");
131            if (CollectionUtils.isNotEmpty(unsafeDependencies)) {
132    
133                // there is some unresolved license
134    
135                getLog().debug("2.0");
136                if (isUseRepositoryMissingFiles()) {
137    
138                    // try to load missing third party files from dependencies
139    
140                    Collection<MavenProject> projects = new ArrayList<MavenProject>(getProjectDependencies().values());
141                    projects.remove(getProject());
142                    projects.removeAll(unsafeDependencies);
143    
144                    getLog().debug("2.1");
145    
146                    SortedProperties resolvedUnsafeMapping = new SortedProperties("UTF-8");
147                    // The next few lines attempt to download groupid--artifactid--third-party.properties for every
148                    // dependency in the tree
149    
150                    // getThridPartyTool().loadThirdPartyDescriptorsForUnsafeMapping(
151                    // getEncoding(), projects, unsafeDependencies, getLicenseMap(), localRepository,
152                    // remoteRepositories);
153                    getLog().debug("2.2");
154    
155                    // push back resolved unsafe mappings
156                    unsafeMappings.putAll(resolvedUnsafeMapping);
157    
158                }
159            }
160            if (isVerbose()) {
161                getLog().info("found " + unsafeMappings.size() + " unsafe mappings");
162            }
163    
164            // compute if missing file should be (re)-generate
165            boolean generateMissingfile = doGenerateMissing
166                    && computeDoGenerateMissingFile(unsafeMappings, unsafeDependencies);
167    
168            setDoGenerateMissing(generateMissingfile);
169    
170            if (generateMissingfile && isVerbose()) {
171                StringBuilder sb = new StringBuilder();
172                sb.append("Will use from missing file ");
173                sb.append(unsafeMappings.size());
174                sb.append(" dependencies :");
175                for (Map.Entry<Object, Object> entry : unsafeMappings.entrySet()) {
176                    String id = (String) entry.getKey();
177                    String license = (String) entry.getValue();
178                    sb.append("\n - ").append(id).append(" - ").append(license);
179                }
180                getLog().info(sb.toString());
181            } else {
182                if (isUseMissingFile() && !unsafeMappings.isEmpty()) {
183                    getLog().debug("Missing file " + getMissingFile() + " is up-to-date.");
184                }
185            }
186            getLog().debug("4");
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    }