Skip to content

Commit

Permalink
Add osgi-repository target location type
Browse files Browse the repository at this point in the history
The OSGi Specification defines a XML format for representing a
repository as defined in "132 Repository Service Specification" that
allows to hold "Resources" with requirements and capabilities.

Such a repository usually contains a set of bundles and is very similar
to what we have today with P2 updatesites, to improve interfacing of PDE
with such repositories this adds a new target location type "Repository"
where content can be used as usual.
  • Loading branch information
laeubi committed Aug 13, 2023
1 parent 161d862 commit f870248
Show file tree
Hide file tree
Showing 17 changed files with 1,091 additions and 7 deletions.
1 change: 1 addition & 0 deletions ui/org.eclipse.pde.core/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ Export-Package:
org.eclipse.pde.unittest.junit",
org.eclipse.pde.internal.core.variables;x-internal:=true
Import-Package: aQute.bnd.build;version="[4.4.0,5.0.0)",
aQute.bnd.header;version="2.5.0",
aQute.bnd.osgi;version="[5.5.0,6.0.0)",
aQute.bnd.osgi.repository;version="[3.0.0,4.0.0)",
aQute.bnd.osgi.resource;version="[4.3.0,5.0.0)",
Expand Down
8 changes: 8 additions & 0 deletions ui/org.eclipse.pde.core/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -437,4 +437,12 @@
class="org.eclipse.pde.internal.core.LocalMavenPluginSourcePathLocator"
complexity="low"/>
</extension>
<extension
point="org.eclipse.pde.core.targetLocations">
<targetLocation
canUpdate="true"
locationFactory="org.eclipse.pde.internal.core.target.RepositoryLocationFactory"
type="Repository">
</targetLocation>
</extension>
</plugin>
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/*******************************************************************************
* Copyright (c) 2023 Christoph Läubrich and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Christoph Läubrich - initial API and implementation
*******************************************************************************/
package org.eclipse.pde.internal.core.target;

import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.equinox.internal.p2.repository.CacheManager;
import org.eclipse.pde.core.target.ITargetDefinition;
import org.eclipse.pde.core.target.TargetBundle;
import org.eclipse.pde.core.target.TargetFeature;
import org.eclipse.pde.internal.core.PDECore;
import org.eclipse.pde.internal.core.util.PDEXmlProcessorFactory;
import org.osgi.resource.Capability;
import org.osgi.resource.Requirement;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import aQute.bnd.osgi.repository.ResourcesRepository;
import aQute.bnd.osgi.repository.XMLResourceParser;
import aQute.bnd.osgi.resource.ResourceUtils;
import aQute.bnd.osgi.resource.ResourceUtils.ContentCapability;

@SuppressWarnings("restriction")
public class RepositoryBundleContainer extends AbstractBundleContainer {
public static final String ATTRIBUTE_URI = "uri"; //$NON-NLS-1$

public static final String ELEMENT_REQUIRE = "require"; //$NON-NLS-1$

public static final String TYPE = "Repository"; //$NON-NLS-1$

private final String uri;

private final Collection<Requirement> requirements;

public RepositoryBundleContainer(String uri, Collection<Requirement> requirements) {
this.uri = uri;
this.requirements = requirements;
}

@Override
protected TargetBundle[] resolveBundles(ITargetDefinition definition, IProgressMonitor monitor)
throws CoreException {
ResourcesRepository repository = getRepository(monitor);
Map<Requirement, Collection<Capability>> providers = repository.findProviders(getRequirements());
List<TargetBundle> bundles = new ArrayList<>();
List<ContentCapability> contentCapabilities = providers.values().stream().flatMap(Collection::stream)
.map(Capability::getResource).distinct().map(ResourceUtils::getContentCapability)
.filter(Objects::nonNull).toList();
CacheManager cacheManager = getCacheManager();
for (ContentCapability content : contentCapabilities) {
URI url = content.url();
try {
File file;
if (cacheManager != null) {
file = cacheManager.createCacheFromFile(url, monitor);
} else {
file = new File(FileLocator.toFileURL(url.toURL()).toURI());
}
bundles.add(new TargetBundle(file));
} catch (IOException | URISyntaxException e) {
throw new CoreException(Status.error("Can't fetch bundle from " + url, e));
}
}
return bundles.toArray(TargetBundle[]::new);
}

public String getUri() {
return uri;
}

public Collection<Requirement> getRequirements() {
return requirements;
}

public ResourcesRepository getRepository(IProgressMonitor monitor) throws CoreException {
String location = getLocation(true);
try {
URI base = new URI(location);
try {
CacheManager cacheManager = getCacheManager();
if (cacheManager != null) {
File file = cacheManager.createCacheFromFile(base, monitor);
return new ResourcesRepository(XMLResourceParser.getResources(file, base));
}
return new ResourcesRepository(XMLResourceParser.getResources(base));
} catch (Exception e) {
if (e instanceof CoreException core) {
throw core;
}
if (e instanceof RuntimeException runtime) {
throw runtime;
}
throw new CoreException(Status.error("Loading repository from " + location + " failed: " + e, e));
}
} catch (URISyntaxException e) {
throw new CoreException(Status.error("Invalid URI: " + location, e));
}
}

private CacheManager getCacheManager() throws CoreException {
return P2TargetUtils.getAgent().getService(CacheManager.class);
}

@Override
protected TargetFeature[] resolveFeatures(ITargetDefinition definition, IProgressMonitor monitor)
throws CoreException {
return new TargetFeature[0];
}

@Override
public String getType() {
return TYPE;
}

@Override
public String getLocation(boolean resolve) throws CoreException {
if (resolve) {
return resolveVariables(uri);
}
return uri;
}

@Override
public String serialize() {
try {
DocumentBuilder docBuilder = PDEXmlProcessorFactory.createDocumentBuilderWithErrorOnDOCTYPE();
Document document = docBuilder.newDocument();
Element containerElement = document.createElement(TargetDefinitionPersistenceHelper.LOCATION);
containerElement.setAttribute(TargetDefinitionPersistenceHelper.ATTR_LOCATION_TYPE, TYPE);
containerElement.setAttribute(ATTRIBUTE_URI, getUri());
for (Requirement requirement : requirements) {
Element requireElement = document.createElement(ELEMENT_REQUIRE);
requireElement.setTextContent(requirement.toString());
containerElement.appendChild(requireElement);
}
document.appendChild(containerElement);
StreamResult result = new StreamResult(new StringWriter());
TransformerFactory f = PDEXmlProcessorFactory.createTransformerFactoryWithErrorOnDOCTYPE();
Transformer transformer = f.newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); //$NON-NLS-1$
transformer.transform(new DOMSource(document), result);
String xml = result.getWriter().toString();
return xml;
} catch (Exception e) {
PDECore.log(e);
return null;
}
}

