001    /**
002     * Copyright (c) 2000-2013 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.util.ant.bnd;
016    
017    import aQute.bnd.build.ProjectBuilder;
018    import aQute.bnd.differ.Baseline;
019    import aQute.bnd.differ.Baseline.BundleInfo;
020    import aQute.bnd.differ.Baseline.Info;
021    import aQute.bnd.differ.DiffPluginImpl;
022    import aQute.bnd.osgi.Builder;
023    import aQute.bnd.osgi.Constants;
024    import aQute.bnd.osgi.Jar;
025    import aQute.bnd.osgi.Resource;
026    import aQute.bnd.service.diff.Delta;
027    import aQute.bnd.service.diff.Diff;
028    import aQute.bnd.version.Version;
029    
030    import com.liferay.portal.kernel.util.StringUtil;
031    import com.liferay.portal.kernel.util.Validator;
032    
033    import java.io.BufferedWriter;
034    import java.io.File;
035    import java.io.FileWriter;
036    import java.io.IOException;
037    import java.io.PrintWriter;
038    
039    import java.util.ArrayList;
040    import java.util.Arrays;
041    import java.util.Comparator;
042    import java.util.List;
043    import java.util.Map;
044    import java.util.Set;
045    
046    import org.apache.tools.ant.BuildException;
047    import org.apache.tools.ant.Project;
048    import org.apache.tools.ant.types.Path;
049    
050    /**
051     * @author Raymond Aug??
052     */
053    public class BaselineJarTask extends BaseBndTask {
054    
055            @Override
056            public void addClasspath(Path classpath) {
057                    _classpath = classpath;
058            }
059    
060            public void setFile(File file) {
061                    _file = file;
062            }
063    
064            public void setOutputPath(File outputPath) {
065                    _outputPath = outputPath;
066            }
067    
068            public void setSourcePath(File sourcePath) {
069                    _sourcePath = sourcePath;
070            }
071    
072            @Override
073            public void trace(String format, Object... args) {
074            }
075    
076            protected void doBaselineJar(
077                            Jar jar, File output, aQute.bnd.build.Project bndProject)
078                    throws Exception {
079    
080                    if (_reportLevelIsOff) {
081                            return;
082                    }
083    
084                    ProjectBuilder projectBuilder = new ProjectBuilder(bndProject);
085    
086                    Jar baselineJar = projectBuilder.getBaselineJar();
087    
088                    try {
089                            if (baselineJar == null) {
090                                    String name = bndProject.getProperty(Constants.BASELINEREPO);
091    
092                                    bndProject.deploy(name, output);
093    
094                                    return;
095                            }
096    
097                            Baseline baseline = new Baseline(this, _diffPluginImpl);
098    
099                            Set<Info> infos = baseline.baseline(jar, baselineJar, null);
100    
101                            if (infos.isEmpty()) {
102                                    return;
103                            }
104    
105                            BundleInfo bundleInfo = baseline.getBundleInfo();
106    
107                            Info[] infosArray = infos.toArray(new Info[infos.size()]);
108    
109                            Arrays.sort(
110                                    infosArray, new Comparator<Info>() {
111    
112                                            @Override
113                                            public int compare(Info info1, Info info2) {
114                                                    return info1.packageName.compareTo(info2.packageName);
115                                            }
116    
117                                    }
118                            );
119    
120                            for (Info info : infosArray) {
121                                    String warnings = "-";
122    
123                                    Version newerVersion = info.newerVersion;
124                                    Version suggestedVersion = info.suggestedVersion;
125    
126                                    if (suggestedVersion != null) {
127                                            if (newerVersion.compareTo(suggestedVersion) > 0) {
128                                                    warnings = "EXCESSIVE VERSION INCREASE";
129                                            }
130                                            else if (newerVersion.compareTo(suggestedVersion) < 0) {
131                                                    warnings = "VERSION INCREASE REQUIRED";
132                                            }
133                                    }
134    
135                                    Diff packageDiff = info.packageDiff;
136    
137                                    Delta delta = packageDiff.getDelta();
138    
139                                    if (delta == Delta.REMOVED) {
140                                            warnings = "PACKAGE REMOVED";
141                                    }
142                                    else if (delta == Delta.UNCHANGED) {
143                                            boolean newVersionSuggested = false;
144    
145                                            if ((suggestedVersion.getMajor() !=
146                                                            newerVersion.getMajor()) ||
147                                                    (suggestedVersion.getMicro() !=
148                                                            newerVersion.getMicro()) ||
149                                                    (suggestedVersion.getMinor() !=
150                                                            newerVersion.getMinor())) {
151    
152                                                    warnings = "VERSION INCREASE SUGGESTED";
153    
154                                                    newVersionSuggested = true;
155                                            }
156    
157                                            if (!newVersionSuggested && !info.mismatch) {
158                                                    continue;
159                                            }
160                                    }
161    
162                                    if (_reportLevelIsStandard && warnings.equals("-")) {
163                                            continue;
164                                    }
165    
166                                    doInfo(bundleInfo, info, warnings);
167    
168                                    if (_reportLevelIsDiff && (delta != Delta.REMOVED)) {
169                                            doPackageDiff(packageDiff);
170                                    }
171                            }
172                    }
173                    finally {
174                            if (baselineJar != null) {
175                                    baselineJar.close();
176                            }
177    
178                            if (_printWriter != null) {
179                                    _printWriter.close();
180                            }
181    
182                            projectBuilder.close();
183                    }
184            }
185    
186            @Override
187            protected void doBeforeExecute() throws Exception {
188                    super.doBeforeExecute();
189    
190                    File bndRootFile = getBndRootFile();
191    
192                    File rootDir = bndRootFile.getParentFile();
193    
194                    if (_classpath == null) {
195                            throw new BuildException("classpath is null");
196                    }
197    
198                    if ((_file == null) || !_file.exists() || _file.isDirectory()) {
199                            if (_file != null) {
200                                    project.log(
201                                            "file is either missing or is a directory " +
202                                                    _file.getAbsolutePath(),
203                                            Project.MSG_ERR);
204                            }
205    
206                            throw new BuildException("file is invalid");
207                    }
208    
209                    if ((_outputPath == null) || !_outputPath.exists() ||
210                            !_outputPath.isDirectory()) {
211    
212                            if (_outputPath != null) {
213                                    project.log(
214                                            "outputPath is either missing or is not a directory " +
215                                                    _outputPath.getAbsolutePath(),
216                                            Project.MSG_ERR);
217                            }
218    
219                            throw new BuildException("outputPath is invalid");
220                    }
221    
222                    _reportLevel = project.getProperty("baseline.jar.report.level");
223    
224                    _reportLevelIsDiff = Validator.equals(_reportLevel, "diff");
225                    _reportLevelIsOff = Validator.equals(_reportLevel, "off");
226                    _reportLevelIsPersist = Validator.equals(_reportLevel, "persist");
227                    _reportLevelIsStandard = Validator.equals(_reportLevel, "standard");
228    
229                    if (_reportLevelIsPersist) {
230                            _reportLevelIsDiff = true;
231    
232                            File baselineReportsDir = new File(
233                                    rootDir, getBaselineResportsDirName());
234    
235                            if (!baselineReportsDir.exists() && !baselineReportsDir.mkdir()) {
236                                    throw new BuildException(
237                                            "Unable tocreate " + baselineReportsDir.getName());
238                            }
239    
240                            _logFile = new File(
241                                    baselineReportsDir, _outputPath.getName() + ".log");
242    
243                            if (_logFile.exists()) {
244                                    _logFile.delete();
245                            }
246                    }
247    
248                    if ((_sourcePath == null) || !_sourcePath.exists() ||
249                            !_sourcePath.isDirectory()) {
250    
251                            if (_sourcePath != null) {
252                                    project.log(
253                                            "sourcePath is either missing or is not a directory " +
254                                                    _sourcePath.getAbsolutePath(),
255                                            Project.MSG_ERR);
256                            }
257    
258                            throw new BuildException("sourcePath is not set correctly");
259                    }
260    
261                    for (String fileName : _classpath.list()) {
262                            _classpathFiles.add(new File(fileName.replace('\\', '/')));
263                    }
264    
265                    _bndDir = new File(rootDir, getBndDirName());
266    
267                    if (!rootDir.canWrite()) {
268                            return;
269                    }
270    
271                    File buildFile = new File(_bndDir, "build.bnd");
272    
273                    if (!_bndDir.exists() && !_bndDir.mkdir()) {
274                            return;
275                    }
276    
277                    if (buildFile.exists() || !_bndDir.canWrite()) {
278                            return;
279                    }
280    
281                    BufferedWriter bufferedWriter = new BufferedWriter(
282                            new FileWriter(buildFile));
283    
284                    for (String line : _BUILD_DEFAULTS) {
285                            bufferedWriter.write(line);
286                            bufferedWriter.newLine();
287                    }
288    
289                    bufferedWriter.close();
290    
291                    File baselineRepoDir = new File(_bndDir, "baselinerepo");
292    
293                    if (!baselineRepoDir.exists()) {
294                            baselineRepoDir.mkdir();
295                    }
296            }
297    
298            protected void doDiff(Diff diff, StringBuffer sb) {
299                    String output = String.format(
300                            "%s%-3s %-10s %s", sb, getShortDelta(diff.getDelta()),
301                            StringUtil.toLowerCase(String.valueOf(diff.getType())),
302                            diff.getName());
303    
304                    project.log(output, Project.MSG_WARN);
305    
306                    if (_printWriter != null) {
307                            _printWriter.println(output);
308                    }
309    
310                    sb.append("\t");
311    
312                    for (Diff curDiff : diff.getChildren()) {
313                            if (curDiff.getDelta() == Delta.UNCHANGED) {
314                                    continue;
315                            }
316    
317                            doDiff(curDiff, sb);
318                    }
319    
320                    sb.deleteCharAt(sb.length() - 1);
321            }
322    
323            @Override
324            protected void doExecute() throws Exception {
325                    aQute.bnd.build.Project bndProject = getBndProject();
326    
327                    Builder builder = new Builder(bndProject);
328    
329                    builder.setClasspath(
330                            _classpathFiles.toArray(new File[_classpathFiles.size()]));
331                    builder.setPedantic(isPedantic());
332                    builder.setProperties(_file);
333                    builder.setSourcepath(new File[] {_sourcePath});
334    
335                    Jar[] jars = builder.builds();
336    
337                    // Report both task failures and bnd build failures
338    
339                    boolean taskFailed = report();
340                    boolean bndFailed = report(builder);
341    
342                    // Fail this build if failure is not ok and either the task failed or
343                    // the bnd build failed
344    
345                    if (taskFailed || bndFailed) {
346                            throw new BuildException(
347                                    "bnd failed",
348                                    new org.apache.tools.ant.Location(_file.getAbsolutePath()));
349                    }
350    
351                    for (Jar jar : jars) {
352                            String bsn = jar.getName();
353    
354                            File outputFile = _outputPath;
355    
356                            String path = builder.getProperty("-output");
357    
358                            if (path == null) {
359                                    outputFile = getFile(_outputPath, bsn + ".jar");
360                            }
361                            else {
362                                    outputFile = getFile(_outputPath, path);
363                            }
364    
365                            if (!outputFile.exists() ||
366                                    (outputFile.lastModified() <= jar.lastModified())) {
367    
368                                    jar.write(outputFile);
369    
370                                    Map<String, Resource> resources = jar.getResources();
371    
372                                    log(
373                                            jar.getName() + " (" + outputFile.getName() + ") " +
374                                                    resources.size());
375    
376                                    doBaselineJar(jar, outputFile, bndProject);
377                            }
378                            else {
379                                    Map<String, Resource> resources = jar.getResources();
380    
381                                    log(
382                                            jar.getName() + " (" + outputFile.getName() + ") " +
383                                                    resources.size() + " (not modified)");
384                            }
385    
386                            report();
387    
388                            jar.close();
389                    }
390    
391                    builder.close();
392            }
393    
394            protected void doHeader(BundleInfo bundleInfo) {
395                    if (_headerPrinted) {
396                            return;
397                    }
398    
399                    _headerPrinted = true;
400    
401                    project.log(
402                            "[Baseline Report] Mode: " + _reportLevel, Project.MSG_WARN);
403    
404                    if (bundleInfo.mismatch) {
405                            project.log(
406                                    "[Baseline Warning] Bundle Version Change Recommended: " +
407                                            bundleInfo.suggestedVersion,
408                                    Project.MSG_WARN);
409                    }
410    
411                    reportLog(
412                            " ", "PACKAGE_NAME", "DELTA", "CUR_VER", "BASE_VER", "REC_VER",
413                            "WARNINGS", "ATTRIBUTES");
414    
415                    reportLog(
416                            "=", "==================================================",
417                            "==========", "==========", "==========", "==========",
418                            "==========", "==========");
419            }
420    
421            protected void doInfo(BundleInfo bundleInfo, Info info, String warnings) {
422                    doHeader(bundleInfo);
423    
424                    reportLog(
425                            String.valueOf(info.mismatch ? '*' : ' '), info.packageName,
426                            String.valueOf(info.packageDiff.getDelta()),
427                            String.valueOf(info.newerVersion),
428                            String.valueOf(info.olderVersion),
429                            String.valueOf(
430                                    (info.suggestedVersion == null) ? "-" : info.suggestedVersion),
431                            warnings, String.valueOf(info.attributes));
432            }
433    
434            protected void doPackageDiff(Diff diff) {
435                    StringBuffer sb = new StringBuffer();
436    
437                    sb.append("\t");
438    
439                    for (Diff curDiff : diff.getChildren()) {
440                            if (curDiff.getDelta() == Delta.UNCHANGED) {
441                                    continue;
442                            }
443    
444                            doDiff(curDiff, sb);
445                    }
446            }
447    
448            protected String getBaselineResportsDirName() {
449                    if (_baselineResportsDirName != null) {
450                            return _baselineResportsDirName;
451                    }
452    
453                    _baselineResportsDirName = project.getProperty(
454                            "baseline.jar.reports.dir.name");
455    
456                    if (_baselineResportsDirName == null) {
457                            _baselineResportsDirName = _BASELINE_REPORTS_DIR;
458                    }
459    
460                    return _baselineResportsDirName;
461            }
462    
463            protected String getShortDelta(Delta delta) {
464                    if (delta == Delta.ADDED) {
465                            return "+";
466                    }
467                    else if (delta == Delta.CHANGED) {
468                            return "~";
469                    }
470                    else if (delta == Delta.MAJOR) {
471                            return ">";
472                    }
473                    else if (delta == Delta.MICRO) {
474                            return "0xB5";
475                    }
476                    else if (delta == Delta.MINOR) {
477                            return "<";
478                    }
479                    else if (delta == Delta.REMOVED) {
480                            return "-";
481                    }
482    
483                    String deltaString = delta.toString();
484    
485                    return String.valueOf(deltaString.charAt(0));
486            }
487    
488            protected void reportLog(
489                    String string1, String string2, String string3, String string4,
490                    String string5, String string6, String string7, String string8) {
491    
492                    String output = String.format(
493                            "%s %-50s %-10s %-10s %-10s %-10s %-10s", string1, string2, string3,
494                            string4, string5, string6, string7);
495    
496                    project.log(output, Project.MSG_WARN);
497    
498                    if (_reportLevelIsPersist) {
499                            try {
500                                    if (_printWriter == null) {
501                                            _logFile.createNewFile();
502    
503                                            _printWriter = new PrintWriter(_logFile);
504                                    }
505    
506                                    _printWriter.println(output);
507                            }
508                            catch (IOException ioe) {
509                                    throw new BuildException(ioe);
510                            }
511                    }
512            }
513    
514            private static final String _BASELINE_REPORTS_DIR = "baseline-reports";
515    
516            private final String[] _BUILD_DEFAULTS = new String[] {
517                    "-plugin: aQute.bnd.deployer.obr.LocalOBR;name=baselinerepo;" +
518                            "mode=build;local=${workspace}/.bnd/baselinerepo",
519                    "-pluginpath: ${workspace}/osgi/lib/plugin/bnd-repository.jar",
520                    "-baseline: ${ant.project.name}",
521                    "-baselinerepo: baselinerepo", "-releaserepo: baselinerepo"
522            };
523    
524            private String _baselineResportsDirName;
525            private File _bndDir;
526            private Path _classpath;
527            private List<File> _classpathFiles = new ArrayList<File>();
528            private DiffPluginImpl _diffPluginImpl = new DiffPluginImpl();
529            private File _file;
530            private boolean _headerPrinted;
531            private File _logFile;
532            private File _outputPath;
533            private PrintWriter _printWriter;
534            private String _reportLevel;
535            private boolean _reportLevelIsDiff;
536            private boolean _reportLevelIsOff = true;
537            private boolean _reportLevelIsPersist;
538            private boolean _reportLevelIsStandard;
539            private File _sourcePath;
540    
541    }