You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by nb...@apache.org on 2018/03/08 14:57:58 UTC

svn commit: r1826244 [3/3] - in /felix/trunk: ./ fileinstall-plugins/ fileinstall-plugins/installer.test/ fileinstall-plugins/installer.test/src/ fileinstall-plugins/installer.test/src/test/ fileinstall-plugins/installer.test/src/test/java/ fileinstall...

Added: felix/trunk/fileinstall-plugins/resolver/src/main/java/org/apache/felix/fileinstall/plugins/resolver/impl/Path.java
URL: http://svn.apache.org/viewvc/felix/trunk/fileinstall-plugins/resolver/src/main/java/org/apache/felix/fileinstall/plugins/resolver/impl/Path.java?rev=1826244&view=auto
==============================================================================
--- felix/trunk/fileinstall-plugins/resolver/src/main/java/org/apache/felix/fileinstall/plugins/resolver/impl/Path.java (added)
+++ felix/trunk/fileinstall-plugins/resolver/src/main/java/org/apache/felix/fileinstall/plugins/resolver/impl/Path.java Thu Mar  8 14:57:57 2018
@@ -0,0 +1,119 @@
+/*******************************************************************************
+ * Copyright (c) Intel Corporation
+ * Copyright (c) 2017
+ *
+ * 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.apache.felix.fileinstall.plugins.resolver.impl;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+final class Path {
+
+	public static final String SLASH = "/";
+	public static final String GO_UP = "..";
+
+	private final List<String> parts;
+
+	private Path(List<String> parts) {
+		this.parts = Collections.unmodifiableList(parts);
+	}
+
+	public static Path parse(String string) {
+		String[] splits = string.split(SLASH);
+		List<String> parts = new ArrayList<String>(splits.length);
+		for (String split : splits) {
+			if (split.length() > 0) {
+                parts.add(split);
+            }
+		}
+		return new Path(parts);
+	}
+
+	@Override
+	public String toString() {
+		StringBuilder builder = new StringBuilder();
+		boolean first = true;
+		for (String part : this.parts) {
+			if (!first) {
+                builder.append(SLASH);
+            }
+			builder.append(part);
+			first = false;
+		}
+		return builder.toString();
+	}
+
+	public Path append(String pathString) {
+		return append(parse(pathString));
+	}
+
+	public Path append(Path path) {
+		List<String> newParts = new ArrayList<String>(this.parts.size() + path.parts.size());
+		newParts.addAll(this.parts);
+
+		boolean atRoot = newParts.isEmpty();
+		for (String part : path.parts) {
+			if (GO_UP.equals(part)) {
+				if (atRoot) {
+					newParts.add(GO_UP);
+				} else {
+					newParts.remove(newParts.size() - 1);
+					atRoot = newParts.isEmpty();
+				}
+			} else {
+				newParts.add(part);
+			}
+		}
+
+		return new Path(newParts);
+	}
+
+	public boolean isEmpty() {
+		return this.parts.isEmpty();
+	}
+
+	public int count() {
+		return this.parts.size();
+	}
+
+	public Path parent() {
+		ArrayList<String> newParts = new ArrayList<String>(this.parts);
+		if (newParts.isEmpty()) {
+            newParts.add(GO_UP);
+        } else {
+            newParts.remove(newParts.size() - 1);
+        }
+		return new Path(newParts);
+	}
+
+	public String head() {
+		if (this.parts.isEmpty()) {
+            throw new IllegalArgumentException("Cannot get head of empty path");
+        }
+		return this.parts.get(0);
+	}
+
+	public Path tail() {
+		if (this.parts.isEmpty()) {
+            throw new IllegalArgumentException("Cannot get tail of empty path");
+        }
+
+		ArrayList<String> newParts = new ArrayList<String>(this.parts);
+		newParts.remove(0);
+		return new Path(newParts);
+	}
+
+}

Added: felix/trunk/fileinstall-plugins/resolver/src/main/java/org/apache/felix/fileinstall/plugins/resolver/impl/PluginResolveContext.java
URL: http://svn.apache.org/viewvc/felix/trunk/fileinstall-plugins/resolver/src/main/java/org/apache/felix/fileinstall/plugins/resolver/impl/PluginResolveContext.java?rev=1826244&view=auto
==============================================================================
--- felix/trunk/fileinstall-plugins/resolver/src/main/java/org/apache/felix/fileinstall/plugins/resolver/impl/PluginResolveContext.java (added)
+++ felix/trunk/fileinstall-plugins/resolver/src/main/java/org/apache/felix/fileinstall/plugins/resolver/impl/PluginResolveContext.java Thu Mar  8 14:57:57 2018
@@ -0,0 +1,338 @@
+/*******************************************************************************
+ * Copyright (c) Intel Corporation
+ * Copyright (c) 2017
+ *
+ * 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.apache.felix.fileinstall.plugins.resolver.impl;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.felix.fileinstall.plugins.resolver.ResolveRequest;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.namespace.IdentityNamespace;
+import org.osgi.framework.wiring.BundleRevision;
+import org.osgi.framework.wiring.BundleWiring;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Namespace;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+import org.osgi.resource.Wiring;
+import org.osgi.service.log.LogService;
+import org.osgi.service.repository.ContentNamespace;
+import org.osgi.service.repository.Repository;
+import org.osgi.service.resolver.HostedCapability;
+import org.osgi.service.resolver.ResolveContext;
+
+import aQute.bnd.deployer.repository.FixedIndexedRepo;
+import aQute.bnd.service.url.URLConnector;
+
+class PluginResolveContext extends ResolveContext {
+
+    private static final String INITIAL_RESOURCE_CAPABILITY_NAMESPACE = "__initial";
+    private static final String MIME_BUNDLE = "application/vnd.osgi.bundle";
+
+    private final BundleContext bundleContext;
+
+    // The repositories that will be queries for providers
+    private final Map<URI, Repository> repositories = new HashMap<>();
+    // A cache of resource->location (URL), generated during resolve and queried
+    // after resolve in order to fetch the resource.
+    private final Map<Resource, String> resourceLocationMap = new IdentityHashMap<>();
+    // A cache of resources to the repositories which own them; used from
+    // insertHostedCapability method.
+    private final Map<Resource, Repository> resourceRepositoryMap = new IdentityHashMap<>();
+
+    private final ResourceImpl initialResource;
+    private final LogService log;
+
+    PluginResolveContext(BundleContext bundleContext, ResolveRequest request, LogService log) throws IOException {
+        this.bundleContext = bundleContext;
+        this.log = log;
+
+        this.initialResource = new ResourceImpl();
+        for (Requirement requirement : request.getRequirements()) {
+            this.initialResource.addRequirement(requirement);
+        }
+        this.initialResource.addCapability(createIdentityCap(this.initialResource, request.getName()));
+        this.initialResource.addCapability(createInitialMarkerCapability(this.initialResource));
+
+        BasicRegistry registry = new BasicRegistry().put(URLConnector.class, new JarURLConnector());
+
+        for (URI indexUri : request.getIndexes()) {
+            // URI cachedIndexUri = getCacheIndexURI(indexUri);
+            Map<String, String> repoProps = new HashMap<>();
+            repoProps.put("locations", indexUri.toString());
+            FixedIndexedRepo repo = new FixedIndexedRepo();
+            repo.setRegistry(registry);
+            repo.setProperties(repoProps);
+
+            this.repositories.put(indexUri, repo);
+        }
+    }
+
+    @Override
+    public Collection<Resource> getMandatoryResources() {
+        return Collections.<Resource>singleton(this.initialResource);
+    }
+
+    @Override
+    public List<Capability> findProviders(Requirement requirement) {
+        List<Capability> resultCaps = new LinkedList<>();
+
+        // Find from installed bundles
+        Bundle[] bundles = this.bundleContext.getBundles();
+        for (Bundle bundle : bundles) {
+            if (bundle.getState() == Bundle.UNINSTALLED) {
+                continue; // Skip UNINSTALLED bundles
+            }
+
+            BundleRevision revision = bundle.adapt(BundleRevision.class);
+            List<Capability> bundleCaps = revision.getCapabilities(requirement.getNamespace());
+            if (bundleCaps != null) {
+                for (Capability bundleCap : bundleCaps) {
+                    if (match(requirement, bundleCap, this.log)) {
+                        resultCaps.add(bundleCap);
+                    }
+                }
+            }
+        }
+
+        // Find from repositories
+        for (Entry<URI, Repository> repoEntry : this.repositories.entrySet()) {
+            Repository repository = repoEntry.getValue();
+            Map<Requirement, Collection<Capability>> providers = repository
+                    .findProviders(Collections.singleton(requirement));
+            if (providers != null) {
+                Collection<Capability> repoCaps = providers.get(requirement);
+                if (repoCaps != null) {
+                    resultCaps.addAll(repoCaps);
+
+                    for (Capability repoCap : repoCaps) {
+                        // Get the list of physical URIs for this resource.
+                        Resource resource = repoCap.getResource();
+                        // Keep track of which repositories own which resources.
+                        this.resourceRepositoryMap.put(resource, repository);
+
+                        // Resolve the Resource's URI relative to the Repository
+                        // Index URI and save for later.
+                        URI repoIndexUri = repoEntry.getKey();
+                        URI resolvedUri = resolveResourceLocation(resource, repoIndexUri);
+                        if (resolvedUri != null) {
+                            // Cache the resolved URI into the resource URI map,
+                            // which will be used after resolve.
+                            this.resourceLocationMap.put(resource, resolvedUri.toString());
+                        }
+                    }
+                }
+            }
+        }
+        return resultCaps;
+    }
+
+    static boolean match(Requirement requirement, Capability capability) {
+        return match(requirement, capability, null);
+    }
+
+    static boolean match(Requirement requirement, Capability capability, LogService log) {
+        // Namespace MUST match
+        if (!requirement.getNamespace().equals(capability.getNamespace())) {
+            return false;
+        }
+
+        // If capability effective!=resolve then it matches only requirements
+        // with same effective
+        String capabilityEffective = capability.getDirectives().get(Namespace.CAPABILITY_EFFECTIVE_DIRECTIVE);
+        if (capabilityEffective != null) {
+            String requirementEffective = requirement.getDirectives().get(Namespace.REQUIREMENT_EFFECTIVE_DIRECTIVE);
+            if (!capabilityEffective.equals(Namespace.EFFECTIVE_RESOLVE)
+                    && !capabilityEffective.equals(requirementEffective)) {
+                return false;
+            }
+        }
+
+        String filterStr = requirement.getDirectives().get(Namespace.REQUIREMENT_FILTER_DIRECTIVE);
+        if (filterStr == null) {
+            return true; // no filter, the requirement always matches
+        }
+
+        try {
+            Filter filter = FrameworkUtil.createFilter(filterStr);
+            return filter.matches(capability.getAttributes());
+        } catch (InvalidSyntaxException e) {
+            if (log != null) {
+                Resource resource = requirement.getResource();
+                String id = resource != null ? getIdentity(resource) : "<unknown>";
+                log.log(LogService.LOG_ERROR,
+                        String.format("Invalid filter syntax in requirement from resource %s: %s", id, filterStr), e);
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Get the repository index for the specified resource, where 0 indicates an
+     * existing OSGi bundle in the framework and -1 indicates not found. This
+     * method is used by
+     * {@link #insertHostedCapability(List, HostedCapability)}.
+     */
+    private int findResourceRepositoryIndex(Resource resource) {
+        if (resource instanceof BundleRevision) {
+            return 0;
+        }
+
+        int index = 1;
+        Repository repo = this.resourceRepositoryMap.get(resource);
+        if (repo == null) {
+            return -1;
+        }
+        for (Repository match : this.repositories.values()) {
+            if (repo == match) {
+                return index;
+            }
+            index++;
+        }
+        // Still not found
+        return -1;
+    }
+
+    @Override
+    public int insertHostedCapability(List<Capability> capabilities, HostedCapability hc) {
+        int hostIndex = findResourceRepositoryIndex(hc.getResource());
+        if (hostIndex == -1) {
+            throw new IllegalArgumentException(
+                    "Hosted capability has host resource not found in any known repository.");
+        }
+
+        for (int pos = 0; pos < capabilities.size(); pos++) {
+            int resourceIndex = findResourceRepositoryIndex(capabilities.get(pos).getResource());
+            if (resourceIndex > hostIndex) {
+                capabilities.add(pos, hc);
+                return pos;
+            }
+        }
+
+        // The list passed by (some versions of) Felix does not support the
+        // single-arg add() method... this throws UnsupportedOperationException.
+        // So we have to call the two-arg add() with an explicit index.
+        int lastPos = capabilities.size();
+        capabilities.add(lastPos, hc);
+        return lastPos;
+    }
+
+    @Override
+    public boolean isEffective(Requirement requirement) {
+        return true;
+    }
+
+    @Override
+    public Map<Resource, Wiring> getWirings() {
+        Map<Resource, Wiring> wiringMap = new HashMap<>();
+        Bundle[] bundles = this.bundleContext.getBundles();
+        for (Bundle bundle : bundles) {
+            // BundleRevision extends Resource
+            BundleRevision revision = bundle.adapt(BundleRevision.class);
+            // BundleWiring extends Wiring
+            BundleWiring wiring = revision.getWiring();
+            if (wiring != null) {
+                wiringMap.put(revision, wiring);
+            }
+        }
+        return wiringMap;
+    }
+
+    boolean isInitialResource(Resource resource) {
+        List<Capability> markerCaps = resource.getCapabilities(INITIAL_RESOURCE_CAPABILITY_NAMESPACE);
+        return markerCaps != null && !markerCaps.isEmpty();
+    }
+
+    String getLocation(Resource resource) {
+        String location;
+        if (resource instanceof BundleRevision) {
+            location = ((BundleRevision) resource).getBundle().getLocation();
+        } else {
+            location = this.resourceLocationMap.get(resource);
+        }
+        return location;
+    }
+
+    private static CapabilityImpl createInitialMarkerCapability(Resource resource) {
+        return new CapabilityImpl(INITIAL_RESOURCE_CAPABILITY_NAMESPACE, Collections.<String,String>emptyMap(), Collections.<String,Object>emptyMap(),
+                resource);
+    }
+
+    private static CapabilityImpl createIdentityCap(Resource resource, String identity) {
+        Map<String, Object> idCapAttrs = new HashMap<>();
+        idCapAttrs.put(IdentityNamespace.IDENTITY_NAMESPACE, identity);
+        CapabilityImpl idCap = new CapabilityImpl(IdentityNamespace.IDENTITY_NAMESPACE, Collections.<String,String>emptyMap(),
+                idCapAttrs, resource);
+        return idCap;
+    }
+
+    private static URI resolveResourceLocation(Resource resource, URI indexUri) {
+        List<Capability> contentCaps = resource.getCapabilities(ContentNamespace.CONTENT_NAMESPACE);
+        if (contentCaps != null) {
+            for (Capability contentCap : contentCaps) {
+                // Ensure this content entry has the correct MIME type for a
+                // bundle
+                if (MIME_BUNDLE.equals(contentCap.getAttributes().get(ContentNamespace.CAPABILITY_MIME_ATTRIBUTE))) {
+                    // Get the URI attribute in the index, which is either an
+                    // asbolute URI, or a relative URI to be resolved against
+                    // the index URI.
+                    URI rawUri = null;
+                    Object uriObj = contentCap.getAttributes().get(ContentNamespace.CAPABILITY_URL_ATTRIBUTE);
+                    if (uriObj instanceof URI) {
+                        rawUri = (URI) uriObj;
+                    } else if (uriObj instanceof String) {
+                        rawUri = URI.create((String) uriObj);
+                    }
+                    if (rawUri != null) {
+                        URI resolvedUri = URIUtils.resolve(indexUri, rawUri);
+                        return resolvedUri;
+                    }
+                }
+            }
+        }
+
+        // No content capability was found in the appropriate form
+        return null;
+    }
+
+    private static String getIdentity(Resource resource) {
+        List<Capability> caps = resource.getCapabilities(IdentityNamespace.IDENTITY_NAMESPACE);
+        if (caps == null || caps.isEmpty()) {
+            return "<unknown>";
+        }
+
+        Object idObj = caps.get(0).getAttributes().get(IdentityNamespace.IDENTITY_NAMESPACE);
+        if (!(idObj instanceof String)) {
+            return "<unknown>";
+        }
+
+        return (String) idObj;
+    }
+
+}