public void reload() {
fResolutionStatus = null;
fBundles = null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*******************************************************************************
* Copyright (c) 2023 Christoph Läubrich and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Christoph Läubrich - initial API and implementation
*******************************************************************************/
package org.eclipse.pde.internal.core.target;

import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.stream.IntStream;

import javax.xml.parsers.DocumentBuilder;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.Status;
import org.eclipse.osgi.util.NLS;
import org.eclipse.pde.core.target.ITargetLocation;
import org.eclipse.pde.core.target.ITargetLocationFactory;
import org.eclipse.pde.internal.core.util.PDEXmlProcessorFactory;
import org.osgi.resource.Requirement;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import aQute.bnd.header.Parameters;
import aQute.bnd.osgi.resource.CapReqBuilder;

public class RepositoryLocationFactory implements ITargetLocationFactory {

@Override
public ITargetLocation getTargetLocation(String type, String serializedXML) throws CoreException {
if (!RepositoryBundleContainer.TYPE.equals(type)) {
throw new CoreException(
Status.error(NLS.bind(Messages.TargetRefrenceLocationFactory_Unsupported_Type, type)));
}
try {
DocumentBuilder docBuilder = PDEXmlProcessorFactory.createDocumentBuilderWithErrorOnDOCTYPE();
Document document = docBuilder
.parse(new ByteArrayInputStream(serializedXML.getBytes(StandardCharsets.UTF_8)));
Element location = document.getDocumentElement();
NodeList childNodes = location.getChildNodes();
List<Requirement> requirements = IntStream.range(0, childNodes.getLength()).mapToObj(childNodes::item)
.filter(Element.class::isInstance).map(Element.class::cast)
.filter(element -> element.getNodeName()
.equalsIgnoreCase(RepositoryBundleContainer.ELEMENT_REQUIRE))
.flatMap(element -> {
String textContent = element.getTextContent();
Parameters parameters = new Parameters(textContent);
return CapReqBuilder.getRequirementsFrom(parameters).stream();
}).toList();
return new RepositoryBundleContainer(
location.getAttribute(RepositoryBundleContainer.ATTRIBUTE_URI), requirements);
} catch (Exception e) {
throw new CoreException(
Status.error(NLS.bind(Messages.TargetRefrenceLocationFactory_Parsing_Failed, e.getMessage()), e));
}
}

}
4 changes: 3 additions & 1 deletion ui/org.eclipse.pde.ui/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,9 @@ Require-Bundle:
biz.aQute.bndlib;bundle-version="6.3.1"
Import-Package: org.eclipse.jdt.debug.ui.console,
org.eclipse.ui.internal.genericeditor,
org.osgi.service.event;version="[1.4,2.0.0)"
org.osgi.service.event;version="[1.4,2.0.0)",
org.osgi.service.repository;version="1.1.0",
org.osgi.util.promise;version="1.3.0"
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Automatic-Module-Name: org.eclipse.pde.ui
Binary file added ui/org.eclipse.pde.ui/icons/dview16/memory_view.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 4 additions & 2 deletions ui/org.eclipse.pde.ui/plugin.properties
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,8 @@ projectConfigurator.label.bundle = Eclipse Plugin/OSGi Bundle
projectConfigurator.label.feature = Eclipse Feature
create.module.info.label.pde= Create module-info.java

locationProvider.reference.name = Target File
locationProvider.reference.description = Add a reference to another target file

locationProvider.description = Add a reference to another target file
locationProvider.name = Target File
locationProvider.repository.name = OSGi Repository
locationProvider.repository.description = Add content from an OSGi Repository according to the Repository Service Specification
39 changes: 37 additions & 2 deletions ui/org.eclipse.pde.ui/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2230,6 +2230,32 @@
type="org.eclipse.jface.viewers.ITreeContentProvider">
</adapter>
</factory>
<factory
adaptableType="org.eclipse.pde.internal.core.target.RepositoryBundleContainer"
class="org.eclipse.pde.internal.ui.shared.target.RepositoryBundleContainerAdapterFactory">
<adapter
type="org.eclipse.pde.ui.target.ITargetLocationHandler">
</adapter>
<adapter
type="org.eclipse.jface.viewers.ILabelProvider">
</adapter>
<adapter
type="org.eclipse.jface.viewers.ITreeContentProvider">
</adapter>
</factory>
<factory
adaptableType="org.eclipse.pde.internal.ui.shared.target.RepositoryBundleContainerAdapterFactory$RequirementNode"
class="org.eclipse.pde.internal.ui.shared.target.RepositoryBundleContainerAdapterFactory">
<adapter
type="org.eclipse.pde.ui.target.ITargetLocationHandler">
</adapter>
<adapter
type="org.eclipse.jface.viewers.ILabelProvider">
</adapter>
<adapter
type="org.eclipse.jface.viewers.ITreeContentProvider">
</adapter>
</factory>
</extension>
<extension
point="org.eclipse.pde.ui.targetLocationProvisioners">
Expand All @@ -2246,9 +2272,18 @@
class="org.eclipse.pde.internal.ui.shared.target.TargetReferenceLocationWizard"
icon="icons/obj16/target_profile_xml_obj.png"
id="org.eclipse.pde.ui.TargetReferenceProvisioner"
name="%locationProvider.name">
name="%locationProvider.reference.name">
<description>
%locationProvider.reference.description
</description>
</locationProvider>
<locationProvider
class="org.eclipse.pde.internal.ui.shared.target.RepositoryLocationWizard"
icon="icons/dview16/memory_view.png"
id="org.eclipse.pde.ui.TargetReferenceProvisioner"
name="%locationProvider.repository.name">
<description>
%locationProvider.description
%locationProvider.repository.description
</description>
</locationProvider>
</extension>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.pde.internal.core.PDEPreferencesManager;
import org.eclipse.pde.internal.ui.launcher.PDELogFileProvider;
import org.eclipse.pde.internal.ui.shared.target.RepositoryBundleContainerAdapterFactory;
import org.eclipse.pde.internal.ui.shared.target.TargetReferenceBundleContainerAdapterFactory;
import org.eclipse.pde.internal.ui.shared.target.TargetStatus;
import org.eclipse.pde.internal.ui.util.SWTUtil;
Expand Down Expand Up @@ -233,6 +234,7 @@ public void stop(BundleContext context) throws Exception {
Utilities.shutdown();
super.stop(context);
TargetReferenceBundleContainerAdapterFactory.LABEL_PROVIDER.dispose();
RepositoryBundleContainerAdapterFactory.LABEL_PROVIDER.dispose();
}

public PDELabelProvider getLabelProvider() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public class PDEPluginImages {

private static final String PATH_OBJ = ICONS_PATH + "obj16/"; //$NON-NLS-1$
private static final String PATH_VIEW = ICONS_PATH + "view16/"; //$NON-NLS-1$
private static final String PATH_DVIEW = ICONS_PATH + "dview16/"; //$NON-NLS-1$
private static final String PATH_LCL = ICONS_PATH + "elcl16/"; //$NON-NLS-1$
private static final String PATH_LCL_DISABLED = ICONS_PATH + "dlcl16/"; //$NON-NLS-1$
private static final String PATH_TOOL = ICONS_PATH + "etool16/"; //$NON-NLS-1$
Expand Down Expand Up @@ -316,6 +317,7 @@ public class PDEPluginImages {
* View
*/
public static final ImageDescriptor DESC_ARGUMENT_TAB = create(PATH_VIEW, "variable_tab.png"); //$NON-NLS-1$
public static final ImageDescriptor DESC_TARGET_REPO = create(PATH_DVIEW, "memory_view.png"); //$NON-NLS-1$

private static ImageDescriptor create(String prefix, String name) {
return ImageDescriptor.createFromURL(makeImageURL(prefix, name));
Expand Down
Loading

0 comments on commit f870248

Please sign in to comment.