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.portal.tools;
016    
017    import com.liferay.portal.kernel.util.FileUtil;
018    import com.liferay.portal.kernel.util.GetterUtil;
019    import com.liferay.portal.kernel.util.NumericalStringComparator;
020    import com.liferay.portal.kernel.util.OSDetector;
021    import com.liferay.portal.kernel.util.PropertiesUtil;
022    import com.liferay.portal.kernel.util.StringBundler;
023    import com.liferay.portal.kernel.util.StringPool;
024    import com.liferay.portal.kernel.util.StringUtil;
025    import com.liferay.portal.kernel.util.Validator;
026    import com.liferay.portal.util.InitUtil;
027    
028    import java.io.File;
029    
030    import java.util.ArrayList;
031    import java.util.Arrays;
032    import java.util.List;
033    import java.util.Properties;
034    import java.util.Set;
035    import java.util.TreeSet;
036    
037    import org.apache.tools.ant.DirectoryScanner;
038    
039    /**
040     * @author Brian Wing Shun Chan
041     */
042    public class PluginsSummaryBuilder {
043    
044            public static void main(String[] args) {
045                    InitUtil.initWithSpring();
046    
047                    File pluginsDir = new File(System.getProperty("plugins.dir"));
048    
049                    new PluginsSummaryBuilder(pluginsDir);
050            }
051    
052            public PluginsSummaryBuilder(File pluginsDir) {
053                    try {
054                            _pluginsDir = pluginsDir;
055    
056                            _latestHASH = _getLatestHASH(pluginsDir);
057    
058                            _createPluginsSummary();
059                    }
060                    catch (Exception e) {
061                            e.printStackTrace();
062                    }
063            }
064    
065            private void _createPluginsSummary() throws Exception {
066                    DirectoryScanner directoryScanner = new DirectoryScanner();
067    
068                    directoryScanner.setBasedir(_pluginsDir);
069                    directoryScanner.setExcludes(
070                            new String[] {"**\\tmp\\**", "**\\tools\\**"});
071                    directoryScanner.setIncludes(
072                            new String[] {
073                                    "**\\liferay-plugin-package.properties"
074                            });
075    
076                    directoryScanner.scan();
077    
078                    String[] fileNames = directoryScanner.getIncludedFiles();
079    
080                    Arrays.sort(fileNames);
081    
082                    _createPluginsSummary(fileNames);
083            }
084    
085            private void _createPluginsSummary(String[] fileNames) throws Exception {
086                    StringBundler sb = new StringBundler();
087    
088                    sb.append("<plugins-summary>\n");
089    
090                    for (String fileName : fileNames) {
091                            fileName = StringUtil.replace(
092                                    fileName, StringPool.BACK_SLASH, StringPool.SLASH);
093    
094                            _createPluginSummary(sb, fileName);
095                    }
096    
097                    for (String author : _distinctAuthors) {
098                            sb.append("\t<author>");
099                            sb.append(author);
100                            sb.append("</author>\n");
101                    }
102    
103                    for (String license : _distinctLicenses) {
104                            sb.append("\t<license>");
105                            sb.append(license);
106                            sb.append("</license>\n");
107                    }
108    
109                    sb.append("</plugins-summary>");
110    
111                    FileUtil.write(_pluginsDir + "/summary.xml", sb.toString());
112            }
113    
114            private void _createPluginSummary(StringBundler sb, String fileName)
115                    throws Exception {
116    
117                    String content = FileUtil.read(fileName);
118    
119                    int x = fileName.indexOf(StringPool.SLASH);
120    
121                    String type = fileName.substring(0, x);
122    
123                    if (type.endsWith("s")) {
124                            type = type.substring(0, type.length() - 1);
125                    }
126    
127                    x = fileName.indexOf(StringPool.SLASH, x) + 1;
128    
129                    int y = fileName.indexOf(StringPool.SLASH, x);
130    
131                    String artifactId = fileName.substring(x, y);
132    
133                    Properties properties = PropertiesUtil.load(content);
134    
135                    String name = _readProperty(properties, "name");
136                    String tags = _readProperty(properties, "tags");
137                    String shortDescription = _readProperty(
138                            properties, "short-description");
139                    String longDescription = _readProperty(properties, "long-description");
140                    String changeLog = _readProperty(properties, "change-log");
141                    String pageURL = _readProperty(properties, "page-url");
142                    String author = _readProperty(properties, "author");
143                    String licenses = _readProperty(properties, "licenses");
144                    String liferayVersions = _readProperty(properties, "liferay-versions");
145    
146                    _distinctAuthors.add(author);
147                    _distinctLicenses.add(licenses);
148    
149                    sb.append("\t<plugin>\n");
150    
151                    _writeElement(sb, "artifact-id", artifactId, 2);
152                    _writeElement(sb, "name", name, 2);
153                    _writeElement(sb, "type", type, 2);
154                    _writeElement(sb, "tags", tags, 2);
155                    _writeElement(sb, "short-description", shortDescription, 2);
156                    _writeElement(sb, "long-description", longDescription, 2);
157                    _writeElement(sb, "change-log", changeLog, 2);
158                    _writeElement(sb, "page-url", pageURL, 2);
159                    _writeElement(sb, "author", author, 2);
160                    _writeElement(sb, "licenses", licenses, 2);
161                    _writeElement(sb, "liferay-versions", liferayVersions, 2);
162    
163                    sb.append("\t\t<releng>\n");
164                    sb.append(_readReleng(fileName, properties));
165                    sb.append("\t\t</releng>\n");
166                    sb.append("\t</plugin>\n");
167            }
168    
169            private Set<String> _extractTicketIds(File pluginDir, String range)
170                    throws Exception {
171    
172                    Set<String> ticketIds = new TreeSet<String>(
173                            new NumericalStringComparator()
174                    );
175    
176                    Runtime runtime = Runtime.getRuntime();
177    
178                    String command = "git log " + range + " .";
179    
180                    if (OSDetector.isWindows()) {
181                            command = "cmd /c " + command;
182                    }
183    
184                    Process process = runtime.exec(command, null, pluginDir);
185    
186                    String content = StringUtil.read(process.getInputStream());
187    
188                    content = StringUtil.replace(content, "\n", " ");
189    
190                    for (String ticketIdPrefix : _TICKET_ID_PREFIXES) {
191                            int x = 0;
192    
193                            while (true) {
194                                    x = content.indexOf(ticketIdPrefix + "-", x);
195    
196                                    if (x == -1) {
197                                            break;
198                                    }
199    
200                                    int y = x + ticketIdPrefix.length() + 1;
201    
202                                    while (true) {
203                                            if ((y + 1) > content.length()) {
204                                                    break;
205                                            }
206    
207                                            if (Character.isDigit(content.charAt(y))) {
208                                                    y++;
209                                            }
210                                            else {
211                                                    break;
212                                            }
213                                    }
214    
215                                    String ticketId = content.substring(x, y);
216    
217                                    ticketIds.add(ticketId);
218    
219                                    x = y;
220                            }
221                    }
222    
223                    return ticketIds;
224            }
225    
226            private String _getChangeLogEntry(
227                    int changeLogVersion, String range, String ticketIdsString) {
228    
229                    StringBundler sb = new StringBundler(8);
230    
231                    if (changeLogVersion > 1) {
232                            sb.append("\n\n");
233                    }
234    
235                    sb.append("#\n");
236                    sb.append("# Module Incremental Version ");
237                    sb.append(changeLogVersion);
238                    sb.append("\n#\n");
239                    sb.append(range);
240                    sb.append("=");
241                    sb.append(ticketIdsString);
242    
243                    return sb.toString();
244            }
245    
246            private String _getLatestHASH(File pluginDir) throws Exception {
247                    Runtime runtime = Runtime.getRuntime();
248    
249                    String command = "git rev-parse HEAD";
250    
251                    if (OSDetector.isWindows()) {
252                            command = "cmd /c " + command;
253                    }
254    
255                    Process process = runtime.exec(command, null, pluginDir);
256    
257                    return StringUtil.read(process.getInputStream());
258            }
259    
260            private String _readProperty(Properties properties, String key) {
261                    return GetterUtil.getString(properties.getProperty(key));
262            }
263    
264            private String _readReleng(
265                            String fileName, Properties pluginPackageProperties)
266                    throws Exception {
267    
268                    int x = fileName.indexOf("WEB-INF");
269    
270                    String relativeWebInfDirName = fileName.substring(0, x + 8);
271    
272                    String fullWebInfDirName =
273                            _pluginsDir + StringPool.SLASH + relativeWebInfDirName;
274    
275                    String relengPropertiesFileName =
276                            fullWebInfDirName + "liferay-releng.properties";
277    
278                    Properties relengProperties = null;
279    
280                    if (FileUtil.exists(relengPropertiesFileName)) {
281                            String relengPropertiesContent = FileUtil.read(
282                                    relengPropertiesFileName);
283    
284                            relengProperties = PropertiesUtil.load(relengPropertiesContent);
285                    }
286                    else {
287                            relengProperties = new Properties();
288                    }
289    
290                    String relengPropertiesContent = _updateRelengPropertiesFile(
291                            relengPropertiesFileName, relengProperties);
292    
293                    relengProperties = PropertiesUtil.load(relengPropertiesContent);
294    
295                    StringBundler sb = new StringBundler();
296    
297                    _writeElement(sb, "bundle", relengProperties, 3);
298                    _writeElement(sb, "category", relengProperties, 3);
299                    _writeElement(sb, "demo-url", relengProperties, 3);
300                    _writeElement(sb, "dependent-apps", relengProperties, 3);
301    
302                    if (FileUtil.exists(fullWebInfDirName + "releng/icons/90x90.png")) {
303                            _writeElement(
304                                    sb, "icon", relativeWebInfDirName + "releng/icons/90x90.png",
305                                    3);
306                    }
307    
308                    _writeElement(sb, "labs", relengProperties, 3);
309                    _writeElement(sb, "marketplace", relengProperties, 3);
310                    _writeElement(sb, "public", relengProperties, 3);
311    
312                    String fullScreenshotsDirName =
313                            fullWebInfDirName + "releng/screenshots/";
314                    String relativeScreenshotsDirName =
315                            relativeWebInfDirName + "releng/screenshots/";
316    
317                    if (FileUtil.exists(fullScreenshotsDirName)) {
318                            String[] screenshotsFileNames = FileUtil.listFiles(
319                                    fullScreenshotsDirName);
320    
321                            Arrays.sort(screenshotsFileNames);
322    
323                            for (String screenshotsFileName : screenshotsFileNames) {
324                                    if (screenshotsFileName.equals("Thumbs.db") ||
325                                            screenshotsFileName.endsWith(".png")) {
326    
327                                            FileUtil.delete(
328                                                    fullScreenshotsDirName + screenshotsFileName);
329                                    }
330    
331                                    if (!screenshotsFileName.endsWith(".jpg")) {
332                                            continue;
333                                    }
334    
335                                    _writeElement(
336                                            sb, "screenshot",
337                                            relativeScreenshotsDirName + screenshotsFileName, 3);
338                            }
339                    }
340    
341                    _writeElement(sb, "support-url", relengProperties, 3);
342                    _writeElement(sb, "supported", relengProperties, 3);
343    
344                    File relengChangeLogFile = new File(
345                            fullWebInfDirName + "liferay-releng.changelog");
346    
347                    if (GetterUtil.getBoolean(
348                                    relengProperties.getProperty("marketplace"))) {
349    
350                            _updateRelengChangeLogFile(
351                                    pluginPackageProperties, relengChangeLogFile, relengProperties);
352                    }
353                    else {
354                            relengChangeLogFile.delete();
355                    }
356    
357                    return sb.toString();
358            }
359    
360            private void _updateRelengChangeLogFile(
361                            Properties pluginPackageProperties, File relengChangeLogFile,
362                            Properties relengProperties)
363                    throws Exception {
364    
365                    StringBundler sb = new StringBundler();
366    
367                    int changeLogVersion = 0;
368    
369                    int moduleIncrementalVersion = GetterUtil.getInteger(
370                            pluginPackageProperties.getProperty("module-incremental-version"));
371    
372                    if (!relengChangeLogFile.exists()) {
373                            FileUtil.write(relengChangeLogFile, "TEMP=");
374                    }
375    
376                    String relengChangeLogContent = FileUtil.read(relengChangeLogFile);
377    
378                    List<String> relengChangeLogEntries = new ArrayList<String>();
379    
380                    String[] relengChangeLogEntriesArray = StringUtil.split(
381                            relengChangeLogContent, "\n");
382    
383                    for (int i = 0; i < relengChangeLogEntriesArray.length; i++) {
384                            String relengChangeLogEntry = relengChangeLogEntriesArray[i];
385    
386                            if (Validator.isNull(relengChangeLogEntry) ||
387                                    relengChangeLogEntry.startsWith("#")) {
388    
389                                    continue;
390                            }
391    
392                            relengChangeLogEntries.add(relengChangeLogEntry);
393    
394                            if (((i + 1) == relengChangeLogEntriesArray.length) &&
395                                    !relengChangeLogEntry.contains("HEAD=") &&
396                                    !relengChangeLogEntry.contains("TEMP=") &&
397                                    !relengChangeLogEntry.contains(_latestHASH) &&
398                                    !relengChangeLogEntries.isEmpty()) {
399    
400                                    int x = relengChangeLogEntry.indexOf("..");
401                                    int y = relengChangeLogEntry.indexOf("=", x);
402    
403                                    String range =
404                                            relengChangeLogEntry.substring(x + 2, y) + "^.." +
405                                                    _latestHASH;
406    
407                                    relengChangeLogEntries.add(range);
408    
409                                    continue;
410                            }
411                    }
412    
413                    for (int i = 0; i < relengChangeLogEntries.size(); i++) {
414                            String relengChangeLogEntry = relengChangeLogEntries.get(i);
415    
416                            String[] relengChangeLogEntryParts = StringUtil.split(
417                                    relengChangeLogEntry, "=");
418    
419                            String range = relengChangeLogEntryParts[0];
420    
421                            if (range.equals("TEMP")) {
422                                    changeLogVersion++;
423    
424                                    sb.append(
425                                            _getChangeLogEntry(
426                                                    changeLogVersion, range, StringPool.BLANK));
427    
428                                    break;
429                            }
430    
431                            File webInfDir = relengChangeLogFile.getParentFile();
432    
433                            File docrootDir = webInfDir.getParentFile();
434    
435                            File pluginDir = docrootDir.getParentFile();
436    
437                            Set<String> ticketIds = _extractTicketIds(pluginDir, range);
438    
439                            if (range.endsWith("^.." + _latestHASH) && ticketIds.isEmpty() &&
440                                    (relengChangeLogEntries.size() > 1)) {
441    
442                                    continue;
443                            }
444    
445                            if (ticketIds.isEmpty()) {
446                                    System.out.println(
447                                            pluginDir + " does not have changes for range " + range);
448                            }
449    
450                            String[] dependentApps = StringUtil.split(
451                                    relengProperties.getProperty("dependent-apps"));
452    
453                            for (String dependentApp : dependentApps) {
454                                    dependentApp = dependentApp.trim();
455    
456                                    if (dependentApp.equals("resources-impoter-web")) {
457                                            continue;
458                                    }
459    
460                                    String dependentAppDirName = null;
461    
462                                    if (dependentApp.endsWith("-hook")) {
463                                            dependentAppDirName = "hooks";
464                                    }
465                                    else if (dependentApp.endsWith("-layouttpl")) {
466                                            dependentAppDirName = "layouttpl";
467                                    }
468                                    else if (dependentApp.endsWith("-portlet")) {
469                                            dependentAppDirName = "portlets";
470                                    }
471                                    else if (dependentApp.endsWith("-theme")) {
472                                            dependentAppDirName = "themes";
473                                    }
474                                    else if (dependentApp.endsWith("-web")) {
475                                            dependentAppDirName = "webs";
476                                    }
477    
478                                    File dependentAppDir = new File(
479                                            _pluginsDir, dependentAppDirName + "/" + dependentApp);
480    
481                                    if (!dependentAppDir.exists()) {
482                                            throw new RuntimeException(
483                                                    dependentAppDir + " does not exist");
484                                    }
485    
486                                    ticketIds.addAll(_extractTicketIds(dependentAppDir, range));
487                            }
488    
489                            String ticketIdsString = StringUtil.merge(
490                                    ticketIds.toArray(new String[ticketIds.size()]), " ");
491    
492                            changeLogVersion++;
493    
494                            sb.append(
495                                    _getChangeLogEntry(changeLogVersion, range, ticketIdsString));
496                    }
497    
498                    File pluginPackagePropertiesFile = new File(
499                            relengChangeLogFile.getParentFile(),
500                            "liferay-plugin-package.properties");
501    
502                    String pluginPackagePropertiesContent = FileUtil.read(
503                            pluginPackagePropertiesFile);
504    
505                    if (!pluginPackagePropertiesContent.contains("long-description")) {
506                            int x = pluginPackagePropertiesContent.indexOf("change-log=");
507    
508                            pluginPackagePropertiesContent =
509                                    pluginPackagePropertiesContent.substring(0, x) +
510                                            "long-description=\n" +
511                                                    pluginPackagePropertiesContent.substring(x);
512                    }
513    
514                    if (moduleIncrementalVersion != changeLogVersion) {
515                            pluginPackagePropertiesContent = StringUtil.replace(
516                                    pluginPackagePropertiesContent,
517                                    "module-incremental-version=" + moduleIncrementalVersion,
518                                    "module-incremental-version=" + changeLogVersion);
519                    }
520    
521                    FileUtil.write(
522                            pluginPackagePropertiesFile, pluginPackagePropertiesContent);
523    
524                    FileUtil.write(relengChangeLogFile, sb.toString());
525            }
526    
527            private String _updateRelengPropertiesFile(
528                            String relengPropertiesFileName, Properties relengProperties)
529                    throws Exception {
530    
531                    StringBundler sb = new StringBundler();
532    
533                    _writeProperty(sb, relengProperties, "bundle", "false");
534                    _writeProperty(sb, relengProperties, "category", "");
535                    _writeProperty(sb, relengProperties, "demo-url", "");
536                    _writeProperty(sb, relengProperties, "dependent-apps", "");
537                    _writeProperty(sb, relengProperties, "labs", "true");
538                    _writeProperty(sb, relengProperties, "marketplace", "false");
539                    _writeProperty(sb, relengProperties, "public", "true");
540                    _writeProperty(sb, relengProperties, "support-url", "");
541                    _writeProperty(sb, relengProperties, "supported", "false");
542    
543                    String relengPropertiesContent = sb.toString();
544    
545                    FileUtil.write(relengPropertiesFileName, relengPropertiesContent);
546    
547                    return relengPropertiesContent;
548            }
549    
550            private void _writeElement(
551                    StringBundler sb, String name, Properties properties, int tabsCount) {
552    
553                    _writeElement(sb, name, _readProperty(properties, name), tabsCount);
554            }
555    
556            private void _writeElement(
557                    StringBundler sb, String name, String value, int tabsCount) {
558    
559                    for (int i = 0; i < tabsCount; i++) {
560                            sb.append("\t");
561                    }
562    
563                    sb.append("<");
564                    sb.append(name);
565                    sb.append("><![CDATA[");
566                    sb.append(value);
567                    sb.append("]]></");
568                    sb.append(name);
569                    sb.append(">\n");
570            }
571    
572            private void _writeProperty(
573                    StringBundler sb, Properties properties, String key,
574                    String defaultValue) {
575    
576                    String value = GetterUtil.getString(
577                            properties.getProperty(key), defaultValue);
578    
579                    if (sb.index() > 0) {
580                            sb.append(StringPool.NEW_LINE);
581                    }
582    
583                    sb.append(key);
584                    sb.append(StringPool.EQUAL);
585                    sb.append(value);
586            }
587    
588            private static final String[] _TICKET_ID_PREFIXES = {"LPS", "SOS"};
589    
590            private Set<String> _distinctAuthors = new TreeSet<String>();
591            private Set<String> _distinctLicenses = new TreeSet<String>();
592            private String _latestHASH;
593            private File _pluginsDir;
594    
595    }