Added: felix/trunk/fileinstall-plugins/resolver/src/main/java/org/apache/felix/fileinstall/plugins/resolver/impl/PluginResolverComponent.java
URL: http://svn.apache.org/viewvc/felix/trunk/fileinstall-plugins/resolver/src/main/java/org/apache/felix/fileinstall/plugins/resolver/impl/PluginResolverComponent.java?rev=1826244&view=auto
==============================================================================
--- felix/trunk/fileinstall-plugins/resolver/src/main/java/org/apache/felix/fileinstall/plugins/resolver/impl/PluginResolverComponent.java (added)
+++ felix/trunk/fileinstall-plugins/resolver/src/main/java/org/apache/felix/fileinstall/plugins/resolver/impl/PluginResolverComponent.java Thu Mar  8 14:57:57 2018
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * Copyright (c) Intel Corporation
+ * Copyright (c) 2017
+ *
+ * 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.apache.felix.fileinstall.plugins.resolver.impl;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.felix.fileinstall.plugins.resolver.PluginResolver;
+import org.apache.felix.fileinstall.plugins.resolver.ResolveRequest;
+import org.apache.felix.fileinstall.plugins.resolver.ResolveResult;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.resource.Resource;
+import org.osgi.resource.Wire;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicyOption;
+import org.osgi.service.log.LogService;
+import org.osgi.service.resolver.Resolver;
+
+@Component(name = "org.apache.felix.fileinstall.plugins.resolver")
+public class PluginResolverComponent implements PluginResolver {
+	
+	private BundleContext bundleContext;
+	
+	@Reference(cardinality = ReferenceCardinality.OPTIONAL, policyOption = ReferencePolicyOption.GREEDY)
+	LogService log;
+	
+	@Reference(target = "(!(" + Constants.SERVICE_BUNDLEID + "=0))")
+	Resolver frameworkResolver;
+
+	@Activate
+	void activate(BundleContext bundleContext) {
+		this.bundleContext = bundleContext;
+	}
+
+	@Override
+	public ResolveResult resolve(ResolveRequest request) throws Exception {
+		PluginResolveContext context = new PluginResolveContext(bundleContext, request, log);
+		Map<Resource, List<Wire>> resolved = frameworkResolver.resolve(context);
+		
+		ResolveResultImpl result = new ResolveResultImpl(request);
+		for (Entry<Resource, List<Wire>> entry : resolved.entrySet()) {
+			Resource resource = entry.getKey();
+			// Skip the synthetic "<<INITIAL>>" resource
+			if (!context.isInitialResource(resource)) {
+				result.addResource(resource, context.getLocation(resource));
+			}
+		}
+		
+		return result;
+	}
+}
\ No newline at end of file

