| /* |
| * 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.ivyde.internal.eclipse.workspaceresolver; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.text.ParseException; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.ivy.Ivy; |
| import org.apache.ivy.core.IvyContext; |
| import org.apache.ivy.core.module.descriptor.Artifact; |
| import org.apache.ivy.core.module.descriptor.Configuration; |
| import org.apache.ivy.core.module.descriptor.DefaultArtifact; |
| import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor; |
| import org.apache.ivy.core.module.descriptor.DependencyArtifactDescriptor; |
| import org.apache.ivy.core.module.descriptor.DependencyDescriptor; |
| import org.apache.ivy.core.module.descriptor.ExcludeRule; |
| import org.apache.ivy.core.module.descriptor.License; |
| import org.apache.ivy.core.module.descriptor.MDArtifact; |
| import org.apache.ivy.core.module.descriptor.ModuleDescriptor; |
| import org.apache.ivy.core.module.id.ModuleId; |
| import org.apache.ivy.core.module.id.ModuleRevisionId; |
| import org.apache.ivy.core.report.ArtifactDownloadReport; |
| import org.apache.ivy.core.report.DownloadReport; |
| import org.apache.ivy.core.report.DownloadStatus; |
| import org.apache.ivy.core.report.MetadataArtifactDownloadReport; |
| import org.apache.ivy.core.resolve.DownloadOptions; |
| import org.apache.ivy.core.resolve.ResolveData; |
| import org.apache.ivy.core.resolve.ResolvedModuleRevision; |
| import org.apache.ivy.core.settings.IvySettings; |
| import org.apache.ivy.osgi.core.BundleInfo; |
| import org.apache.ivy.osgi.core.ManifestHeaderElement; |
| import org.apache.ivy.osgi.core.ManifestHeaderValue; |
| import org.apache.ivy.plugins.resolver.AbstractResolver; |
| import org.apache.ivy.plugins.resolver.util.ResolvedResource; |
| import org.apache.ivy.plugins.version.VersionMatcher; |
| import org.apache.ivy.util.Message; |
| import org.apache.ivyde.eclipse.cp.IvyClasspathContainer; |
| import org.apache.ivyde.eclipse.cp.IvyClasspathContainerHelper; |
| import org.apache.ivyde.internal.eclipse.IvyDEMessage; |
| import org.apache.ivyde.internal.eclipse.IvyPlugin; |
| import org.apache.ivyde.internal.eclipse.cpcontainer.IvyClasspathContainerImpl; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| |
| /** |
| * This is an Eclipse workspace Ivy resolver. When used with the custom IvyClasspathContainer |
| * changes, this resolver will link dependent projects when they are open in the same workspace, |
| * allowing full-fledged linked project functionality Eclipse provides, such as incremental |
| * compilation, debugging, mouseover javadocs, and source browsing across projects. |
| * |
| * <b>How it works</b> During a resolve, it looks at all open projects in the workspace that have |
| * Ivy containers. The <b>first</b> project that publishes the module on which the project being |
| * resolved depends, will be picked and returned as a special type of artifact called "project". |
| * |
| * The IvyClasspathContainer will recognize the artifact as a project and put the eclipse project as |
| * a dependent project within the classpath container of the parent. |
| * |
| * If you do not want a project to be linked as a dependency, close it or delete from the workspace. |
| * As soon as you do that, any projects that were linked to it will automatically re-resolve (see |
| * {@link WorkspaceResourceChangeListener}) and use the standard Ivy means of finding the |
| * dependency. |
| * |
| * The {@link WorkspaceResourceChangeListener} will also auto-resolve when a new project is added or |
| * opened, so opening a project will automatically link it into the currently open projects where |
| * necessary. |
| * |
| * Since the resolver is not aware which module revision a project is publishing, it optimistically |
| * matches any revision of the module. |
| * |
| * Since the resolver stops after finding the first open project which matches the module, having |
| * multiple open versions of the same project in the workspace (for example, different branches) may |
| * set the wrong version as a dependency. You are advised to only open the version of the project |
| * which you want other projects in the workspace to depend on. |
| * |
| * NOTE: Transitive dependencies are not passed from the dependent project to the parent when |
| * projects are linked. If you find you are missing some transitive dependencies, just set your |
| * dependent eclipse project to export its ivy dependencies. (Project->Properties->Java Build |
| * Path->Order and Export-> -> check the ivy container) This will only export the configuration that |
| * project is using and not what a dependent project may ask for when it's being resolved. To do |
| * that, this resolver will need to be modified to pass transitive dependencies along. |
| */ |
| public class WorkspaceResolver extends AbstractResolver { |
| |
| public static final String ECLIPSE_PROJECT_TYPE = "eclipse-project"; |
| |
| public static final String ECLIPSE_PROJECT_EXTENSION = "eclipse-project"; |
| |
| public static final String CACHE_NAME = "__ivyde-workspace-resolver-cache"; |
| |
| public static final String IVYDE_WORKSPACE_ARTIFACTS = "IvyDEWorkspaceArtifacts"; |
| |
| public static final String IVYDE_WORKSPACE_ARTIFACT_REPORTS = "IvyDEWorkspaceArtifactReports"; |
| |
| private IProject[] projects; |
| |
| private boolean ignoreBranchOnWorkspaceProjects; |
| |
| private boolean ignoreVersionOnWorkspaceProjects; |
| |
| private boolean osgiResolveInWorkspaceAvailable; |
| |
| public WorkspaceResolver(IProject project, IvySettings ivySettings) { |
| String projectName = project == null ? "<null>" : project.getName(); |
| setName(projectName + "-ivyde-workspace-resolver"); |
| setSettings(ivySettings); |
| setCache(CACHE_NAME); |
| |
| projects = ResourcesPlugin.getWorkspace().getRoot().getProjects(); |
| |
| ignoreBranchOnWorkspaceProjects = IvyPlugin.getPreferenceStoreHelper() |
| .getIgnoreBranchOnWorkspaceProjects(); |
| |
| ignoreVersionOnWorkspaceProjects = IvyPlugin.getPreferenceStoreHelper() |
| .getIgnoreVersionOnWorkspaceProjects(); |
| |
| osgiResolveInWorkspaceAvailable = IvyPlugin.getDefault() |
| .isIvyVersionGreaterOrEqual(2, 4, 0); |
| } |
| |
| public DownloadReport download(Artifact[] artifacts, DownloadOptions options) { |
| IvyContext context = IvyContext.getContext(); |
| @SuppressWarnings("unchecked") |
| Map<Artifact, Artifact> workspaceArtifacts = (Map<Artifact, Artifact>) context |
| .get(IVYDE_WORKSPACE_ARTIFACTS); |
| Map<Artifact, ArtifactDownloadReport> workspaceReports = null; |
| if (workspaceArtifacts != null) { |
| workspaceReports = new HashMap<Artifact, ArtifactDownloadReport>(); |
| context.set(IVYDE_WORKSPACE_ARTIFACT_REPORTS, workspaceReports); |
| } |
| |
| // Not much to do here - downloads are not required for workspace projects. |
| DownloadReport dr = new DownloadReport(); |
| for (int i = 0; i < artifacts.length; i++) { |
| ArtifactDownloadReport adr = new ArtifactDownloadReport(artifacts[i]); |
| dr.addArtifactReport(adr); |
| // Only report java projects as downloaded |
| if (artifacts[i].getType().equals(ECLIPSE_PROJECT_TYPE)) { |
| adr.setDownloadStatus(DownloadStatus.NO); |
| adr.setSize(0); |
| Message.verbose("\t[IN WORKSPACE] " + artifacts[i]); |
| } else if (workspaceArtifacts != null && workspaceArtifacts.containsKey(artifacts[i])) { |
| adr.setDownloadStatus(DownloadStatus.NO); |
| adr.setSize(0); |
| // there is some 'forced' artifact by the dependency descriptor |
| Artifact eclipseArtifact = (Artifact) workspaceArtifacts.get(artifacts[i]); |
| ArtifactDownloadReport eclipseAdr = new ArtifactDownloadReport(eclipseArtifact); |
| eclipseAdr.setDownloadStatus(DownloadStatus.NO); |
| eclipseAdr.setSize(0); |
| workspaceReports.put(artifacts[i], eclipseAdr); |
| Message.verbose("\t[IN WORKSPACE] " + eclipseArtifact); |
| } else { |
| adr.setDownloadStatus(DownloadStatus.FAILED); |
| Message.verbose("\t[Eclipse Workspace resolver] " |
| + "cannot download non-project artifact: " + artifacts[i]); |
| } |
| } |
| return dr; |
| } |
| |
| public ResolvedModuleRevision getDependency(DependencyDescriptor dd, ResolveData data) |
| throws ParseException { |
| IvyContext context = IvyContext.getContext(); |
| |
| String contextId = "ivyde.workspaceresolver." + getName() + "." |
| + dd.getDependencyRevisionId().toString(); |
| DependencyDescriptor parentDD = (DependencyDescriptor) context.get(contextId); |
| if (parentDD != null |
| && parentDD.getDependencyRevisionId().equals(dd.getDependencyRevisionId())) { |
| // this very workspace resolver has been already called for the same dependency |
| // we are going into a circular dependency, let's return 'not found' |
| return null; |
| } |
| context.set(contextId, dd); |
| |
| ModuleRevisionId dependencyMrid = dd.getDependencyRevisionId(); |
| String org = dependencyMrid.getModuleId().getOrganisation(); |
| String module = dependencyMrid.getModuleId().getName(); |
| |
| VersionMatcher versionMatcher = getSettings().getVersionMatcher(); |
| |
| // Iterate over workspace to find Java project which has an Ivy |
| // container for this dependency |
| for (int i = 0; i < projects.length; i++) { |
| IProject p = projects[i]; |
| if (!p.exists()) { |
| continue; |
| } |
| List<IvyClasspathContainer> containers = IvyClasspathContainerHelper.getContainers(p); |
| Iterator<IvyClasspathContainer> itContainer = containers.iterator(); |
| while (itContainer.hasNext()) { |
| IvyClasspathContainerImpl ivycp = (IvyClasspathContainerImpl) itContainer.next(); |
| ModuleDescriptor md = ivycp.getState().getCachedModuleDescriptor(); |
| if (md == null) { |
| continue; |
| } |
| ModuleRevisionId candidateMrid = md.getModuleRevisionId(); |
| |
| // search a match on the organization and the module name |
| |
| if (osgiResolveInWorkspaceAvailable && org.equals(BundleInfo.BUNDLE_TYPE)) { |
| // looking for an OSGi bundle via its symbolic name |
| String sn = md.getExtraInfoContentByTagName("Bundle-SymbolicName"); |
| if (sn == null || !module.equals(sn)) { |
| // not found, skip to next |
| continue; |
| } |
| } else if (osgiResolveInWorkspaceAvailable && org.equals(BundleInfo.PACKAGE_TYPE)) { |
| // looking for an OSGi bundle via its exported package |
| String exportedPackages = md.getExtraInfoContentByTagName("Export-Package"); |
| if (exportedPackages == null) { |
| // not found, skip to next |
| continue; |
| } |
| boolean found = false; |
| String version = null; |
| ManifestHeaderValue exportElements = new ManifestHeaderValue(exportedPackages); |
| for (ManifestHeaderElement exportElement : exportElements.getElements()) { |
| if (exportElement.getValues().contains(module)) { |
| found = true; |
| version = exportElement.getAttributes().get("version"); |
| break; |
| } |
| } |
| if (!found) { |
| // not found, skip to next |
| continue; |
| } |
| if (version == null) { |
| // no version means anything can match. Let's trick the version matcher by |
| // setting the exact expected version |
| version = dependencyMrid.getRevision(); |
| } |
| md.setResolvedModuleRevisionId(ModuleRevisionId.newInstance(org, module, |
| version)); |
| } else { |
| if (!candidateMrid.getModuleId().equals(dependencyMrid.getModuleId())) { |
| // it doesn't match org#module, skip to next |
| continue; |
| } |
| } |
| |
| IvyDEMessage.verbose("Workspace resolver found potential matching project " |
| + p.getName() + " with module " + candidateMrid + " for module " |
| + dependencyMrid); |
| |
| if (!ignoreBranchOnWorkspaceProjects) { |
| ModuleId mid = dependencyMrid.getModuleId(); |
| String defaultBranch = getSettings().getDefaultBranch(mid); |
| String dependencyBranch = dependencyMrid.getBranch(); |
| String candidateBranch = candidateMrid.getBranch(); |
| if (dependencyBranch == null) { |
| dependencyBranch = defaultBranch; |
| } |
| if (candidateBranch == null) { |
| candidateBranch = defaultBranch; |
| } |
| if (dependencyBranch != candidateBranch) { |
| // Both cannot be null |
| if (dependencyBranch == null || candidateBranch == null) { |
| IvyDEMessage |
| .verbose("\t\trejected since branches doesn't match (one is set, the other isn't)"); |
| continue; |
| } |
| if (!dependencyBranch.equals(candidateBranch)) { |
| IvyDEMessage.verbose("\t\trejected since branches doesn't match"); |
| continue; |
| } |
| } |
| } |
| |
| // Found one; check if it is for the module we need |
| if (ignoreVersionOnWorkspaceProjects |
| || md.getModuleRevisionId().getRevision().equals(Ivy.getWorkingRevision()) |
| || versionMatcher.accept(dd.getDependencyRevisionId(), md)) { |
| |
| if (ignoreVersionOnWorkspaceProjects) { |
| IvyDEMessage.verbose("\t\tmatched (version are ignored)"); |
| } else { |
| IvyDEMessage.verbose("\t\tversion matched"); |
| } |
| |
| Artifact af = new DefaultArtifact(md.getModuleRevisionId(), |
| md.getPublicationDate(), p.getFullPath().toString(), |
| ECLIPSE_PROJECT_TYPE, ECLIPSE_PROJECT_EXTENSION); |
| |
| DependencyArtifactDescriptor[] dArtifacts = dd.getAllDependencyArtifacts(); |
| if (dArtifacts != null) { |
| // the dependency is declaring explicitely some artifacts to download |
| // we need to trick to and map these requested artifact by the Eclipse |
| // project |
| |
| // we need the context which is used when downloading data, which is the |
| // parent |
| // of the current one |
| // so let's hack: popContext (the child), getContext (the parent), setVar, |
| // pushContext (child) |
| IvyContext currentContext = IvyContext.popContext(); |
| IvyContext parentContext = IvyContext.getContext(); |
| @SuppressWarnings("unchecked") |
| Map<Artifact, Artifact> workspaceArtifacts = (Map<Artifact, Artifact>) parentContext |
| .get(IVYDE_WORKSPACE_ARTIFACTS); |
| if (workspaceArtifacts == null) { |
| workspaceArtifacts = new HashMap<Artifact, Artifact>(); |
| parentContext.set(IVYDE_WORKSPACE_ARTIFACTS, workspaceArtifacts); |
| } |
| for (int j = 0; j < dArtifacts.length; j++) { |
| Artifact artifact = new MDArtifact(md, dArtifacts[j].getName(), |
| dArtifacts[j].getType(), dArtifacts[j].getExt(), |
| dArtifacts[j].getUrl(), |
| dArtifacts[j].getQualifiedExtraAttributes()); |
| workspaceArtifacts.put(artifact, af); |
| } |
| IvyContext.pushContext(currentContext); |
| } |
| |
| DefaultModuleDescriptor workspaceMd = cloneMd(md, af); |
| |
| MetadataArtifactDownloadReport madr = new MetadataArtifactDownloadReport(af); |
| madr.setDownloadStatus(DownloadStatus.SUCCESSFUL); |
| madr.setSearched(true); |
| |
| return new ResolvedModuleRevision(this, this, workspaceMd, madr); |
| } else { |
| IvyDEMessage.verbose("\t\treject as version didn't match"); |
| } |
| } |
| } |
| |
| // Didn't find module in any open project, proceed to other resolvers. |
| return null; |
| } |
| |
| private DefaultModuleDescriptor cloneMd(ModuleDescriptor md, Artifact af) { |
| |
| DefaultModuleDescriptor newMd = new DefaultModuleDescriptor(md.getModuleRevisionId(), |
| "release", null, true); |
| newMd.addConfiguration(new Configuration(ModuleDescriptor.DEFAULT_CONFIGURATION)); |
| newMd.setLastModified(System.currentTimeMillis()); |
| |
| newMd.setDescription(md.getDescription()); |
| newMd.setHomePage(md.getHomePage()); |
| newMd.setLastModified(md.getLastModified()); |
| newMd.setPublicationDate(md.getPublicationDate()); |
| newMd.setResolvedPublicationDate(md.getResolvedPublicationDate()); |
| newMd.setStatus(md.getStatus()); |
| |
| Configuration[] allConfs = md.getConfigurations(); |
| if (allConfs.length == 0) { |
| newMd.addArtifact(ModuleDescriptor.DEFAULT_CONFIGURATION, af); |
| } else { |
| for (int k = 0; k < allConfs.length; k++) { |
| newMd.addConfiguration(allConfs[k]); |
| newMd.addArtifact(allConfs[k].getName(), af); |
| } |
| } |
| |
| DependencyDescriptor[] dependencies = md.getDependencies(); |
| for (int k = 0; k < dependencies.length; k++) { |
| newMd.addDependency(dependencies[k]); |
| } |
| |
| ExcludeRule[] allExcludeRules = md.getAllExcludeRules(); |
| for (int k = 0; k < allExcludeRules.length; k++) { |
| newMd.addExcludeRule(allExcludeRules[k]); |
| } |
| |
| newMd.getExtraInfos().addAll(md.getExtraInfos()); |
| |
| License[] licenses = md.getLicenses(); |
| for (int k = 0; k < licenses.length; k++) { |
| newMd.addLicense(licenses[k]); |
| } |
| |
| return newMd; |
| } |
| |
| public void publish(Artifact artifact, File src, boolean overwrite) throws IOException { |
| throw new UnsupportedOperationException("publish not supported by " + getName()); |
| } |
| |
| public ResolvedResource findIvyFileRef(DependencyDescriptor dd, ResolveData data) { |
| return null; |
| } |
| } |