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