Added: felix/trunk/fileinstall-plugins/resolver/src/main/java/org/apache/felix/fileinstall/plugins/resolver/impl/RequirementImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/fileinstall-plugins/resolver/src/main/java/org/apache/felix/fileinstall/plugins/resolver/impl/RequirementImpl.java?rev=1826244&view=auto
==============================================================================
--- felix/trunk/fileinstall-plugins/resolver/src/main/java/org/apache/felix/fileinstall/plugins/resolver/impl/RequirementImpl.java (added)
+++ felix/trunk/fileinstall-plugins/resolver/src/main/java/org/apache/felix/fileinstall/plugins/resolver/impl/RequirementImpl.java Thu Mar  8 14:57:57 2018
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (c) Intel Corporation
+ * Copyright (c) 2017
+ *
+ * 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.apache.felix.fileinstall.plugins.resolver.impl;
+
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.osgi.resource.Namespace;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+
+public class RequirementImpl extends CapReqBase implements Requirement {
+
+	public static RequirementImpl copy(Requirement req, Resource resource) {
+		return new RequirementImpl(req.getNamespace(), req.getDirectives(), req.getAttributes(), resource);
+	}
+
+	public RequirementImpl(String namespace, Map<String, String> directives, Map<String, Object> attribs, Resource resource) {
+		super(namespace, directives, attribs, resource);
+	}
+
+	@Override
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		sb.append(this.namespace);
+
+		String filterStr = this.directives.get(Namespace.REQUIREMENT_FILTER_DIRECTIVE);
+		if (filterStr != null) {
+            sb.append(":").append(filterStr);
+        }
+
+		for (Entry<String,String> directive : this.directives.entrySet()) {
+			if (!Namespace.REQUIREMENT_FILTER_DIRECTIVE.equals(directive.getKey())) {
+                sb.append(", ").append(directive.getKey()).append(":=").append(directive.getValue());
+            }
+		}
+
+		for (Entry<String,Object> attrib : this.attribs.entrySet()) {
+			sb.append(", ").append(attrib.getKey()).append("=").append(attrib.getValue());
+		}
+
+		return sb.toString();
+	}
+
+}

