/*
* Copyright 2012 Red Hat, Inc. and/or its affiliates.
*
* Licensed 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.guvnor.m2repo.backend.server;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.maven.model.Model;
import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
import org.codehaus.plexus.util.IOUtil;
import org.drools.core.io.impl.ReaderInputStream;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.util.artifact.SubArtifact;
import org.guvnor.common.services.project.model.GAV;
import org.guvnor.m2repo.backend.server.repositories.ArtifactRepository;
import org.guvnor.m2repo.backend.server.repositories.DistributionManagementArtifactRepository;
import org.guvnor.m2repo.backend.server.repositories.FileSystemArtifactRepository;
import org.guvnor.m2repo.backend.server.repositories.LocalArtifactRepository;
import org.guvnor.m2repo.preferences.ArtifactRepositoryPreference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.uberfire.apache.commons.io.FilenameUtils;
import static org.guvnor.m2repo.utils.FileNameUtilities.*;
@ApplicationScoped
public class GuvnorM2Repository {
private static final Logger log = LoggerFactory.getLogger(GuvnorM2Repository.class);
public static String M2_REPO_DIR;
private static final int BUFFER_SIZE = 1024;
private final List<ArtifactRepository> repositories = new ArrayList<>();
private final List<ArtifactRepository> pomRepositories = new ArrayList<>();
private ArtifactRepositoryPreference preferences;
public GuvnorM2Repository() {
}
@Inject
public GuvnorM2Repository(ArtifactRepositoryPreference preferences) {
this.preferences = preferences;
}
@PostConstruct
public void init() {
preferences.load();
setM2Repos();
}
private void setM2Repos() {
final String M2_REPO_ROOT = FilenameUtils.separatorsToSystem(preferences.getDefaultM2RepoDir());
final String meReposDir = System.getProperty("org.guvnor.m2repo.dir");
if (meReposDir == null || meReposDir.trim().isEmpty()) {
M2_REPO_DIR = M2_REPO_ROOT;
} else {
M2_REPO_DIR = meReposDir.trim();
}
final LocalArtifactRepository localRepository = new LocalArtifactRepository("local-m2-repo");
final FileSystemArtifactRepository fileSystemRepository = new FileSystemArtifactRepository("guvnor-m2-repo",
this.M2_REPO_DIR);
final DistributionManagementArtifactRepository distributionManagementArtifactRepository = new DistributionManagementArtifactRepository("distribution-management-repo");
repositories.add(localRepository);
repositories.add(fileSystemRepository);
repositories.add(distributionManagementArtifactRepository);
pomRepositories.add(localRepository);
pomRepositories.add(fileSystemRepository);
}
public String getM2RepositoryRootDir() {
if (!M2_REPO_DIR.endsWith(File.separator)) {
return M2_REPO_DIR + File.separator;
} else {
return M2_REPO_DIR;
}
}
public String getRepositoryURL() {
File file = new File(getM2RepositoryRootDir());
return "file://" + file.getAbsolutePath();
}
public void deployArtifact(final InputStream jarStream,
final GAV gav,
final boolean includeAdditionalRepositories) {
//Write JAR to temporary file for deployment
File jarFile = new File(System.getProperty("java.io.tmpdir"),
toFileName(gav,
"jar"));
try {
inputStreamToFile(jarStream,
jarFile);
//Write pom.xml to JAR if it doesn't already exist
String pomXML = loadPomFromJar(new File(jarFile.getPath()));
if (pomXML == null) {
pomXML = generatePOM(gav);
jarFile = appendFileToJar(pomXML,
getPomXmlPath(gav),
jarFile.getPath());
}
//Write pom.properties to JAR if it doesn't already exist
String pomProperties = loadGAVFromJarInternal(new File(jarFile.getPath()));
if (pomProperties == null) {
pomProperties = generatePomProperties(gav);
jarFile = appendFileToJar(pomProperties,
getPomPropertiesPath(gav),
jarFile.getPath());
}
deployArtifact(gav,
pomXML,
jarFile,
includeAdditionalRepositories);
} finally {
try {
jarFile.delete();
} catch (Exception e) {
log.warn("Unable to remove temporary file '" + jarFile.getAbsolutePath() + "'");
}
}
}
public void deployPom(final InputStream pomStream,
final GAV gav) {
//Write POM to temporary file for deployment
File pomFile = new File(System.getProperty("java.io.tmpdir"),
toFileName(gav,
"pom"));
try {
inputStreamToFile(pomStream,
pomFile);
deployPom(gav,
pomFile);
} finally {
try {
pomFile.delete();
} catch (Exception e) {
log.warn("Unable to remove temporary file '" + pomFile.getAbsolutePath() + "'");
}
}
}
private void inputStreamToFile(final InputStream inputStream,
final File file) {
FileOutputStream fos = null;
try {
if (!file.exists()) {
file.getParentFile().mkdirs();
file.createNewFile();
}
fos = new FileOutputStream(file);
final byte[] buf = new byte[BUFFER_SIZE];
int byteRead = 0;
while ((byteRead = inputStream.read(buf)) != -1) {
fos.write(buf,
0,
byteRead);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (fos != null) {
try {
fos.flush();
fos.close();
} catch (IOException e) {
log.error("Error occurred when trying to close stream",
e);
}
}
}
}
public void deployParentPom(final GAV gav) {
//Write pom.xml to temporary file for deployment
final File pomXMLFile = new File(System.getProperty("java.io.tmpdir"),
toFileName(gav,
"pom.xml"));
try {
String pomXML = generateParentPOM(gav);
writeStringIntoFile(pomXML,
pomXMLFile);
//pom.xml Artifact
Artifact pomXMLArtifact = new DefaultArtifact(gav.getGroupId(),
gav.getArtifactId(),
"pom",
gav.getVersion());
pomXMLArtifact = pomXMLArtifact.setFile(pomXMLFile);
final Artifact finalPomXMLArtifact = pomXMLArtifact;
this.pomRepositories.forEach(artifactRepository -> {
artifactRepository.deploy(pomXML,
finalPomXMLArtifact);
});
} finally {
try {
pomXMLFile.delete();
} catch (Exception e) {
log.warn("Unable to remove temporary file '" + pomXMLFile.getAbsolutePath() + "'");
}
}
}
private void deployArtifact(final GAV gav,
final String pomXML,
final File jarFile,
final boolean includeAdditionalRepositories) {
//Write pom.xml to temporary file for deployment
final File pomXMLFile = new File(System.getProperty("java.io.tmpdir"),
toFileName(gav,
"pom.xml"));
try {
writeStringIntoFile(pomXML,
pomXMLFile);
//JAR Artifact
Artifact jarArtifact = new DefaultArtifact(gav.getGroupId(),
gav.getArtifactId(),
"jar",
gav.getVersion());
jarArtifact = jarArtifact.setFile(jarFile);
//pom.xml Artifact
Artifact pomXMLArtifact = new SubArtifact(jarArtifact,
"",
"pom");
pomXMLArtifact = pomXMLArtifact.setFile(pomXMLFile);
final Artifact finalJarArtifact = jarArtifact;
final Artifact finalPomXMLArtifact = pomXMLArtifact;
this.repositories.forEach((repository) -> repository.deploy(pomXML,
finalJarArtifact,
finalPomXMLArtifact));
//Only deploy to additional repositories if required. This flag is principally for Unit Tests
if (!includeAdditionalRepositories) {
return;
}
} finally {
try {
pomXMLFile.delete();
} catch (Exception e) {
log.warn("Unable to remove temporary file '" + pomXMLFile.getAbsolutePath() + "'");
}
}
}
private void writeStringIntoFile(final String string,
final File file) {
FileOutputStream fos = null;
try {
if (!file.exists()) {
file.getParentFile().mkdirs();
file.createNewFile();
}
fos = new FileOutputStream(file);
IOUtils.write(string,
fos);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (fos != null) {
try {
fos.flush();
fos.close();
} catch (IOException e) {
log.error("Error occurred trying to close a stream",
e);
}
}
}
}
private void deployPom(final GAV gav,
final File pomFile) {
//POM Artifact
Artifact pomArtifact = new DefaultArtifact(gav.getGroupId(),
gav.getArtifactId(),
"pom",
gav.getVersion());
pomArtifact = pomArtifact.setFile(pomFile);
final Artifact finalPomArtifact = pomArtifact;
this.pomRepositories.forEach(artifactRepository -> {
artifactRepository.deploy(null,
finalPomArtifact);
});
}
/**
* Finds files within the repository.
* @return an collection of java.io.File with the matching files
*/
public Collection<File> listFiles() {
return listFiles(null);
}
/**
* Finds files within the repository with the given filters.
* @param filters filter to apply when finding files. The filter is used to create a wildcard matcher, ie., "*filter*.*", in which "*" is
* to represent a multiple wildcard characters.
* @return an collection of java.io.File with the matching files
*/
public List<File> listFiles(final String filters) {
return listFiles(filters,
null);
}
/**
* Finds files within the repository with the given filters and formats.
* @param filters filter to apply when finding files. The filter is used to create a wildcard matcher, ie., "*filter*.*", in which "*" is
* to represent a multiple wildcard characters.
* @param fileFormats file formats to apply when finding files, ie., [ "jar", "kjar" ].
* @return an collection of java.io.File with the matching files
*/
public List<File> listFiles(final String filters,
List<String> fileFormats) {
final List<String> wildcards = new ArrayList<String>();
String wildcardPrefix = "";
if (filters != null) {
wildcardPrefix = "*" + filters;
}
if (fileFormats == null) {
fileFormats = new ArrayList<String>();
fileFormats.add("jar");
fileFormats.add("kjar");
fileFormats.add("pom");
}
for (String fileFormat : fileFormats) {
wildcards.add(wildcardPrefix + "*." + fileFormat);
}
final List<File> files = new ArrayList<File>(getFiles(wildcards));
return files;
}
public List<Artifact> listArtifacts(final String filters,
List<String> fileFormats) {
final List<String> wildcards = new ArrayList<String>();
String wildcardPrefix = "";
if (filters != null) {
wildcardPrefix = "*" + filters;
}
if (fileFormats == null) {
fileFormats = new ArrayList<String>();
fileFormats.add("jar");
fileFormats.add("kjar");
fileFormats.add("pom");
}
for (String fileFormat : fileFormats) {
wildcards.add(wildcardPrefix + "*." + fileFormat);
}
final List<Artifact> files = new ArrayList<>(getArtifacts(wildcards));
return files;
}
protected Collection<File> getFiles(final List<String> wildcards) {
return this.repositories.stream()
.flatMap(artifactRepository -> artifactRepository.listFiles(wildcards).stream())
.collect(Collectors.toList());
}
protected Collection<Artifact> getArtifacts(final List<String> wildcards) {
return this.repositories.stream()
.flatMap(artifactRepository -> artifactRepository.listArtifacts(wildcards).stream())
.collect(Collectors.toList());
}
public static String getPomText(final String path) {
final File file = new File(M2_REPO_DIR,
path);
final String normalizedPath = file.toPath().normalize().toString();
if (isJar(normalizedPath) || isKJar(normalizedPath)) {
return loadPomFromJar(file);
} else if (isDeployedPom(normalizedPath)) {
return loadPom(file);
} else {
throw new RuntimeException("Not a valid jar, kjar or pom file: " + path);
}
}
private static String loadPomFromJar(final File file) {
try {
ZipFile zip = new ZipFile(file);
for (Enumeration e = zip.entries(); e.hasMoreElements(); ) {
ZipEntry entry = (ZipEntry) e.nextElement();
if (entry.getName().startsWith("META-INF/maven") && entry.getName().endsWith("pom.xml")) {
return zipEntryToString(zip,
entry);
}
}
} catch (ZipException e) {
log.error(e.getMessage());
} catch (IOException e) {
log.error(e.getMessage());
}
return null;
}
private static String loadPom(final File file) {
try (InputStream is = new FileInputStream(file);
InputStreamReader isr = new InputStreamReader(is,
"UTF-8")) {
StringBuilder sb = new StringBuilder();
for (int c = isr.read(); c != -1; c = isr.read()) {
sb.append((char) c);
}
return sb.toString();
} catch (IOException e) {
log.error(e.getMessage());
}
return null;
}
public GAV loadGAVFromJar(final String jarPath) {
File zip = new File(M2_REPO_DIR,
jarPath);
try {
final String pomProperties = loadGAVFromJarInternal(zip);
final Properties props = new Properties();
props.load(new StringReader(pomProperties));
final String groupId = props.getProperty("groupId");
final String artifactId = props.getProperty("artifactId");
final String version = props.getProperty("version");
return new GAV(groupId,
artifactId,
version);
} catch (IOException e) {
log.error(e.getMessage());
}
return null;
}
private String loadGAVFromJarInternal(final File file) {
try {
ZipFile zip = new ZipFile(file);
for (Enumeration e = zip.entries(); e.hasMoreElements(); ) {
ZipEntry entry = (ZipEntry) e.nextElement();
if (entry.getName().startsWith("META-INF/maven") && entry.getName().endsWith("pom.properties")) {
return zipEntryToString(zip,
entry);
}
}
} catch (ZipException e) {
log.error(e.getMessage());
} catch (IOException e) {
log.error(e.getMessage());
}
return null;
}
public static String loadPomFromJar(final InputStream jarInputStream) {
try {
InputStream is = getInputStreamFromJar(jarInputStream,
"META-INF/maven",
"pom.xml");
StringBuilder sb = new StringBuilder();
for (int c = is.read(); c != -1; c = is.read()) {
sb.append((char) c);
}
return sb.toString();
} catch (IOException e) {
log.error(e.getMessage());
}
return null;
}
public static String loadPomPropertiesFromJar(final InputStream jarInputStream) {
try {
InputStream is = getInputStreamFromJar(jarInputStream,
"META-INF/maven",
"pom.properties");
StringBuilder sb = new StringBuilder();
for (int c = is.read(); c != -1; c = is.read()) {
sb.append((char) c);
}
return sb.toString();
} catch (IOException e) {
log.error(e.getMessage());
}
return null;
}
private static InputStream getInputStreamFromJar(final InputStream jarInputStream,
final String prefix,
final String suffix) throws IOException {
ZipInputStream zis = new ZipInputStream(jarInputStream);
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
final String entryName = entry.getName();
if (entryName.startsWith(prefix) && entryName.endsWith(suffix)) {
return new ReaderInputStream(new InputStreamReader(zis,
"UTF-8"));
}
}
throw new FileNotFoundException("Could not find '" + prefix + "/*/" + suffix + "' in the jar.");
}
private File appendFileToJar(final String content,
final String contentPath,
final String jarPath) {
File originalJarFile = new File(jarPath);
File appendedJarFile = new File(jarPath + ".tmp");
try (ZipFile war = new ZipFile(originalJarFile);
ZipOutputStream append = new ZipOutputStream(new FileOutputStream(appendedJarFile))) {
// first, copy contents from existing war
copyEntriesFromExistingWar(war,
append);
// append pom.xml
ZipEntry e = new ZipEntry(contentPath);
append.putNextEntry(e);
append.write(content.getBytes());
append.closeEntry();
} catch (IOException e) {
log.error(e.getMessage());
}
return appendedJarFile;
}
private void copyEntriesFromExistingWar(final ZipFile war,
final ZipOutputStream append) throws IOException {
Enumeration<? extends ZipEntry> entries = war.entries();
while (entries.hasMoreElements()) {
ZipEntry e = entries.nextElement();
append.putNextEntry(e);
if (!e.isDirectory()) {
IOUtil.copy(war.getInputStream(e),
append);
}
append.closeEntry();
}
}
protected String toFileName(final GAV gav,
final String fileName) {
return gav.getGroupId() + "-" + gav.getArtifactId() + "-" + gav.getVersion() + "-" + Math.random() + "." + fileName;
}
public String generatePOM(final GAV gav) {
Model model = new Model();
model.setGroupId(gav.getGroupId());
model.setArtifactId(gav.getArtifactId());
model.setVersion(gav.getVersion());
model.setModelVersion("4.0.0");
StringWriter stringWriter = new StringWriter();
try {
new MavenXpp3Writer().write(stringWriter,
model);
} catch (IOException e) {
log.error(e.getMessage());
}
return stringWriter.toString();
}
public static String generatePomProperties(final GAV gav) {
StringBuilder sBuilder = new StringBuilder();
sBuilder.append("groupId=");
sBuilder.append(gav.getGroupId());
sBuilder.append("\n");
sBuilder.append("artifactId=");
sBuilder.append(gav.getArtifactId());
sBuilder.append("\n");
sBuilder.append("version=");
sBuilder.append(gav.getVersion());
sBuilder.append("\n");
return sBuilder.toString();
}
public String getPomXmlPath(final GAV gav) {
return "META-INF/maven/" + gav.getGroupId() + "/" + gav.getArtifactId() + "/pom.xml";
}
public String getPomPropertiesPath(final GAV gav) {
return "META-INF/maven/" + gav.getGroupId() + "/" + gav.getArtifactId() + "/pom.properties";
}
public String generateParentPOM(final GAV gav) {
Model model = new Model();
model.setGroupId(gav.getGroupId());
model.setArtifactId(gav.getArtifactId());
model.setVersion(gav.getVersion());
model.setPackaging("pom");
model.setModelVersion("4.0.0");
StringWriter stringWriter = new StringWriter();
try {
new MavenXpp3Writer().write(stringWriter,
model);
} catch (IOException e) {
log.error(e.getMessage());
}
return stringWriter.toString();
}
public boolean containsArtifact(final GAV gav) {
return this.repositories.stream()
.anyMatch(artifactRepository -> artifactRepository.containsArtifact(gav));
}
public File getArtifactFileFromRepository(final GAV gav) {
final List<File> artifacts = this.repositories
.stream()
.map(artifactRepository -> artifactRepository.getArtifactFileFromRepository(gav))
.filter(artifact -> artifact != null)
.collect(Collectors.toList());
return artifacts.get(0);
}
protected static String zipEntryToString(ZipFile zip,
ZipEntry entry) throws IOException {
final InputStream is = zip.getInputStream(entry);
final InputStreamReader isr = new InputStreamReader(is,
"UTF-8");
StringBuilder sb = new StringBuilder();
for (int c = isr.read(); c != -1; c = isr.read()) {
sb.append((char) c);
}
return sb.toString();
}
}