blob: 7cd69181a62a34f0bd7fdf19b336b4692caa775b [file] [log] [blame]
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~ Licensed to the Apache Software Foundation (ASF) under one
~ or more contributor license agreements. See the NOTICE file
~ distributed with this work for additional information
~ regarding copyright ownership. The ASF licenses this file
~ to you under the Apache License, Version 2.0 (the
~ "License"); you may not use this file except in compliance
~ with the License. You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing,
~ software distributed under the License is distributed on an
~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
~ KIND, either express or implied. See the License for the
~ specific language governing permissions and limitations
~ under the License.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
package org.apache.sling.scriptingbundle.maven.plugin;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.plugin.logging.Log;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.osgi.framework.Version;
import org.osgi.framework.VersionRange;
class ResourceTypeFolderAnalyser {
private final Log log;
private final Path scriptsDirectory;
private final ResourceTypeFolderPredicate resourceTypeFolderPredicate;
ResourceTypeFolderAnalyser(@NotNull Log log, @NotNull Path scriptsDirectory) {
this.log = log;
this.scriptsDirectory = scriptsDirectory;
this.resourceTypeFolderPredicate = new ResourceTypeFolderPredicate(log);
}
Capabilities getCapabilities(@NotNull Path resourceTypeDirectory, @NotNull Map<String, String> scriptEngineMappings) {
Set<ProvidedCapability> providedCapabilities = new LinkedHashSet<>();
Set<RequiredCapability> requiredCapabilities = new LinkedHashSet<>();
String version = null;
String resourceType = null;
try {
Path fileName = resourceTypeDirectory.getFileName();
if (fileName != null) {
version = Version.parseVersion(fileName.toString()).toString();
}
} catch (IllegalArgumentException ignored) {
log.debug("No resourceType version detected in " + scriptsDirectory.resolve(resourceTypeDirectory).toString());
}
if (StringUtils.isNotEmpty(version)) {
Path parent = resourceTypeDirectory.getParent();
if (parent != null) {
resourceType = getResourceType(parent);
}
} else {
resourceType = getResourceType(resourceTypeDirectory);
}
if (resourceType != null) {
String resourceTypeLabel = getResourceTypeLabel(resourceType);
try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(scriptsDirectory.resolve(resourceTypeDirectory))) {
for (Path directoryEntry : directoryStream) {
if (Files.isRegularFile(directoryEntry)) {
Path file = directoryEntry.getFileName();
if (file != null) {
if (MetadataMojo.EXTENDS_FILE.equals(file.toString())) {
processExtendsFile(resourceType, version, directoryEntry, providedCapabilities, requiredCapabilities);
} else if (MetadataMojo.REQUIRES_FILE.equals(file.toString())) {
processRequiresFile(directoryEntry, requiredCapabilities);
} else {
processScriptFile(resourceTypeDirectory, scriptEngineMappings, directoryEntry, resourceType, version,
resourceTypeLabel, providedCapabilities);
}
}
} else if (Files.isDirectory(directoryEntry) && !resourceTypeFolderPredicate.test(directoryEntry)) {
try (Stream<Path> subtree = Files.walk(directoryEntry)) {
Iterator<Path> subtreeIterator = subtree.iterator();
while (subtreeIterator.hasNext()) {
Path subtreeEntry = subtreeIterator.next();
if (Files.isRegularFile(subtreeEntry)) {
processScriptFile(resourceTypeDirectory, scriptEngineMappings, subtreeEntry, resourceType, version,
resourceTypeLabel, providedCapabilities);
}
}
}
}
}
} catch (IOException | IllegalArgumentException e) {
log.warn(String.format("Cannot analyse folder %s.", scriptsDirectory.resolve(resourceTypeDirectory).toString()), e);
}
}
return new Capabilities(providedCapabilities, requiredCapabilities);
}
private void processExtendsFile(@NotNull String resourceType, @Nullable String version, @NotNull Path extendsFile,
@NotNull Set<ProvidedCapability> providedCapabilities,
@NotNull Set<RequiredCapability> requiredCapabilities)
throws IOException {
List<String> extendResources = Files.readAllLines(extendsFile, StandardCharsets.UTF_8);
if (extendResources.size() == 1) {
String extend = extendResources.get(0);
if (StringUtils.isNotEmpty(extend)) {
String[] extendParts = extend.split(";");
String extendedResourceType = extendParts[0];
String extendedResourceTypeVersion = extendParts.length > 1 ? extendParts[1] : null;
providedCapabilities.add(
ProvidedCapability.builder()
.withResourceType(resourceType)
.withVersion(version)
.withExtendsResourceType(extendedResourceType)
.build());
RequiredCapability.Builder requiredBuilder =
RequiredCapability.builder().withResourceType(extendedResourceType);
try {
if (extendedResourceTypeVersion != null) {
requiredBuilder.withVersionRange(
VersionRange.valueOf(extendedResourceTypeVersion.substring(extendedResourceTypeVersion.indexOf('=') + 1)));
}
} catch (IllegalArgumentException ignored) {
log.warn(String.format("Invalid version range '%s' defined for extended resourceType '%s'" +
" in file %s.", extendedResourceTypeVersion, extendedResourceType,
extendsFile.toString()));
}
requiredCapabilities.add(requiredBuilder.build());
}
}
}
private void processRequiresFile(@NotNull Path requiresFile,
@NotNull Set<RequiredCapability> requiredCapabilities)
throws IOException {
List<String> requiredResourceTypes = Files.readAllLines(requiresFile, StandardCharsets.UTF_8);
for (String requiredResourceType : requiredResourceTypes) {
if (StringUtils.isNotEmpty(requiredResourceType)) {
String[] requireParts = requiredResourceType.split(";");
String resourceType = requireParts[0];
String version = requireParts.length > 1 ? requireParts[1] : null;
RequiredCapability.Builder requiredBuilder =
RequiredCapability.builder().withResourceType(resourceType);
try {
if (version != null) {
requiredBuilder.withVersionRange(VersionRange.valueOf(version.substring(version.indexOf('=') + 1)));
}
} catch (IllegalArgumentException ignored) {
log.warn(String.format("Invalid version range '%s' defined for required resourceType '%s'" +
" in file %s.", version, resourceType,
requiresFile.toString()));
}
requiredCapabilities.add(requiredBuilder.build());
}
}
}
private void processScriptFile(@NotNull Path resourceTypeDirectory, @NotNull Map<String, String> scriptEngineMappings,
@NotNull Path script, @NotNull String resourceType, @Nullable String version,
@NotNull String resourceTypeLabel, @NotNull Set<ProvidedCapability> providedCapabilities) {
Path scriptFile = script.getFileName();
if (scriptFile != null) {
Path relativeResourceTypeFolder = resourceTypeDirectory.relativize(script);
int pathSegments = relativeResourceTypeFolder.getNameCount();
ArrayList<String> selectors = new ArrayList<>();
if (pathSegments > 1) {
for (int i = 0; i < pathSegments - 1; i++) {
selectors.add(relativeResourceTypeFolder.getName(i).toString());
}
}
String[] parts = scriptFile.toString().split("\\.");
if (parts.length >= 2 && parts.length <= 4) {
String method = null;
String requestExtension = null;
String scriptExtension = parts[parts.length - 1];
String first = parts[0];
if (!first.equals(resourceTypeLabel)) {
if (MetadataMojo.METHODS.contains(first)) {
// method script
method = first;
if ("HEAD".equals(method) || "GET".equals(method)) {
if (parts.length == 4) {
selectors.add(parts[1]);
requestExtension = parts[2];
} else if (parts.length == 3) {
String middlePart = parts[1];
if (MimeTypeChecker.hasMimeType(middlePart)) {
requestExtension = middlePart;
} else {
selectors.add(middlePart);
}
}
} else {
if (parts.length != 2) {
log.warn(String.format("Script %s doesn't respect naming conventions. Only GET and HEAD scripts are " +
"allowed to use additional selectors or request extensions.", script.toString()));
return;
}
}
} else {
// selector script
if (parts.length == 3) {
selectors.add(first);
requestExtension = parts[1];
} else if (parts.length == 2) {
selectors.add(first);
} else {
log.warn(String.format("Script %s doesn't respect naming conventions. A selector script can contain at max " +
"the request extension, but no other selectors."));
return;
}
}
} else {
// main script
if (parts.length > 3) {
log.warn(String.format("Script %s doesn't respect naming conventions. The main script can contain at max " +
"the request extension, but no other selectors."));
return;
}
if (parts.length == 3) {
requestExtension = parts[parts.length - 2];
}
}
String scriptEngine = scriptEngineMappings.get(scriptExtension);
if (StringUtils.isNotEmpty(scriptEngine)) {
providedCapabilities.add(ProvidedCapability.builder().withResourceType(resourceType).withScriptEngine(scriptEngine)
.withVersion(version).withSelectors(selectors).withRequestExtension(requestExtension).withRequestMethod(method)
.build());
} else {
log.warn(String.format("Unknown script engine mapping for script %s. Skipping.", scriptFile.toString()));
}
} else {
log.warn(String.format("Skipping script %s since it either does not target a script engine or it provides too many " +
"selector parts in its name.", script.toString()));
}
} else {
log.warn(String.format("Skipping path %s since it has 0 elements.", script.toString()));
}
}
private String getResourceType(@NotNull Path resourceTypeFolder) {
StringBuilder stringBuilder = new StringBuilder();
Path relativeResourceTypePath = scriptsDirectory.relativize(resourceTypeFolder);
if (StringUtils.isNotEmpty(relativeResourceTypePath.toString())) {
int parts = relativeResourceTypePath.getNameCount();
for (int i = 0; i < parts; i++) {
stringBuilder.append(relativeResourceTypePath.getName(i));
if (i < parts - 1) {
stringBuilder.append('/');
}
}
}
return stringBuilder.toString();
}
private @NotNull String getResourceTypeLabel(@NotNull String resourceType) {
String resourceTypeLabel = null;
if (resourceType.contains("/")) {
int lastIndex = resourceType.lastIndexOf('/');
if (lastIndex < resourceType.length() - 2) {
resourceTypeLabel = resourceType.substring(++lastIndex);
}
} else if (resourceType.contains(".")) {
int lastIndex = resourceType.lastIndexOf('.');
if (lastIndex < resourceType.length() - 2) {
resourceTypeLabel = resourceType.substring(++lastIndex);
}
} else {
resourceTypeLabel = resourceType;
}
if (StringUtils.isEmpty(resourceTypeLabel)) {
throw new IllegalArgumentException(String.format("Resource type '%s' does not provide a resourceTypeLabel.", resourceType));
}
return resourceTypeLabel;
}
}