Added: felix/trunk/fileinstall-plugins/resolver/src/main/java/org/apache/felix/fileinstall/plugins/resolver/impl/ResolveResultImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/fileinstall-plugins/resolver/src/main/java/org/apache/felix/fileinstall/plugins/resolver/impl/ResolveResultImpl.java?rev=1826244&view=auto
==============================================================================
--- felix/trunk/fileinstall-plugins/resolver/src/main/java/org/apache/felix/fileinstall/plugins/resolver/impl/ResolveResultImpl.java (added)
+++ felix/trunk/fileinstall-plugins/resolver/src/main/java/org/apache/felix/fileinstall/plugins/resolver/impl/ResolveResultImpl.java Thu Mar  8 14:57:57 2018
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright (c) Intel Corporation
+ * Copyright (c) 2017
+ *
+ * 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.apache.felix.fileinstall.plugins.resolver.impl;
+
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+import org.apache.felix.fileinstall.plugins.resolver.ResolveRequest;
+import org.apache.felix.fileinstall.plugins.resolver.ResolveResult;
+import org.osgi.resource.Resource;
+
+public class ResolveResultImpl implements ResolveResult {
+	
+	private final ResolveRequest request;
+	private final Map<Resource, String> resourceMap = new IdentityHashMap<>();
+
+	public ResolveResultImpl(ResolveRequest request) {
+		this.request = request;
+	}
+
+	@Override
+	public ResolveRequest getRequest() {
+		return request;
+	}
+
+	@Override
+	public Map<Resource, String> getResources() {
+		return Collections.unmodifiableMap(resourceMap);
+	}
+
+	void addResource(Resource resource, String location) {
+		resourceMap.put(resource, location);
+	}
+
+
+}
\ No newline at end of file

