001
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
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 }