blob: d063a074f633d5699505fc009979bea27bc02698 [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.apache.felix.utils.repository;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.xml.stream.XMLStreamException;
import org.apache.felix.utils.resource.CapabilitySet;
import org.apache.felix.utils.resource.SimpleFilter;
import org.osgi.framework.Version;
import org.osgi.resource.Capability;
import org.osgi.resource.Requirement;
import org.osgi.resource.Resource;
import static org.osgi.framework.namespace.IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE;
import static org.osgi.framework.namespace.IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE;
import static org.osgi.framework.namespace.IdentityNamespace.IDENTITY_NAMESPACE;
/**
* Repository conforming to the OSGi Repository specification.
* The content of the URL can be gzipped.
*/
public class XmlRepository extends BaseRepository {
protected final String url;
protected final long expiration;
protected final Map<String, XmlLoader> loaders = new HashMap<>();
protected final ReadWriteLock lock = new ReentrantReadWriteLock();
public XmlRepository(String url, long expiration) {
this.url = url;
this.expiration = expiration;
}
@Override
public List<Resource> getResources() {
checkAndLoadCache();
return super.getResources();
}
@Override
public Map<Requirement, Collection<Capability>> findProviders(Collection<? extends Requirement> requirements) {
checkAndLoadCache();
return super.findProviders(requirements);
}
public String getUrl() {
return url;
}
protected Map<String, XmlLoader> getLoaders() {
return loaders;
}
@Override
protected void addResource(Resource resource) {
List<Capability> identities = resource.getCapabilities(IDENTITY_NAMESPACE);
if (identities.isEmpty()) {
throw new IllegalStateException("Invalid resource: a capability with 'osgi.identity' namespace is required");
} else if (identities.size() > 1) {
throw new IllegalStateException("Invalid resource: multiple 'osgi.identity' capabilities found");
}
Capability identity = identities.get(0);
Object name = identity.getAttributes().get(IDENTITY_NAMESPACE);
Object type = identity.getAttributes().get(CAPABILITY_TYPE_ATTRIBUTE);
Object vers = identity.getAttributes().get(CAPABILITY_VERSION_ATTRIBUTE);
if (!String.class.isInstance(name)
|| !String.class.isInstance(type)
|| !Version.class.isInstance(vers)) {
throw new IllegalStateException("Invalid osgi.identity capability: " + identity);
}
if (!hasResource((String) type, (String) name, (Version) vers)) {
super.addResource(resource);
}
}
private boolean hasResource(String type, String name, Version version) {
CapabilitySet set = capSets.get(IDENTITY_NAMESPACE);
if (set != null) {
Map<String, Object> attrs = new HashMap<>();
attrs.put(CAPABILITY_TYPE_ATTRIBUTE, type);
attrs.put(IDENTITY_NAMESPACE, name);
attrs.put(CAPABILITY_VERSION_ATTRIBUTE, version);
SimpleFilter sf = SimpleFilter.convert(attrs);
return !set.match(sf, true).isEmpty();
} else {
return false;
}
}
protected void checkAndLoadCache() {
if (checkAndLoadReferrals(url, Integer.MAX_VALUE)) {
lock.writeLock().lock();
try {
resources.clear();
capSets.clear();
populate(loaders.get(url).xml, Integer.MAX_VALUE);
} finally {
lock.writeLock().unlock();
}
}
}
private void populate(StaxParser.XmlRepository xml, int hopCount) {
if (hopCount > 0) {
for (Resource resource : xml.resources) {
addResource(resource);
}
for (StaxParser.Referral referral : xml.referrals) {
populate(loaders.get(referral.url).xml, Math.min(referral.depth, hopCount - 1));
}
}
}
private boolean checkAndLoadReferrals(String url, int hopCount) {
boolean modified = false;
if (hopCount > 0) {
XmlLoader loader = loaders.get(url);
if (loader == null) {
loader = new XmlLoader(url, expiration);
loaders.put(url, loader);
}
modified = loader.checkAndLoadCache();
for (StaxParser.Referral referral : loader.xml.referrals) {
modified |= checkAndLoadReferrals(referral.url, Math.min(referral.depth, hopCount - 1));
}
}
return modified;
}
protected static class XmlLoader extends UrlLoader {
protected StaxParser.XmlRepository xml;
public XmlLoader(String url, long expiration) {
super(url, expiration);
}
public XmlLoader(String url, long expiration, StaxParser.XmlRepository xml) {
super(url, expiration);
this.xml = xml;
}
@Override
protected boolean doRead(InputStream is) throws IOException {
try {
StaxParser.XmlRepository oldXml = xml;
xml = StaxParser.parse(URI.create(getUrl()), is, oldXml);
return oldXml != xml;
} catch (XMLStreamException e) {
throw new IOException("Unable to read xml repository", e);
}
}
}
}