/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ package com.liferay.project.templates; import com.liferay.project.templates.internal.util.FileUtil; import com.liferay.project.templates.internal.util.Validator; import com.liferay.project.templates.internal.util.WorkspaceUtil; import com.liferay.project.templates.util.FileTestUtil; import com.liferay.project.templates.util.StringTestUtil; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.StringReader; import java.nio.charset.StandardCharsets; import java.nio.file.DirectoryStream; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.Arrays; import java.util.HashSet; import java.util.Properties; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.junit.Assert; import org.junit.Test; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * @author Andrea Di Giorgi */ public class ProjectTemplateFilesTest { @Test public void testProjectTemplateFiles() throws Exception { DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); try (DirectoryStream<Path> directoryStream = FileTestUtil.getProjectTemplatesDirectoryStream()) { for (Path path : directoryStream) { _testProjectTemplateFiles(path, documentBuilder); } } } private Element _getChildElement(Element parentElement, String name) { Node node = parentElement.getFirstChild(); do { if (node.getNodeType() == Node.ELEMENT_NODE) { Element element = (Element)node; if (name.equals(element.getTagName())) { return element; } } } while ((node = node.getNextSibling()) != null); return null; } private boolean _isInJavaSrcDir(Path path) throws IOException { path = path.toRealPath(); String pathString = path.toString(); if (File.separatorChar != '/') { pathString = pathString.replace(File.separatorChar, '/'); } for (String sourceSetName : _SOURCESET_NAMES) { if (pathString.contains("/src/" + sourceSetName + "/java/")) { return true; } } return false; } private boolean _isTextFile(String fileName, String extension) { if (fileName.equals("gitignore") || _textFileExtensions.contains(extension)) { return true; } return false; } private void _testArchetypeMetadataXml( Path projectTemplateDirPath, String projectTemplateDirName, boolean hasJavaFiles) throws IOException { Path archetypeMetadataXmlPath = projectTemplateDirPath.resolve( "src/main/resources/META-INF/maven/archetype-metadata.xml"); Assert.assertTrue( "Missing " + archetypeMetadataXmlPath, Files.exists(archetypeMetadataXmlPath)); String archetypeDescriptorName = projectTemplateDirName.substring( FileTestUtil.PROJECT_TEMPLATE_DIR_PREFIX.length()); if (archetypeDescriptorName.equals(WorkspaceUtil.WORKSPACE)) { archetypeDescriptorName = "liferay-" + archetypeDescriptorName; } else { archetypeDescriptorName = "liferay-module-" + archetypeDescriptorName; } String archetypeMetadataXml = FileUtil.read(archetypeMetadataXmlPath); Assert.assertTrue( "Incorrect archetype descriptor name in " + archetypeMetadataXmlPath, archetypeMetadataXml.startsWith( "<?xml version=\"1.0\"?>\n\n<archetype-descriptor name=\"" + archetypeDescriptorName + "\">")); boolean hasArchetypeMetadataAuthorProperty = archetypeMetadataXml.contains("<requiredProperty key=\"author\">"); if (hasJavaFiles) { Assert.assertTrue( "Missing \"author\" required property in " + archetypeMetadataXmlPath, hasArchetypeMetadataAuthorProperty); } else { Assert.assertFalse( "Forbidden \"author\" required property in " + archetypeMetadataXmlPath, hasArchetypeMetadataAuthorProperty); } } private void _testBndBnd(Path projectTemplateDirPath) throws IOException { Path bndBndPath = projectTemplateDirPath.resolve("bnd.bnd"); Properties properties = FileUtil.readProperties(bndBndPath); String bundleDescription = properties.getProperty("Bundle-Description"); Assert.assertTrue( "Missing 'Bundle-Description' header in " + bndBndPath, Validator.isNotNull(bundleDescription)); Matcher matcher = _bundleDescriptionPattern.matcher(bundleDescription); Assert.assertTrue( "Header \"Bundle-Description\" in " + bndBndPath + " must match pattern \"" + _bundleDescriptionPattern.pattern() + "\"", matcher.matches()); } private void _testBuildGradle(Path archetypeResourcesDirPath) { Path buildGradlePath = archetypeResourcesDirPath.resolve( "build.gradle"); Assert.assertTrue( "Missing " + buildGradlePath, Files.exists(buildGradlePath)); } private void _testGitIgnore( String projectTemplateDirName, Path archetypeResourcesDirPath) throws IOException { Path dotGitIgnorePath = archetypeResourcesDirPath.resolve(".gitignore"); Path gitIgnorePath = archetypeResourcesDirPath.resolve("gitignore"); Assert.assertFalse( "Rename " + dotGitIgnorePath + " to " + gitIgnorePath + " to bypass GRADLE-1883", Files.exists(dotGitIgnorePath)); Assert.assertTrue( "Missing " + gitIgnorePath, Files.exists(gitIgnorePath)); if (!projectTemplateDirName.equals( FileTestUtil.PROJECT_TEMPLATE_DIR_PREFIX + WorkspaceUtil.WORKSPACE)) { String gitIgnore = _GIT_IGNORE; if (Files.exists( archetypeResourcesDirPath.resolve("package.json"))) { gitIgnore = _GIT_IGNORE_WITH_PACKAGE_JSON; } Assert.assertEquals( "Incorrect " + gitIgnorePath, gitIgnore, FileUtil.read(gitIgnorePath)); } } private void _testGradleWrapper(Path archetypeResourcesDirPath) { Assert.assertFalse( "Forbidden Gradle Wrapper in " + archetypeResourcesDirPath, Files.exists(archetypeResourcesDirPath.resolve("gradlew"))); } private void _testLanguageProperties(Path path) throws IOException { try (BufferedReader bufferedReader = Files.newBufferedReader( path, StandardCharsets.UTF_8)) { String line = null; while ((line = bufferedReader.readLine()) != null) { Assert.assertFalse( "Forbidden empty line in " + path, line.isEmpty()); Assert.assertFalse( "Forbidden comments in " + path, line.startsWith("##")); } } } private void _testMavenWrapper(Path archetypeResourcesDirPath) { Assert.assertFalse( "Forbidden Maven Wrapper in " + archetypeResourcesDirPath, Files.exists(archetypeResourcesDirPath.resolve("mvnw"))); } private void _testPomXml( Path archetypeResourcesDirPath, DocumentBuilder documentBuilder) throws Exception { Path pomXmlPath = archetypeResourcesDirPath.resolve("pom.xml"); Assert.assertTrue("Missing " + pomXmlPath, Files.exists(pomXmlPath)); Document document = documentBuilder.parse(pomXmlPath.toFile()); Element projectElement = document.getDocumentElement(); Element packagingElement = _getChildElement( projectElement, "packaging"); if (packagingElement != null) { Assert.assertNotEquals( "Incorrect packaging in " + pomXmlPath, "jar", packagingElement.getTextContent()); } Element propertiesElement = _getChildElement( projectElement, "properties"); Assert.assertNotNull( "Missing \"properties\" element in " + pomXmlPath, propertiesElement); String sourceEncoding = null; Element sourceEncodingElement = _getChildElement( propertiesElement, "project.build.sourceEncoding"); if (sourceEncodingElement != null) { sourceEncoding = sourceEncodingElement.getTextContent(); } Assert.assertEquals( "Incorrect property \"project.build.sourceEncoding\" in " + pomXmlPath, sourceEncoding, StandardCharsets.UTF_8.name()); NodeList executionNodeList = projectElement.getElementsByTagName( "execution"); for (int i = 0; i < executionNodeList.getLength(); i++) { Element executionElement = (Element)executionNodeList.item(i); Element idElement = _getChildElement(executionElement, "id"); if (idElement != null) { String id = idElement.getTextContent(); Assert.assertFalse( "Execution ID \"" + id + "\" in " + pomXmlPath + " cannot start with \"default-\"", id.startsWith("default-")); Matcher matcher = _pomXmlExecutionIdPattern.matcher(id); Assert.assertTrue( "Execution ID \"" + id + "\" in " + pomXmlPath + " must match pattern \"" + _pomXmlExecutionIdPattern.pattern() + "\"", matcher.matches()); } } _testPomXmlVersions(pomXmlPath, projectElement, "dependency"); _testPomXmlVersions(pomXmlPath, projectElement, "plugin"); } private void _testPomXmlVersions( Path pomXmlPath, Element projectElement, String name) { Properties systemProperties = System.getProperties(); NodeList nodeList = projectElement.getElementsByTagName(name); for (int i = 0; i < nodeList.getLength(); i++) { Element element = (Element)nodeList.item(i); Element artifactIdElement = _getChildElement(element, "artifactId"); String artifactId = artifactIdElement.getTextContent(); String key = artifactId + ".version"; if (systemProperties.containsKey(key)) { Element versionElement = _getChildElement(element, "version"); Assert.assertEquals( "Incorrect version of " + name + " \"" + artifactId + "\" in " + pomXmlPath, "@" + key + "@", versionElement.getTextContent()); } } } private void _testProjectTemplateFiles( Path projectTemplateDirPath, DocumentBuilder documentBuilder) throws Exception { Path archetypeResourcesDirPath = projectTemplateDirPath.resolve( "src/main/resources/archetype-resources"); Assert.assertTrue( "Missing " + archetypeResourcesDirPath, Files.isDirectory(archetypeResourcesDirPath)); String projectTemplateDirName = String.valueOf( projectTemplateDirPath.getFileName()); _testBndBnd(projectTemplateDirPath); _testBuildGradle(archetypeResourcesDirPath); _testGitIgnore(projectTemplateDirName, archetypeResourcesDirPath); _testGradleWrapper(archetypeResourcesDirPath); _testMavenWrapper(archetypeResourcesDirPath); _testPomXml(archetypeResourcesDirPath, documentBuilder); final AtomicBoolean hasJavaFiles = new AtomicBoolean(); Files.walkFileTree( archetypeResourcesDirPath, new SimpleFileVisitor<Path>() { @Override public FileVisitResult preVisitDirectory( Path dirPath, BasicFileAttributes basicFileAttributes) throws IOException { Path languagePropertiesPath = dirPath.resolve( "Language.properties"); if (Files.exists(languagePropertiesPath)) { _testLanguageProperties(languagePropertiesPath); String glob = "Language_*.properties"; Assert.assertNull( "Forbidden " + dirPath + File.separator + glob, FileUtil.getFile(dirPath, glob)); } return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile( Path path, BasicFileAttributes basicFileAttributes) throws IOException { String fileName = String.valueOf(path.getFileName()); String extension = FileTestUtil.getExtension(fileName); boolean javaFile = extension.equals("java"); if (javaFile) { hasJavaFiles.set(true); } if (!fileName.equals(".gitkeep") && (_isInJavaSrcDir(path) != javaFile)) { Assert.fail("Wrong source directory " + path); } if (_isTextFile(fileName, extension)) { _testTextFile(path, fileName, extension); } return FileVisitResult.CONTINUE; } }); _testArchetypeMetadataXml( projectTemplateDirPath, projectTemplateDirName, hasJavaFiles.get()); } private void _testTextFile(Path path, String fileName, String extension) throws IOException { String text = FileUtil.read(path); boolean trailingEmptyLine = false; if ((text.length() > 0) && (text.charAt(text.length() - 1) == '\n')) { trailingEmptyLine = true; } Assert.assertFalse("Trailing empty line in " + path, trailingEmptyLine); try (BufferedReader bufferedReader = new BufferedReader( new StringReader(text))) { String line = null; while ((line = bufferedReader.readLine()) != null) { Assert.assertFalse( "Forbidden whitespace trailing character in " + path, !line.isEmpty() && Character.isWhitespace(line.charAt(line.length() - 1))); } } Matcher matcher = _velocityIfPattern.matcher(text); while (matcher.find()) { String condition = matcher.group(1); Assert.assertEquals( "Source formatting error in " + path, "#if (" + condition.trim() + ")", matcher.group()); } if (extension.equals("java")) { Assert.assertTrue( "Missing @author tag in " + path, text.contains("* @author ${author}")); } if (extension.equals("xml") && !fileName.equals("liferay-layout-templates.xml") && Validator.isNotNull(text)) { String xmlDeclaration = _XML_DECLARATION; if (fileName.equals("service.xml")) { xmlDeclaration = _SERVICE_XML_DECLARATION; } Assert.assertTrue( "Incorrect XML declaration in " + path, text.startsWith(xmlDeclaration)); } } private static final String _GIT_IGNORE; private static final String _GIT_IGNORE_WITH_PACKAGE_JSON; private static final String _SERVICE_XML_DECLARATION; private static final String[] _SOURCESET_NAMES = { "main", "test", "testIntegration" }; private static final String _XML_DECLARATION = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n"; private static final Pattern _bundleDescriptionPattern = Pattern.compile( "Creates a .+\\."); private static final Pattern _pomXmlExecutionIdPattern = Pattern.compile( "[a-z]+(?:-[a-z]+)*"); private static final Set<String> _textFileExtensions = new HashSet<>( Arrays.asList( "bnd", "gradle", "java", "jsp", "jspf", "properties", "xml")); private static final Pattern _velocityIfPattern = Pattern.compile( "#if\\s*\\(\\s*(.+)\\s*\\)"); static { Set<String> gitIgnoreLines = new TreeSet<>(); gitIgnoreLines.add(".gradle/"); gitIgnoreLines.add("build/"); gitIgnoreLines.add("target/"); _GIT_IGNORE = StringTestUtil.merge(gitIgnoreLines, '\n'); gitIgnoreLines.add("node_modules/"); _GIT_IGNORE_WITH_PACKAGE_JSON = StringTestUtil.merge( gitIgnoreLines, '\n'); StringBuilder sb = new StringBuilder(); sb.append("<?xml version=\"1.0\"?>"); sb.append('\n'); sb.append("<!DOCTYPE service-builder PUBLIC "); sb.append("\"-//Liferay//DTD Service Builder 7.0.0//EN\" "); sb.append("\"http://www.liferay.com/dtd/"); sb.append("liferay-service-builder_7_0_0.dtd\">\n\n"); _SERVICE_XML_DECLARATION = sb.toString(); } }