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