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