| /* |
| * 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.feature.cpconverter.handlers; |
| |
| import static java.util.Objects.requireNonNull; |
| import static org.apache.jackrabbit.vault.packaging.PackageProperties.NAME_VERSION; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.Objects; |
| import java.util.Properties; |
| import java.util.jar.JarEntry; |
| import java.util.jar.JarInputStream; |
| import java.util.jar.Manifest; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import org.apache.felix.utils.manifest.Clause; |
| import org.apache.felix.utils.manifest.Parser; |
| import org.apache.jackrabbit.vault.fs.io.Archive; |
| import org.apache.jackrabbit.vault.fs.io.Archive.Entry; |
| import org.apache.sling.feature.ArtifactId; |
| import org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter; |
| import org.apache.sling.feature.cpconverter.artifacts.InputStreamArtifactWriter; |
| import org.codehaus.plexus.util.StringUtils; |
| import org.jetbrains.annotations.NotNull; |
| import org.osgi.framework.Constants; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| public final class BundleEntryHandler extends AbstractRegexEntryHandler { |
| |
| private static final String NAME_GROUP_ID = "groupId"; |
| |
| private static final String NAME_ARTIFACT_ID = "artifactId"; |
| |
| private static final String NAME_CLASSIFIER = "classifier"; |
| |
| private static final String BUNDLE_SYMBOLIC_NAME = "Bundle-SymbolicName"; |
| |
| private static final String BUNDLE_NAME = "Bundle-Name"; |
| |
| private static final String BUNDLE_VERSION = "Bundle-Version"; |
| |
| private static final String JAR_TYPE = "jar"; |
| |
| private final Logger logger = LoggerFactory.getLogger(getClass()); |
| |
| private final Pattern pomPropertiesPattern = Pattern.compile("META-INF/maven/[^/]+/[^/]+/pom.properties"); |
| |
| public BundleEntryHandler() { |
| super("/jcr_root/(?:apps|libs)/.+/install(?:\\.([^/]+))?/(?:([0-9]+)/)?.+\\.jar"); |
| } |
| |
| @Override |
| public void handle(@NotNull String path, @NotNull Archive archive, @NotNull Entry entry, @NotNull ContentPackage2FeatureModelConverter converter) throws Exception { |
| logger.info("Processing bundle {}...", entry.getName()); |
| |
| String groupId; |
| String artifactId; |
| String version; |
| String classifier = null; |
| Manifest manifest; |
| |
| try (JarInputStream jarInput = new JarInputStream(Objects.requireNonNull(archive.openInputStream(entry)))) { |
| Properties properties = readGav(entry.getName(), jarInput); |
| manifest = jarInput.getManifest(); |
| |
| if (!properties.isEmpty()) { |
| groupId = getCheckedProperty(properties, NAME_GROUP_ID); |
| artifactId = getCheckedProperty(properties, NAME_ARTIFACT_ID); |
| version = getCheckedProperty(properties, NAME_VERSION); |
| classifier = properties.getProperty(NAME_CLASSIFIER); |
| } else { // maybe the included jar is just an OSGi bundle but not a valid Maven artifact |
| groupId = getCheckedProperty(manifest, BUNDLE_SYMBOLIC_NAME); |
| // Make sure there are not spaces in the name to adhere to the Maven Group Id specification |
| groupId = groupId.replaceAll(" ", "_"); |
| artifactId = getCheckedProperty(manifest, BUNDLE_NAME); |
| // Make sure there are not spaces in the name to adhere to the Maven Artifact Id specification |
| artifactId = artifactId.replaceAll(" ", "_"); |
| version = getCheckedProperty(manifest, BUNDLE_VERSION); |
| } |
| } |
| |
| Matcher matcher = getPattern().matcher(path); |
| String runMode = null; |
| Integer startLevel = null; |
| // we are pretty sure it matches, here |
| if (!matcher.matches()) { |
| throw new IllegalStateException("Something went terribly wrong: pattern '" |
| + getPattern().pattern() |
| + "' should have matched already with path '" |
| + path |
| + "' but it does not, currently"); |
| } |
| |
| if (StringUtils.isNotBlank(matcher.group(1))) { |
| // there is a specified RunMode |
| runMode = matcher.group(1); |
| logger.debug("Runmode {} was extracted from path {}", runMode, path); |
| } |
| |
| if (StringUtils.isNotBlank(matcher.group(2))) { |
| // there is a specified Start Level |
| startLevel = Integer.parseInt(matcher.group(2)); // NumberFormatException impossible due to RegEx |
| logger.debug("Start level {} was extracted from path {}", startLevel, path); |
| } |
| |
| try (InputStream input = archive.openInputStream(entry)) { |
| if (input != null) { |
| ArtifactId id = new ArtifactId(groupId, artifactId, version, classifier, JAR_TYPE); |
| |
| Objects.requireNonNull(converter.getArtifactsDeployer()).deploy(new InputStreamArtifactWriter(input), id); |
| |
| Objects.requireNonNull(converter.getFeaturesManager()).addArtifact(runMode, id, startLevel); |
| |
| String epHeader = manifest.getMainAttributes().getValue(Constants.EXPORT_PACKAGE); |
| if (epHeader != null) { |
| for (Clause clause : Parser.parseHeader(epHeader)) { |
| converter.getFeaturesManager().addAPIRegionExport(runMode, clause.getName()); |
| } |
| } |
| } |
| } |
| } |
| |
| // method visibility set to 'protected' fot testing purposes |
| protected @NotNull Properties readGav(@NotNull String entryName, @NotNull JarInputStream jarInput) throws IOException { |
| Properties properties = new Properties(); |
| |
| String bundleName = entryName; |
| // Remove the leading path |
| int idx = bundleName.lastIndexOf('/'); |
| if (idx >= 0) { |
| bundleName = bundleName.substring(idx + 1); |
| } |
| // Remove the extension |
| int edx = bundleName.lastIndexOf('.'); |
| if (edx > 0) { |
| bundleName = bundleName.substring(0, edx); |
| } |
| |
| JarEntry jarEntry; |
| while ((jarEntry = jarInput.getNextJarEntry()) != null) { |
| String nextEntryName = jarEntry.getName(); |
| |
| if (pomPropertiesPattern.matcher(nextEntryName).matches()) { |
| logger.info("Reading '{}' bundle GAV from {}...", bundleName, nextEntryName); |
| |
| properties.load(jarInput); |
| |
| String artifactId = properties.getProperty(NAME_ARTIFACT_ID); |
| String version = properties.getProperty(NAME_VERSION); |
| |
| if (artifactId == null || version == null) { |
| continue; |
| } |
| |
| // bundleName is now the bare name without extension |
| String synthesized = artifactId + "-" + version; |
| |
| // it was the pom.properties we were looking for |
| if (bundleName.startsWith(synthesized)) { |
| |
| // check the artifact has a classifier in the bundle file name |
| if (synthesized.length() < bundleName.length()) { |
| String suffix = bundleName.substring(synthesized.length()); |
| if (suffix.length() > 1 && suffix.startsWith("-")) { |
| String classifier = suffix.substring(1); |
| logger.info("Inferred classifier of '" |
| + artifactId |
| + ":" |
| + version |
| + "' to be '" |
| + classifier |
| + "'"); |
| properties.setProperty(NAME_CLASSIFIER, classifier); |
| } |
| } |
| |
| break; |
| } |
| } |
| } |
| |
| return properties; |
| } |
| |
| private static @NotNull String getCheckedProperty(@NotNull Manifest manifest, @NotNull String name) { |
| String property = manifest.getMainAttributes().getValue(name).trim(); |
| return requireNonNull(property, "Jar file can not be defined as a valid OSGi bundle without specifying a valid '" |
| + name |
| + "' property."); |
| } |
| |
| private static String getCheckedProperty(@NotNull Properties properties, @NotNull String name) { |
| String property = properties.getProperty(name).trim(); |
| return requireNonNull(property, "Jar file can not be defined as a valid Maven artifact without specifying a valid '" |
| + name |
| + "' property."); |
| } |
| |
| } |