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