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            getLog().info("dependencies tool=" + dependenciesTool.getClass());
118    
119            return dependenciesTool.loadProjectDependencies(getProject(), this, localRepository, remoteRepositories,
120                    getArtifactCache());
121        }
122    
123        @Override
124        protected SortedProperties createUnsafeMapping() throws ProjectBuildingException, IOException,
125                ThirdPartyToolException {
126    
127            SortedProperties unsafeMappings = getThridPartyTool().loadUnsafeMapping(getLicenseMap(), getArtifactCache(),
128                    getEncoding(), getMissingFile());
129    
130            SortedSet<MavenProject> unsafeDependencies = getUnsafeDependencies();
131    
132            getLog().debug("1.0");
133            if (CollectionUtils.isNotEmpty(unsafeDependencies)) {
134    
135                // there is some unresolved license
136    
137                getLog().debug("2.0");
138                if (isUseRepositoryMissingFiles()) {
139    
140                    // try to load missing third party files from dependencies
141    
142                    Collection<MavenProject> projects = new ArrayList<MavenProject>(getProjectDependencies().values());
143                    projects.remove(getProject());
144                    projects.removeAll(unsafeDependencies);
145    
146                    getLog().debug("2.1");
147                    SortedProperties resolvedUnsafeMapping = new SortedProperties("UTF-8");// getThridPartyTool().loadThirdPartyDescriptorsForUnsafeMapping(
148                    // getEncoding(), projects, unsafeDependencies, getLicenseMap(), localRepository,
149                    // remoteRepositories);
150                    getLog().debug("2.2");
151    
152                    // push back resolved unsafe mappings
153                    unsafeMappings.putAll(resolvedUnsafeMapping);
154    
155                }
156            }
157            if (isVerbose()) {
158                getLog().info("found " + unsafeMappings.size() + " unsafe mappings");
159            }
160    
161            // compute if missing file should be (re)-generate
162            boolean generateMissingfile = doGenerateMissing
163                    && computeDoGenerateMissingFile(unsafeMappings, unsafeDependencies);
164    
165            setDoGenerateMissing(generateMissingfile);
166    
167            if (generateMissingfile && isVerbose()) {
168                StringBuilder sb = new StringBuilder();
169                sb.append("Will use from missing file ");
170                sb.append(unsafeMappings.size());
171                sb.append(" dependencies :");
172                for (Map.Entry<Object, Object> entry : unsafeMappings.entrySet()) {
173                    String id = (String) entry.getKey();
174                    String license = (String) entry.getValue();
175                    sb.append("\n - ").append(id).append(" - ").append(license);
176                }
177                getLog().info(sb.toString());
178            } else {
179                if (isUseMissingFile() && !unsafeMappings.isEmpty()) {
180                    getLog().debug("Missing file " + getMissingFile() + " is up-to-date.");
181                }
182            }
183            getLog().info("4");
184            return unsafeMappings;
185        }
186    
187        /**
188         * @param unsafeMappings
189         *            the unsafe mapping coming from the missing file
190         * @param unsafeDependencies
191         *            the unsafe dependencies from the project
192         * @return {@code true} if missing ifle should be (re-)generated, {@code false} otherwise
193         * @throws IOException
194         *             if any IO problem
195         * @since 1.0
196         */
197        protected boolean computeDoGenerateMissingFile(SortedProperties unsafeMappings,
198                SortedSet<MavenProject> unsafeDependencies) throws IOException {
199    
200            if (!isUseMissingFile()) {
201    
202                // never use the missing file
203                return false;
204            }
205    
206            if (isForce()) {
207    
208                // the mapping for missing file is not empty, regenerate it
209                return !CollectionUtils.isEmpty(unsafeMappings.keySet());
210            }
211    
212            if (!CollectionUtils.isEmpty(unsafeDependencies)) {
213    
214                // there is some unsafe dependencies from the project, must
215                // regenerate missing file
216                return true;
217            }
218    
219            File missingFile = getMissingFile();
220    
221            if (!missingFile.exists()) {
222    
223                // the missing file does not exists, this happens when
224                // using remote missing file from dependencies
225                return true;
226            }
227    
228            // check if the missing file has changed
229            SortedProperties oldUnsafeMappings = new SortedProperties(getEncoding());
230            oldUnsafeMappings.load(missingFile);
231            return !unsafeMappings.equals(oldUnsafeMappings);
232        }
233    
234        @Override
235        protected boolean checkSkip() {
236            if (!isDoGenerate() && !isDoGenerateBundle() && !isDoGenerateMissing()) {
237    
238                getLog().info("All files are up to date, skip goal execution.");
239                return false;
240            }
241            return true;
242        }
243    
244        @Override
245        protected void doAction() throws Exception {
246            boolean unsafe = checkUnsafeDependencies();
247    
248            writeThirdPartyFile();
249    
250            if (isDoGenerateMissing()) {
251    
252                writeMissingFile();
253            }
254    
255            if (unsafe && isFailIfWarning()) {
256                throw new MojoFailureException("There is some dependencies with no license, please fill the file "
257                        + getMissingFile());
258            }
259    
260            if (!unsafe && isUseMissingFile() && MapUtils.isEmpty(getUnsafeMappings()) && getMissingFile().exists()) {
261    
262                // there is no missing dependencies, but still a missing file, delete it
263                getLog().debug("There is no dependency to put in missing file, delete it at " + getMissingFile());
264                FileUtil.deleteFile(getMissingFile());
265            }
266    
267            if (!unsafe && isDeployMissingFile() && MapUtils.isNotEmpty(getUnsafeMappings())) {
268    
269                // can deploy missing file
270                File file = getMissingFile();
271    
272                getLog().debug("Will deploy third party file from " + file);
273                getThridPartyTool().attachThirdPartyDescriptor(getProject(), file);
274            }
275    
276            addResourceDir(getOutputDirectory(), "**/*.txt");
277        }
278    
279        protected void writeMissingFile() throws IOException {
280    
281            Log log = getLog();
282            LicenseMap licenseMap = getLicenseMap();
283            File file = getMissingFile();
284    
285            FileUtil.createDirectoryIfNecessary(file.getParentFile());
286            log.info("Regenerate missing license file " + file);
287    
288            FileOutputStream writer = new FileOutputStream(file);
289            try {
290                StringBuilder sb = new StringBuilder(" Generated by " + getClass().getName());
291                List<String> licenses = new ArrayList<String>(licenseMap.keySet());
292                licenses.remove(LicenseMap.getUnknownLicenseMessage());
293                if (!licenses.isEmpty()) {
294                    sb.append("\n-------------------------------------------------------------------------------");
295                    sb.append("\n Already used licenses in project :");
296                    for (String license : licenses) {
297                        sb.append("\n - ").append(license);
298                    }
299                }
300                sb.append("\n-------------------------------------------------------------------------------");
301                sb.append("\n Please fill the missing licenses for dependencies :\n\n");
302                getUnsafeMappings().store(writer, sb.toString());
303            } finally {
304                writer.close();
305            }
306        }
307    
308        public boolean isDoGenerateMissing() {
309            return doGenerateMissing;
310        }
311    
312        public void setDoGenerateMissing(boolean doGenerateMissing) {
313            this.doGenerateMissing = doGenerateMissing;
314        }
315    
316        public ArtifactRepository getLocalRepository() {
317            return localRepository;
318        }
319    
320        public List getRemoteRepositories() {
321            return remoteRepositories;
322        }
323    
324        /**
325         * {@inheritDoc}
326         */
327        @Override
328        public boolean isIncludeTransitiveDependencies() {
329            return includeTransitiveDependencies;
330        }
331    
332        // /**
333        // * {@inheritDoc}
334        // */
335        // public List<String> getExcludedScopes()
336        // {
337        // return Arrays.asList( Artifact.SCOPE_SYSTEM );
338        // }
339    
340        public boolean isDeployMissingFile() {
341            return deployMissingFile;
342        }
343    
344        public boolean isUseRepositoryMissingFiles() {
345            return useRepositoryMissingFiles;
346        }
347    
348    }