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