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                    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 NaturalOrderStringComparator()
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                    File buildXmlFile = new File(pluginDir, "build.xml");
224                    System.out.println("## read a " + buildXmlFile);
225    
226                    String buildXmlContent = _fileUtil.read(buildXmlFile);
227    
228                    int x = buildXmlContent.indexOf("import.shared");
229    
230                    if (x == -1) {
231                            return ticketIds;
232                    }
233    
234                    x = buildXmlContent.indexOf("value=\"", x);
235                    x = buildXmlContent.indexOf("\"", x);
236    
237                    int y = buildXmlContent.indexOf("\" />", x);
238    
239                    if ((x == -1) || (y == -1)) {
240                            return ticketIds;
241                    }
242    
243                    String[] importShared = StringUtil.split(
244                            buildXmlContent.substring(x + 1, y));
245    
246                    if (importShared.length == 0) {
247                            return ticketIds;
248                    }
249    
250                    for (String currentImportShared : importShared) {
251                            File currentImportSharedDir = new File(
252                                    pluginDir, "../../shared/" + currentImportShared);
253    
254                            if (!currentImportSharedDir.exists()) {
255                                    continue;
256                            }
257    
258                            ticketIds.addAll(_extractTicketIds(currentImportSharedDir, range));
259                    }
260    
261                    return ticketIds;
262            }
263    
264            private String _getChangeLogEntry(
265                    int changeLogVersion, String range, String ticketIdsString) {
266    
267                    StringBundler sb = new StringBundler(8);
268    
269                    if (changeLogVersion > 1) {
270                            sb.append("\n\n");
271                    }
272    
273                    sb.append("#\n");
274                    sb.append("# Module Incremental Version ");
275                    sb.append(changeLogVersion);
276                    sb.append("\n#\n");
277                    sb.append(range);
278                    sb.append("=");
279                    sb.append(ticketIdsString);
280    
281                    return sb.toString();
282            }
283    
284            private String _getLatestHASH(File pluginDir) throws Exception {
285                    Runtime runtime = Runtime.getRuntime();
286    
287                    String command = "git rev-parse HEAD";
288    
289                    if (OSDetector.isWindows()) {
290                            command = "cmd /c " + command;
291                    }
292    
293                    Process process = runtime.exec(command, null, pluginDir);
294    
295                    return StringUtil.read(process.getInputStream());
296            }
297    
298            private String _readProperty(Properties properties, String key) {
299                    return GetterUtil.getString(properties.getProperty(key));
300            }
301    
302            private String _readReleng(
303                            String fileName, Properties pluginPackageProperties)
304                    throws Exception {
305    
306                    int x = fileName.indexOf("WEB-INF");
307    
308                    String relativeWebInfDirName = fileName.substring(0, x + 8);
309    
310                    String fullWebInfDirName =
311                            _pluginsDir + StringPool.SLASH + relativeWebInfDirName;
312    
313                    String relengPropertiesFileName =
314                            fullWebInfDirName + "liferay-releng.properties";
315    
316                    Properties relengProperties = null;
317    
318                    if (FileUtil.exists(relengPropertiesFileName)) {
319                            String relengPropertiesContent = FileUtil.read(
320                                    relengPropertiesFileName);
321    
322                            relengProperties = PropertiesUtil.load(relengPropertiesContent);
323                    }
324                    else {
325                            relengProperties = new Properties();
326                    }
327    
328                    String relengPropertiesContent = _updateRelengPropertiesFile(
329                            relengPropertiesFileName, relengProperties);
330    
331                    relengProperties = PropertiesUtil.load(relengPropertiesContent);
332    
333                    StringBundler sb = new StringBundler();
334    
335                    _writeElement(sb, "bundle", relengProperties, 3);
336                    _writeElement(sb, "category", relengProperties, 3);
337                    _writeElement(sb, "demo-url", relengProperties, 3);
338                    _writeElement(sb, "dependent-apps", relengProperties, 3);
339    
340                    if (FileUtil.exists(fullWebInfDirName + "releng/icons/90x90.png")) {
341                            _writeElement(
342                                    sb, "icon", relativeWebInfDirName + "releng/icons/90x90.png",
343                                    3);
344                    }
345    
346                    _writeElement(sb, "labs", relengProperties, 3);
347                    _writeElement(sb, "marketplace", relengProperties, 3);
348                    _writeElement(sb, "public", relengProperties, 3);
349    
350                    String fullScreenshotsDirName =
351                            fullWebInfDirName + "releng/screenshots/";
352                    String relativeScreenshotsDirName =
353                            relativeWebInfDirName + "releng/screenshots/";
354    
355                    if (FileUtil.exists(fullScreenshotsDirName)) {
356                            String[] screenshotsFileNames = FileUtil.listFiles(
357                                    fullScreenshotsDirName);
358    
359                            Arrays.sort(screenshotsFileNames);
360    
361                            for (String screenshotsFileName : screenshotsFileNames) {
362                                    if (screenshotsFileName.equals("Thumbs.db") ||
363                                            screenshotsFileName.endsWith(".png")) {
364    
365                                            FileUtil.delete(
366                                                    fullScreenshotsDirName + screenshotsFileName);
367                                    }
368    
369                                    if (!screenshotsFileName.endsWith(".jpg")) {
370                                            continue;
371                                    }
372    
373                                    _writeElement(
374                                            sb, "screenshot",
375                                            relativeScreenshotsDirName + screenshotsFileName, 3);
376                            }
377                    }
378    
379                    _writeElement(sb, "support-url", relengProperties, 3);
380                    _writeElement(sb, "supported", relengProperties, 3);
381    
382                    File relengChangeLogFile = new File(
383                            fullWebInfDirName + "liferay-releng.changelog");
384    
385                    if (GetterUtil.getBoolean(
386                                    relengProperties.getProperty("marketplace"))) {
387    
388                            _updateRelengChangeLogFile(
389                                    pluginPackageProperties, relengChangeLogFile, relengProperties);
390                    }
391                    else {
392                            relengChangeLogFile.delete();
393                    }
394    
395                    return sb.toString();
396            }
397    
398            private void _updateRelengChangeLogFile(
399                            Properties pluginPackageProperties, File relengChangeLogFile,
400                            Properties relengProperties)
401                    throws Exception {
402    
403                    StringBundler sb = new StringBundler();
404    
405                    int changeLogVersion = 0;
406    
407                    int moduleIncrementalVersion = GetterUtil.getInteger(
408                            pluginPackageProperties.getProperty("module-incremental-version"));
409    
410                    if (!relengChangeLogFile.exists()) {
411                            FileUtil.write(relengChangeLogFile, "TEMP=");
412                    }
413    
414                    String relengChangeLogContent = FileUtil.read(relengChangeLogFile);
415    
416                    List<String> relengChangeLogEntries = new ArrayList<String>();
417    
418                    String[] relengChangeLogEntriesArray = StringUtil.split(
419                            relengChangeLogContent, "\n");
420    
421                    for (int i = 0; i < relengChangeLogEntriesArray.length; i++) {
422                            String relengChangeLogEntry = relengChangeLogEntriesArray[i];
423    
424                            if (Validator.isNull(relengChangeLogEntry) ||
425                                    relengChangeLogEntry.startsWith("#")) {
426    
427                                    continue;
428                            }
429    
430                            relengChangeLogEntries.add(relengChangeLogEntry);
431    
432                            if (((i + 1) == relengChangeLogEntriesArray.length) &&
433                                    !relengChangeLogEntry.contains("HEAD=") &&
434                                    !relengChangeLogEntry.contains("TEMP=") &&
435                                    !relengChangeLogEntry.contains(_latestHASH) &&
436                                    !relengChangeLogEntries.isEmpty()) {
437    
438                                    int x = relengChangeLogEntry.indexOf("..");
439                                    int y = relengChangeLogEntry.indexOf("=", x);
440    
441                                    String range =
442                                            relengChangeLogEntry.substring(x + 2, y) + "^.." +
443                                                    _latestHASH;
444    
445                                    relengChangeLogEntries.add(range);
446    
447                                    continue;
448                            }
449                    }
450    
451                    File webInfDir = relengChangeLogFile.getParentFile();
452    
453                    File docrootDir = webInfDir.getParentFile();
454    
455                    File pluginDir = docrootDir.getParentFile();
456    
457                    for (int i = 0; i < relengChangeLogEntries.size(); i++) {
458                            String relengChangeLogEntry = relengChangeLogEntries.get(i);
459    
460                            String[] relengChangeLogEntryParts = StringUtil.split(
461                                    relengChangeLogEntry, "=");
462    
463                            String range = relengChangeLogEntryParts[0];
464    
465                            if (range.equals("TEMP")) {
466                                    changeLogVersion++;
467    
468                                    sb.append(
469                                            _getChangeLogEntry(
470                                                    changeLogVersion, range, StringPool.BLANK));
471    
472                                    break;
473                            }
474    
475                            Set<String> ticketIds = _extractTicketIds(pluginDir, range);
476    
477                            if (range.endsWith("^.." + _latestHASH) && ticketIds.isEmpty() &&
478                                    (relengChangeLogEntries.size() > 1)) {
479    
480                                    continue;
481                            }
482    
483                            if (ticketIds.isEmpty()) {
484                                    System.out.println(
485                                            pluginDir + " does not have changes for range " + range);
486                            }
487    
488                            String[] dependentApps = StringUtil.split(
489                                    relengProperties.getProperty("dependent-apps"));
490    
491                            for (String dependentApp : dependentApps) {
492                                    dependentApp = dependentApp.trim();
493    
494                                    if (dependentApp.equals("resources-impoter-web")) {
495                                            continue;
496                                    }
497    
498                                    String dependentAppDirName = null;
499    
500                                    if (dependentApp.endsWith("-hook")) {
501                                            dependentAppDirName = "hooks";
502                                    }
503                                    else if (dependentApp.endsWith("-layouttpl")) {
504                                            dependentAppDirName = "layouttpl";
505                                    }
506                                    else if (dependentApp.endsWith("-portlet")) {
507                                            dependentAppDirName = "portlets";
508                                    }
509                                    else if (dependentApp.endsWith("-theme")) {
510                                            dependentAppDirName = "themes";
511                                    }
512                                    else if (dependentApp.endsWith("-web")) {
513                                            dependentAppDirName = "webs";
514                                    }
515    
516                                    File dependentAppDir = new File(
517                                            _pluginsDir, dependentAppDirName + "/" + dependentApp);
518    
519                                    if (!dependentAppDir.exists()) {
520                                            throw new RuntimeException(
521                                                    dependentAppDir + " does not exist");
522                                    }
523    
524                                    ticketIds.addAll(_extractTicketIds(dependentAppDir, range));
525                            }
526    
527                            String ticketIdsString = StringUtil.merge(
528                                    ticketIds.toArray(new String[ticketIds.size()]), " ");
529    
530                            changeLogVersion++;
531    
532                            sb.append(
533                                    _getChangeLogEntry(changeLogVersion, range, ticketIdsString));
534                    }
535    
536                    File pluginPackagePropertiesFile = new File(
537                            webInfDir, "liferay-plugin-package.properties");
538    
539                    String pluginPackagePropertiesContent = FileUtil.read(
540                            pluginPackagePropertiesFile);
541    
542                    if (!pluginPackagePropertiesContent.contains("long-description")) {
543                            int x = pluginPackagePropertiesContent.indexOf("change-log=");
544    
545                            pluginPackagePropertiesContent =
546                                    pluginPackagePropertiesContent.substring(0, x) +
547                                            "long-description=\n" +
548                                                    pluginPackagePropertiesContent.substring(x);
549                    }
550    
551                    if (moduleIncrementalVersion != changeLogVersion) {
552                            pluginPackagePropertiesContent = StringUtil.replace(
553                                    pluginPackagePropertiesContent,
554                                    "module-incremental-version=" + moduleIncrementalVersion,
555                                    "module-incremental-version=" + changeLogVersion);
556                    }
557    
558                    FileUtil.write(
559                            pluginPackagePropertiesFile, pluginPackagePropertiesContent);
560    
561                    FileUtil.write(relengChangeLogFile, sb.toString());
562    
563                    File relengChangeLogMD5File = new File(
564                            webInfDir, "liferay-releng.changelog.md5");
565    
566                    String md5Checksum = FileUtil.getMD5Checksum(relengChangeLogFile);
567    
568                    FileUtil.write(relengChangeLogMD5File, md5Checksum);
569            }
570    
571            private String _updateRelengPropertiesFile(
572                            String relengPropertiesFileName, Properties relengProperties)
573                    throws Exception {
574    
575                    StringBundler sb = new StringBundler();
576    
577                    _writeProperty(sb, relengProperties, "bundle", "false");
578                    _writeProperty(sb, relengProperties, "category", "");
579                    _writeProperty(sb, relengProperties, "demo-url", "");
580                    _writeProperty(sb, relengProperties, "dependent-apps", "");
581                    _writeProperty(sb, relengProperties, "labs", "true");
582                    _writeProperty(sb, relengProperties, "marketplace", "false");
583                    _writeProperty(sb, relengProperties, "public", "true");
584                    _writeProperty(sb, relengProperties, "support-url", "");
585                    _writeProperty(sb, relengProperties, "supported", "false");
586    
587                    String relengPropertiesContent = sb.toString();
588    
589                    FileUtil.write(relengPropertiesFileName, relengPropertiesContent);
590    
591                    return relengPropertiesContent;
592            }
593    
594            private void _writeElement(
595                    StringBundler sb, String name, Properties properties, int tabsCount) {
596    
597                    _writeElement(sb, name, _readProperty(properties, name), tabsCount);
598            }
599    
600            private void _writeElement(
601                    StringBundler sb, String name, String value, int tabsCount) {
602    
603                    for (int i = 0; i < tabsCount; i++) {
604                            sb.append("\t");
605                    }
606    
607                    sb.append("<");
608                    sb.append(name);
609                    sb.append("><![CDATA[");
610                    sb.append(value);
611                    sb.append("]]></");
612                    sb.append(name);
613                    sb.append(">\n");
614            }
615    
616            private void _writeProperty(
617                    StringBundler sb, Properties properties, String key,
618                    String defaultValue) {
619    
620                    String value = GetterUtil.getString(
621                            properties.getProperty(key), defaultValue);
622    
623                    if (sb.index() > 0) {
624                            sb.append(StringPool.NEW_LINE);
625                    }
626    
627                    sb.append(key);
628                    sb.append(StringPool.EQUAL);
629                    sb.append(value);
630            }
631    
632            private static final String[] _TICKET_ID_PREFIXES = {"LPS", "SOS", "SYNC"};
633    
634            private static FileImpl _fileUtil = FileImpl.getInstance();
635    
636            private Set<String> _distinctAuthors = new TreeSet<String>();
637            private Set<String> _distinctLicenses = new TreeSet<String>();
638            private String _latestHASH;
639            private File _pluginsDir;
640    
641    }