Added: felix/trunk/fileinstall-plugins/resolver/src/main/java/org/apache/felix/fileinstall/plugins/resolver/impl/ResourceImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/fileinstall-plugins/resolver/src/main/java/org/apache/felix/fileinstall/plugins/resolver/impl/ResourceImpl.java?rev=1826244&view=auto
==============================================================================
--- felix/trunk/fileinstall-plugins/resolver/src/main/java/org/apache/felix/fileinstall/plugins/resolver/impl/ResourceImpl.java (added)
+++ felix/trunk/fileinstall-plugins/resolver/src/main/java/org/apache/felix/fileinstall/plugins/resolver/impl/ResourceImpl.java Thu Mar  8 14:57:57 2018
@@ -0,0 +1,102 @@
+/*******************************************************************************
+ * Copyright (c) Intel Corporation
+ * Copyright (c) 2017
+ *
+ * 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.apache.felix.fileinstall.plugins.resolver.impl;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.osgi.framework.namespace.IdentityNamespace;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+
+public class ResourceImpl implements Resource {
+
+	private final Map<String, List<Requirement>> requirements = new HashMap<>();
+	private final Map<String, List<Capability>> capabilities = new HashMap<>();
+
+	void addRequirement(Requirement req) {
+		if (req.getResource() != this) {
+            req = RequirementImpl.copy(req, this);
+        }
+
+		List<Requirement> list = this.requirements.get(req.getNamespace());
+		if (list == null) {
+			list = new LinkedList<>();
+			this.requirements.put(req.getNamespace(), list);
+		}
+		list.add(req);
+	}
+
+	void addCapability(Capability cap) {
+		if (cap.getResource() != this) {
+            cap = CapabilityImpl.copy(cap, this);
+        }
+
+		List<Capability> list = this.capabilities.get(cap.getNamespace());
+		if (list == null) {
+			list = new LinkedList<>();
+			this.capabilities.put(cap.getNamespace(), list);
+		}
+		list.add(cap);
+	}
+
+	@Override
+	public List<Capability> getCapabilities(String namespace) {
+		List<Capability> result;
+		if (namespace == null) {
+			result = new LinkedList<>();
+			for (List<Capability> list : this.capabilities.values()) {
+                result.addAll(list);
+            }
+		} else {
+			result = this.capabilities.get(namespace);
+		}
+		return result != null ? result : Collections.<Capability>emptyList();
+	}
+
+	@Override
+	public List<Requirement> getRequirements(String namespace) {
+		List<Requirement> result;
+		if (namespace == null) {
+			result = new LinkedList<>();
+			for (List<Requirement> list : this.requirements.values()) {
+                result.addAll(list);
+            }
+		} else {
+			result = this.requirements.get(namespace);
+		}
+		return result != null ? result : Collections.<Requirement>emptyList();
+	}
+
+	@Override
+	public String toString() {
+		String name = "<unknown>";
+		List<Capability> idCaps = getCapabilities(IdentityNamespace.IDENTITY_NAMESPACE);
+		if (idCaps != null && !idCaps.isEmpty()) {
+			Object nameObj = idCaps.get(0).getAttributes().get(IdentityNamespace.IDENTITY_NAMESPACE);
+			if (nameObj instanceof String) {
+                name = (String) nameObj;
+            }
+		}
+		return name;
+	}
+
+}

Added: felix/trunk/fileinstall-plugins/resolver/src/main/java/org/apache/felix/fileinstall/plugins/resolver/impl/URIUtils.java
URL: http://svn.apache.org/viewvc/felix/trunk/fileinstall-plugins/resolver/src/main/java/org/apache/felix/fileinstall/plugins/resolver/impl/URIUtils.java?rev=1826244&view=auto
==============================================================================
--- felix/trunk/fileinstall-plugins/resolver/src/main/java/org/apache/felix/fileinstall/plugins/resolver/impl/URIUtils.java (added)
+++ felix/trunk/fileinstall-plugins/resolver/src/main/java/org/apache/felix/fileinstall/plugins/resolver/impl/URIUtils.java Thu Mar  8 14:57:57 2018
@@ -0,0 +1,166 @@
+/*******************************************************************************
+ * Copyright (c) Intel Corporation
+ * Copyright (c) 2017
+ *
+ * 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.apache.felix.fileinstall.plugins.resolver.impl;
+
+import java.net.URI;
+
+public final class URIUtils {
+
+	private static final String JAR_URI_SCHEME = "jar";
+
+	private URIUtils() {}
+
+	/**
+	 * Resolve a URI relative to a base URI.
+	 *
+	 * @param baseUri
+	 *            The base URI which must be absolute.
+	 * @param uri
+	 *            The relative URI to resolve against the specified base. Note
+	 *            that if this URI is absolute, it will be simply returned
+	 *            unchanged.
+	 * @return An absolute, resolved URI.
+	 * @throws {@link IllegalArgumentException} If the {@code baseUri} parameter
+	 *         is null/relative/opaque or if the URI otherwise cannot be
+	 *         resolved.
+	 */
+	public static URI resolve(URI baseUri, URI uri) throws IllegalArgumentException {
+		URI resolved;
+		if (uri.isAbsolute()) {
+            resolved = uri;
+        } else {
+			URI relative;
+			String innerPath;
+
+			// Work out if the relative URI contains a bang (!), meaning to navigate into a JAR
+			String uriPath = uri.getPath();
+			int bangIndex = uriPath.indexOf('!');
+			if (bangIndex >= 0) {
+				relative = URI.create(uriPath.substring(0, bangIndex));
+				innerPath = uriPath.substring(bangIndex + 1);
+			} else {
+				relative = uri;
+				innerPath = null;
+			}
+
+			// Do the normal resolution
+			if (baseUri == null) {
+                throw new IllegalArgumentException(String.format("Cannot resolve relative URI (%s): base URI is null/unavailable.", uri));
+            }
+			if (!baseUri.isAbsolute()) {
+                throw new IllegalArgumentException(String.format("Cannot resolve relative URI (%s): base URI is also relative (%s).", uri, baseUri));
+            }
+			if (baseUri.isOpaque()) {
+				// Handle "jar:" URIs as a special case
+				if (JAR_URI_SCHEME.equals(baseUri.getScheme())) {
+                    resolved = resolveJarUri(baseUri, relative);
+                } else {
+                    throw new IllegalArgumentException(String.format("Cannot resolve relative URI (%s): base URI is opaque (%s).", uri, baseUri));
+                }
+			} else {
+				resolved = baseUri.resolve(relative);
+			}
+
+			// If an inner path was indicated, create a jar: URI
+			if (innerPath != null) {
+				resolved = URI.create("jar:" + resolved.toString() + "!" + innerPath);
+			}
+		}
+		return resolved;
+	}
+
+	private static URI resolveJarUri(URI baseUri, URI uri) throws IllegalArgumentException {
+		assert JAR_URI_SCHEME.equals(baseUri.getScheme()) : "not a jar: URI";
+
+		String ssp = baseUri.getSchemeSpecificPart();
+		int bangIndex = ssp.lastIndexOf('!');
+		if (bangIndex == -1) {
+            throw new IllegalArgumentException("Invalid URI in the jar: scheme (missing ! separator): " + baseUri);
+        }
+		URI jarUri = URI.create(ssp.substring(0, bangIndex));
+		String pathStr = ssp.substring(bangIndex + 1);
+
+		String query = baseUri.getQuery();
+		if (query == null) {
+			int qmIndex = pathStr.lastIndexOf('?');
+			if (qmIndex >= 0) {
+				query = pathStr.substring(qmIndex + 1);
+				pathStr = pathStr.substring(0, qmIndex);
+			} else {
+				query = "";
+			}
+		}
+		boolean nonavigate = query.indexOf("navigate=false") >= 0;
+
+		Path basePath = Path.parse(pathStr);
+		Path baseDir = basePath.parent();
+		Path resolvedPath = baseDir.append(uri.getPath());
+
+		URI result;
+		if (resolvedPath.isEmpty()) {
+			result = jarUri;
+		} else if (Path.GO_UP.equals(resolvedPath.head())) {
+			if (nonavigate) {
+                throw new IllegalArgumentException("Cannot navigate outside JAR contents.");
+            }
+			result = jarUri.resolve(resolvedPath.tail().toString());
+		} else {
+			result = URI.create(JAR_URI_SCHEME + ":" + jarUri + "!/" + resolvedPath);
+		}
+
+		return result;
+	}
+
+	/**
+	 * Returns the file name of the resource references by the URI. Works with jar: URIs, which normally return
+	 * {@code null} if the {@link URI#getPath()} call is used.
+	 * @param uri
+	 * @return The filename, if it can be calculated.
+	 * @throws IllegalArgumentException if the filename cannot be calculated (e.g. if the scheme is not understood).
+	 */
+	public static String getFileName(URI uri) throws IllegalArgumentException {
+		String filename;
+		if (JAR_URI_SCHEME.equals(uri.getScheme())) {
+			String ssp = uri.getRawSchemeSpecificPart();
+			String innerPath;
+			int bang = ssp.indexOf('!');
+			if (bang < 0) {
+                throw new IllegalArgumentException("Invalid jar content URI; missing '!/' path separator: " + uri);
+            } else {
+                innerPath = ssp.substring(bang + 1);
+            }
+			filename = getPathLastSegment(innerPath);
+		} else {
+			String path = uri.getPath();
+			if (path == null) {
+                throw new IllegalArgumentException("Cannot calculate filename for unknown opaque URI scheme: " + uri);
+            }
+			filename = getPathLastSegment(path);
+		}
+		return filename;
+	}
+
+	private static String getPathLastSegment(String path) {
+		int slash = path.lastIndexOf('/');
+		if (slash < 0) {
+            return path;
+        }
+
+		return path.substring(slash + 1);
+	}
+
+}

