blob: 7133b77fdda87d8089687b9f61254c622685dcc3 [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.netbeans.modules.maven.classpath;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.project.MavenProject;
import org.netbeans.api.annotations.common.SuppressWarnings;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.classpath.JavaClassPathConstants;
import org.netbeans.api.java.project.JavaProjectConstants;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.api.project.SourceGroup;
import org.netbeans.api.project.Sources;
import org.netbeans.api.project.ant.AntArtifact;
import org.netbeans.api.project.libraries.Library;
import org.netbeans.modules.maven.api.ModelUtils;
import org.netbeans.modules.maven.api.ModelUtils.Descriptor;
import org.netbeans.modules.maven.api.NbMavenProject;
import org.netbeans.modules.maven.indexer.api.NBVersionInfo;
import org.netbeans.modules.maven.indexer.api.RepositoryPreferences;
import org.netbeans.modules.maven.indexer.api.RepositoryQueries;
import org.netbeans.modules.maven.model.ModelOperation;
import org.netbeans.modules.maven.model.pom.Dependency;
import org.netbeans.modules.maven.model.pom.POMModel;
import org.netbeans.modules.maven.model.pom.Repository;
import org.netbeans.spi.java.project.classpath.ProjectClassPathModifierImplementation;
import org.netbeans.spi.project.ProjectServiceProvider;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.filesystems.URLMapper;
import org.openide.util.Exceptions;
import org.openide.util.Utilities;
/**
* an implementation of ProjectClassPathModifierImplementation that tried to match
* maven dependencies to the way classpath items are added through this api.
* @author mkleint
*/
@ProjectServiceProvider(service=ProjectClassPathModifierImplementation.class, projectType="org-netbeans-modules-maven")
public class CPExtender extends ProjectClassPathModifierImplementation {
private static final Logger LOG = Logger.getLogger(CPExtender.class.getName());
private Project project;
private static final String POM_XML = "pom.xml"; //NOI18N
public CPExtender(Project project) {
this.project = project;
}
private boolean addRemoveLibrary(Library library, POMModel model, String scope, boolean add) throws IOException {
Boolean modified = checkLibraryForPoms(library, model, scope, add);
if (modified == null) {
List<URL> urls = library.getContent("classpath"); //NOI18N
modified = urls.size() > 0;
assert model != null;
for (URL url : urls) {
FileObject fo = URLMapper.findFileObject(url);
if (fo == null) {
throw new IOException("Could find no file corresponding to " + url);
}
FileObject jarFO = FileUtil.getArchiveFile(fo);
if (jarFO == null || FileUtil.getArchiveRoot(jarFO) != fo) {
throw new IOException("Cannot add non-root of JAR: " + url);
}
File jar = FileUtil.toFile(jarFO);
if (jar == null) {
throw new IOException("no disk file found corresponding to " + jarFO);
}
if (jar.isDirectory()) {
throw new IOException("Cannot add folders to Maven projects as dependencies: " + url); //NOI18N
}
modified = modified && addRemoveJAR(jar, model, scope, add);
}
}
return modified;
}
private boolean addRemoveJAR(File jar, POMModel mdl, String scope, boolean add) throws IOException {
if (!add) {
throw new UnsupportedOperationException("removing JARs not yet supported");
}
NBVersionInfo dep = null;
for (NBVersionInfo _dep : RepositoryQueries.findBySHA1Result(jar, RepositoryPreferences.getInstance().getRepositoryInfos()).getResults()) {
if (!"unknown.binary".equals(_dep.getGroupId())) {
dep = _dep;
break;
}
}
if (dep == null) {
dep = new NBVersionInfo(null, "unknown.binary", jar.getName().replaceFirst("[.]jar$", ""), "SNAPSHOT", null, null, null, null, null);
addJarToPrivateRepo(jar, mdl, dep);
}
//if not found anywhere, add to a custom file:// based repository structure within the project's directory.
boolean added = false;
Dependency dependency = ModelUtils.checkModelDependency(mdl, dep.getGroupId(), dep.getArtifactId(), false);
if (dependency == null) {
dependency = ModelUtils.checkModelDependency(mdl, dep.getGroupId(), dep.getArtifactId(), true);
LOG.log(Level.FINE, "added new dep {0} as {1}", new Object[] {jar, dep});
added = true;
}
if (!Utilities.compareObjects(dep.getVersion(), dependency.getVersion())) {
dependency.setVersion(dep.getVersion());
LOG.log(Level.FINE, "upgraded version on {0} as {1}", new Object[] {jar, dep});
added = true;
}
if (!Utilities.compareObjects(scope, dependency.getScope())) {
dependency.setScope(scope);
LOG.log(Level.FINE, "changed scope on {0} as {1}", new Object[] {jar, dep});
added = true;
}
return added;
}
/**
* @return true if something was added, false if everything was already there, null if could not do anything
*/
@SuppressWarnings("NP_BOOLEAN_RETURN_NULL")
private Boolean checkLibraryForPoms(Library library, POMModel model, String scope, boolean add) {
if (!"j2se".equals(library.getType())) {//NOI18N
//only j2se library supported for now..
return null;
}
Boolean modified = null;
Descriptor d = ModelUtils.checkLibraries(library);
for (ModelUtils.LibraryDescriptor result : d.getDependencies()) {
LOG.log(Level.FINE, "found {0} for {1}", new Object[] {result, library.getName()});
//set dependency
modified = false;
Dependency dep = ModelUtils.checkModelDependency(model, result.getGroupId(), result.getArtifactId(), false);
if (!add) {
if (dep != null &&
Utilities.compareObjects(result.getVersion(), dep.getVersion()) &&
Utilities.compareObjects(scope, dep.getScope()) &&
Utilities.compareObjects(result.getClassifier(), dep.getClassifier())) {
model.removeChildComponent(dep);
modified = true;
}
continue;
}
if (dep == null) {
dep = ModelUtils.checkModelDependency(model, result.getGroupId(), result.getArtifactId(), true);
modified = true;
}
if (!Utilities.compareObjects(result.getVersion(), dep.getVersion())) {
dep.setVersion(result.getVersion());
modified = true;
}
if (!Utilities.compareObjects(result.getType(), dep.getType()) && !Utilities.compareObjects("jar", result.getType())) {
dep.setType(result.getType());
modified = true;
}
if (!Utilities.compareObjects(scope, dep.getScope())) {
dep.setScope(scope);
modified = true;
}
if (!Utilities.compareObjects(result.getClassifier(), dep.getClassifier())) {
dep.setClassifier(result.getClassifier());
modified = true;
}
}
for (ModelUtils.RepositoryDescriptor result : d.getRepositories()) {
//set repository
org.netbeans.modules.maven.model.pom.Repository reposit = ModelUtils.addModelRepository(
project.getLookup().lookup(NbMavenProject.class).getMavenProject(), model, result.getRepoRoot());
if (reposit != null) {
reposit.setId(library.getName());
reposit.setLayout(result.getRepoType());
reposit.setName("Repository for library " + library.getDisplayName()); //NOI18N - content coming into the pom.xml file
modified = true;
}
}
LOG.log(Level.FINE, "checkLibraryForPoms on {0} -> {1}", new Object[] {library, modified});
return modified;
}
@Override
public SourceGroup[] getExtensibleSourceGroups() {
Sources s = ProjectUtils.getSources(this.project);
assert s != null;
List<SourceGroup> grps = new ArrayList<SourceGroup>();
SourceGroup[] java = s.getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA);
if (java != null) {
grps.addAll(Arrays.asList(java));
}
SourceGroup[] res = s.getSourceGroups(JavaProjectConstants.SOURCES_TYPE_RESOURCES);
if (res != null) {
grps.addAll(Arrays.asList(res));
}
SourceGroup[] web = s.getSourceGroups("doc_root");
if (web != null) {
grps.addAll(Arrays.asList(web));
}
return grps.toArray(new SourceGroup[0]);
}
@Override
public String[] getExtensibleClassPathTypes(SourceGroup arg0) {
return new String[] {
ClassPath.COMPILE,
ClassPath.EXECUTE,
JavaClassPathConstants.COMPILE_ONLY,
JavaClassPathConstants.MODULE_COMPILE_PATH,
JavaClassPathConstants.MODULE_EXECUTE_PATH,
JavaClassPathConstants.PROCESSOR_PATH
};
}
public @Override boolean addLibraries(Library[] libraries, SourceGroup grp, String type) throws IOException {
return addRemoveLibraries(libraries, grp, type, true);
}
public @Override boolean removeLibraries(Library[] libraries, SourceGroup grp, String type) throws IOException {
return addRemoveLibraries(libraries, grp, type, false);
}
private boolean addRemoveLibraries(final Library[] libraries, SourceGroup grp, String type, final boolean add) throws IOException {
final AtomicBoolean modified = new AtomicBoolean();
final String scope = findScope(grp, type);
ModelOperation<POMModel> operation = new ModelOperation<POMModel>() {
public @Override void performOperation(POMModel model) {
for (Library library : libraries) {
try {
modified.compareAndSet(false, addRemoveLibrary(library, model, scope, add));
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
}
}
};
FileObject pom = project.getProjectDirectory().getFileObject(POM_XML);//NOI18N
org.netbeans.modules.maven.model.Utilities.performPOMModelOperations(pom, Collections.singletonList(operation));
//TODO is the manual reload necessary if pom.xml file is being saved?
// NbMavenProject.fireMavenProjectReload(project);
if (modified.get()) {
project.getLookup().lookup(NbMavenProject.class).triggerDependencyDownload();
}
return modified.get();
}
public @Override boolean addRoots(final URL[] urls, SourceGroup grp, String type) throws IOException {
final AtomicBoolean added = new AtomicBoolean();
final String scope = findScope(grp, type);
ModelOperation<POMModel> operation = new ModelOperation<POMModel>() {
public @Override void performOperation(POMModel model) {
for (URL url : urls) {
File jar = FileUtil.archiveOrDirForURL(url);
if (jar != null && jar.isFile()) {
try {
added.compareAndSet(false, addRemoveJAR(jar, model, scope, true));
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
} else {
Logger.getLogger(CPExtender.class.getName()).log(Level.INFO, "Adding non-jar root to Maven projects makes no sense. ({0})", url); //NOI18N
}
}
}
};
FileObject pom = project.getProjectDirectory().getFileObject(POM_XML);//NOI18N
org.netbeans.modules.maven.model.Utilities.performPOMModelOperations(pom, Collections.singletonList(operation));
if (added.get()) {
project.getLookup().lookup(NbMavenProject.class).triggerDependencyDownload();
}
return added.get();
}
@Override
protected boolean addProjects(final Project[] projects, SourceGroup sg, String classPathType) throws IOException, UnsupportedOperationException {
final AtomicBoolean added = new AtomicBoolean();
final AtomicBoolean nonMavenError = new AtomicBoolean();
final String scope = findScope(sg, classPathType);
ModelOperation<POMModel> operation = new ModelOperation<POMModel>() {
public @Override void performOperation(POMModel model) {
Set<Artifact> arts = project.getLookup().lookup(NbMavenProject.class).getMavenProject().getArtifacts();
for (Project prj: projects) {
NbMavenProject nbprj = prj.getLookup().lookup(NbMavenProject.class);
if (nbprj != null) {
MavenProject mp = nbprj.getMavenProject();
String id = mp.getArtifact().getId();
for (Artifact a : arts) {
if (a.getId().equals(id)) {
//already there..
continue;
}
}
Dependency dependency = ModelUtils.checkModelDependency(model, mp.getGroupId(), mp.getArtifactId(), true);
dependency.setVersion(mp.getVersion());
if (scope != null) {
dependency.setScope(scope);
}
added.set(true);
} else {
// unsupported usecase, not a maven project
nonMavenError.set(true);
}
}
}
};
FileObject pom = project.getProjectDirectory().getFileObject(POM_XML);//NOI18N
org.netbeans.modules.maven.model.Utilities.performPOMModelOperations(pom, Collections.singletonList(operation));
if (nonMavenError.get()) {
//throw late to prevent the pom model to go bust eventually
throw new UnsupportedOperationException("Attempting to add a non-Maven project dependency to a Maven project, not supported."); //NOI18N
}
return added.get();
}
@Override
public boolean removeRoots(URL[] arg0, SourceGroup arg1, String arg2) throws IOException,
UnsupportedOperationException {
throw new UnsupportedOperationException("Removing binary dependencies is not supported by Maven projects.");
}
@Override
public boolean addAntArtifacts(AntArtifact[] arg0, URI[] arg1,
SourceGroup arg2, String arg3) throws IOException, UnsupportedOperationException {
throw new UnsupportedOperationException("Cannot add Ant based projects as subprojects to Maven projects.");//NOI18N
}
@Override
public boolean removeAntArtifacts(AntArtifact[] arg0, URI[] arg1,
SourceGroup arg2, String arg3) throws IOException, UnsupportedOperationException {
throw new UnsupportedOperationException("Cannot remove Ant based projects as subprojects from Maven projects.");//NOI18N
}
private static String findScope(SourceGroup grp, String type) {
String scope = ClassPath.EXECUTE.equals(type) || JavaClassPathConstants.MODULE_EXECUTE_PATH.equals(type)? Artifact.SCOPE_RUNTIME : null; //NOI18N
//figure if we deal with test or regular sources.
String name = grp.getName();
if (MavenSourcesImpl.NAME_TESTSOURCE.equals(name)) {
scope = "test"; //NOI18N
}
if (scope == null &&
(JavaClassPathConstants.COMPILE_ONLY.equals(type) || JavaClassPathConstants.PROCESSOR_PATH.equals(type))) {
scope = Artifact.SCOPE_PROVIDED;
}
return scope;
}
// XXX this is a poor solution; http://jira.codehaus.org/secure/attachment/53864/MNG-1867.zip is better
private void addJarToPrivateRepo(File jar, POMModel mdl, NBVersionInfo dep) throws IOException {
//first add the local repo to
List<Repository> repos = mdl.getProject().getRepositories();
boolean found = false;
String path = null;
if (repos != null) {
for (Repository repo : repos) {
if ("unknown-jars-temp-repo".equals(repo.getId())) { //NOI18N
found = true;
String url = repo.getUrl();
if (url.startsWith("file:${project.basedir}/")) { //NOI18N
path = url.substring("file:${project.basedir}/".length()); //NOI18N
} else {
path = "lib"; //NOI18N
}
break;
}
}
}
if (!found) {
Repository repo = mdl.getFactory().createRepository();
repo.setId("unknown-jars-temp-repo"); //NOI18N
repo.setName("A temporary repository created by NetBeans for libraries and jars it could not identify. Please replace the dependencies in this repository with correct ones and delete this repository."); //NOI18N
repo.setUrl("file:${project.basedir}/lib"); //NOI18N
mdl.getProject().addRepository(repo);
path = "lib"; //NOI18N
}
assert path != null;
FileObject root = FileUtil.createFolder(project.getProjectDirectory(), path);
FileObject grp = FileUtil.createFolder(root, dep.getGroupId().replace('.', '/')); //NOI18N
FileObject art = FileUtil.createFolder(grp, dep.getArtifactId());
FileObject ver = FileUtil.createFolder(art, dep.getVersion());
String name = dep.getArtifactId() + '-' + dep.getVersion();
FileObject file = FileUtil.toFileObject(jar);
if (ver.getFileObject(name, file.getExt()) == null) { //#160803
FileUtil.copyFile(file, ver, name, file.getExt());
}
}
}