Added: felix/trunk/fileinstall-plugins/resolver/src/main/java/org/apache/felix/fileinstall/plugins/resolver/package-info.java
URL: http://svn.apache.org/viewvc/felix/trunk/fileinstall-plugins/resolver/src/main/java/org/apache/felix/fileinstall/plugins/resolver/package-info.java?rev=1826244&view=auto
==============================================================================
--- felix/trunk/fileinstall-plugins/resolver/src/main/java/org/apache/felix/fileinstall/plugins/resolver/package-info.java (added)
+++ felix/trunk/fileinstall-plugins/resolver/src/main/java/org/apache/felix/fileinstall/plugins/resolver/package-info.java Thu Mar  8 14:57:57 2018
@@ -0,0 +1,19 @@
+/*******************************************************************************
+ * Copyright (c) Intel Corporation
+ * Copyright (c) 2017
+ *
+ * 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.
+ *******************************************************************************/
+@org.osgi.annotation.versioning.Version("1.0.0")
+package org.apache.felix.fileinstall.plugins.resolver;
+

Added: felix/trunk/fileinstall-plugins/resolver/src/test/java/org/apache/felix/fileinstall/plugins/resolver/impl/PluginResolveContextTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/fileinstall-plugins/resolver/src/test/java/org/apache/felix/fileinstall/plugins/resolver/impl/PluginResolveContextTest.java?rev=1826244&view=auto
==============================================================================
--- felix/trunk/fileinstall-plugins/resolver/src/test/java/org/apache/felix/fileinstall/plugins/resolver/impl/PluginResolveContextTest.java (added)
+++ felix/trunk/fileinstall-plugins/resolver/src/test/java/org/apache/felix/fileinstall/plugins/resolver/impl/PluginResolveContextTest.java Thu Mar  8 14:57:57 2018
@@ -0,0 +1,137 @@
+/*******************************************************************************
+ * Copyright (c) Intel Corporation
+ * Copyright (c) 2017
+ *
+ * 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.apache.felix.fileinstall.plugins.resolver.impl;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Test;
+
+@SuppressWarnings("serial")
+public class PluginResolveContextTest {
+
+    @Test
+    public void testMatchRequirementsEffectiveResolve() throws Exception {
+        assertTrue("effective should match: req=default, cap=default",
+                PluginResolveContext.match(createRequirement("foo", "(foo=bar)", null),
+                        createCapability("foo", null, new HashMap<String, Object>() {
+                            {
+                                put("foo", "bar");
+                            }
+                        })));
+        assertTrue("effective should match: req=default, cap=resolve",
+                PluginResolveContext.match(createRequirement("foo", "(foo=bar)", null),
+                        createCapability("foo", "resolve", new HashMap<String, Object>() {
+                            {
+                                put("foo", "bar");
+                            }
+                        })));
+        assertTrue("effective should match: req=resolve, cap=default",
+                PluginResolveContext.match(createRequirement("foo", "(foo=bar)", "resolve"),
+                        createCapability("foo", null, new HashMap<String, Object>() {
+                            {
+                                put("foo", "bar");
+                            }
+                        })));
+        assertTrue("effective should match: req=resolve, cap=resolve",
+                PluginResolveContext.match(createRequirement("foo", "(foo=bar)", "resolve"),
+                        createCapability("foo", "resolve", new HashMap<String, Object>() {
+                            {
+                                put("foo", "bar");
+                            }
+                        })));
+    }
+
+    @Test
+    public void testEffectiveResolveCapsMatchAnyRequirement() throws Exception {
+        // Caps with effective=resolve (or default) match requirements with any
+        // effective=...
+        assertTrue("effective should match: req=blah, cap=default",
+                PluginResolveContext.match(createRequirement("foo", "(foo=bar)", "blah"),
+                        createCapability("foo", null, new HashMap<String, Object>() {
+                            {
+                                put("foo", "bar");
+                            }
+                        })));
+        assertTrue("effective should match: req=blah, cap=resolve",
+                PluginResolveContext.match(createRequirement("foo", "(foo=bar)", "blah"),
+                        createCapability("foo", "resolve", new HashMap<String, Object>() {
+                            {
+                                put("foo", "bar");
+                            }
+                        })));
+    }
+
+    @Test
+    public void testEffectiveNonResolveCapsMatchRequirementWithSame() throws Exception {
+        // Caps with effective=!resolve ONLY match reqs with the same effective
+        assertTrue("effective should match: req=blah, cap=blah",
+                PluginResolveContext.match(createRequirement("foo", "(foo=bar)", "blah"),
+                        createCapability("foo", "blah", new HashMap<String, Object>() {
+                            {
+                                put("foo", "bar");
+                            }
+                        })));
+        assertFalse("effective should NOT match: req=default, cap=blah",
+                PluginResolveContext.match(createRequirement("foo", "(foo=bar)", null),
+                        createCapability("foo", "blah", new HashMap<String, Object>() {
+                            {
+                                put("foo", "bar");
+                            }
+                        })));
+        assertFalse("effective should NOT match: req=default, cap=blah",
+                PluginResolveContext.match(createRequirement("foo", "(foo=bar)", "resolve"),
+                        createCapability("foo", "blah", new HashMap<String, Object>() {
+                            {
+                                put("foo", "bar");
+                            }
+                        })));
+        assertFalse("effective should NOT match: req=wibble, cap=blah",
+                PluginResolveContext.match(createRequirement("foo", "(foo=bar)", "wibble"),
+                        createCapability("foo", "blah", new HashMap<String, Object>() {
+                            {
+                                put("foo", "bar");
+                            }
+                        })));
+    }
+
+    static RequirementImpl createRequirement(String ns, String filter, String effective) {
+        Map<String, String> directives = new HashMap<>();
+        if (filter != null) {
+            directives.put("filter", "(foo=bar)");
+        }
+        if (effective != null) {
+            directives.put("effective", effective);
+        }
+
+        return new RequirementImpl(ns, directives, Collections.<String, Object>emptyMap(), null);
+    }
+
+    static CapabilityImpl createCapability(String ns, String effective, Map<String, Object> attrs) {
+        Map<String, String> directives = new HashMap<>();
+        if (effective != null) {
+            directives.put("effective", effective);
+        }
+
+        return new CapabilityImpl(ns, directives, attrs, null);
+    }
+
+}

Added: felix/trunk/fileinstall-plugins/resolver/src/test/java/org/apache/felix/fileinstall/plugins/resolver/impl/URIUtilsTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/fileinstall-plugins/resolver/src/test/java/org/apache/felix/fileinstall/plugins/resolver/impl/URIUtilsTest.java?rev=1826244&view=auto
==============================================================================
--- felix/trunk/fileinstall-plugins/resolver/src/test/java/org/apache/felix/fileinstall/plugins/resolver/impl/URIUtilsTest.java (added)
+++ felix/trunk/fileinstall-plugins/resolver/src/test/java/org/apache/felix/fileinstall/plugins/resolver/impl/URIUtilsTest.java Thu Mar  8 14:57:57 2018
@@ -0,0 +1,87 @@
+/*******************************************************************************
+ * Copyright (c) Intel Corporation
+ * Copyright (c) 2017
+ *
+ * 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.apache.felix.fileinstall.plugins.resolver.impl;
+
+import java.net.URI;
+
+import junit.framework.TestCase;
+
+public class URIUtilsTest extends TestCase {
+	public void testResolveRelativeHttpUri() {
+		URI resolved = URIUtils.resolve(URI.create("http://example.org/repository/index.xml"), URI.create("bundle/bundle.jar"));
+		assertEquals("http://example.org/repository/bundle/bundle.jar", resolved.toString());
+	}
+
+	public void testResolveAbsoluteFileBaseUri() {
+		URI resolved = URIUtils.resolve(URI.create("file:/Users/bob/repository/index.xml"), URI.create("bundle/bundle.jar"));
+		assertEquals("file:/Users/bob/repository/bundle/bundle.jar", resolved.toString());
+	}
+
+	public void testResolveJarUri() throws Exception {
+		URI base = new URI("jar:http://example.com/systems/example.jar!/sys/system.xml");
+
+		// Sibling resource within the JAR
+		assertEquals("jar:http://example.com/systems/example.jar!/sys/repo/index-nim.xml", URIUtils.resolve(base, new URI("repo/index-nim.xml")).toString());
+
+		// Move up a path within the JAR
+		assertEquals("jar:http://example.com/systems/example.jar!/repo/index-nim.xml", URIUtils.resolve(base, new URI("../repo/index-nim.xml")).toString());
+
+		// Here's the tricky bit... move up a path *outside* the JAR
+		assertEquals("http://example.com/systems/index.xml", URIUtils.resolve(base, new URI("../../index.xml")).toString());
+		assertEquals("http://example.com/repos/index.xml", URIUtils.resolve(base, new URI("../../../repos/index.xml")).toString());
+		
+		// Even trickier... move up a path to another JAR, and then into it
+		assertEquals("jar:http://example.com/systems/other.jar!/index.xml", URIUtils.resolve(base, new URI("../../other.jar!/index.xml")).toString());
+	}
+	
+	public void testResolveJarUriNoNavigateUp() throws Exception {
+		URI base = new URI("jar:http://example.com/systems/example.jar!/sys/system.xml?navigate=false");
+
+		// Sibling resource within the JAR
+		assertEquals("jar:http://example.com/systems/example.jar!/sys/repo/index-nim.xml", URIUtils.resolve(base, new URI("repo/index-nim.xml")).toString());
+
+		// Move up a path within the JAR
+		assertEquals("jar:http://example.com/systems/example.jar!/repo/index-nim.xml", URIUtils.resolve(base, new URI("../repo/index-nim.xml")).toString());
+
+		// Moving outside the JAR is not allowed
+		try {
+			URIUtils.resolve(base, new URI("../../index.xml"));
+			fail("Should throw IllegalArgumentException");
+		} catch (IllegalArgumentException e) {
+			// expected
+		}
+	}
+	
+	public void testGetFileName() {
+		assertEquals("system.xml", URIUtils.getFileName(URI.create("http://www.example.com/foo/bar/system.xml")));
+		assertEquals("system.xml", URIUtils.getFileName(URI.create("jar:http://www.example.com/foo/bar/system.jar!/bar/foo/system.xml")));
+
+		try {
+			URIUtils.getFileName(URI.create("blah:this/could/be/anything.xml"));
+			fail("Should throw IllArgExc");
+		} catch (IllegalArgumentException e) {
+			// expected
+		}
+
+		try {
+			URIUtils.getFileName(URI.create("jar:http://www.example.com/foo/bar/system.jar"));
+			fail("Should throw IllArgExc");
+		} catch (IllegalArgumentException e) {
+			// expected
+		}
+	}
+}

Modified: felix/trunk/pom.xml
URL: http://svn.apache.org/viewvc/felix/trunk/pom.xml?rev=1826244&r1=1826243&r2=1826244&view=diff
==============================================================================
--- felix/trunk/pom.xml (original)
+++ felix/trunk/pom.xml Thu Mar  8 14:57:57 2018
@@ -95,5 +95,8 @@
         <module>webconsole-plugins/upnp</module>
         <module>webconsole-plugins/useradmin</module>
         <module>wireadmin</module>
+        <module>fileinstall-plugins/resolver</module>
+        <module>fileinstall-plugins/installer</module>
+        <module>fileinstall-plugins/installer.test</module>
     </modules>
 </project>