package fr.imag.adele.apam.declarations.repository.acr;
import java.net.URL;
import java.text.ParseException;
import java.util.List;
import org.apache.felix.bundlerepository.Capability;
import org.apache.felix.bundlerepository.RepositoryAdmin;
import org.apache.felix.bundlerepository.Requirement;
import org.apache.felix.bundlerepository.Resource;
import org.osgi.framework.Version;
import fr.imag.adele.apam.CST;
import fr.imag.adele.apam.declarations.ComponentDeclaration;
import fr.imag.adele.apam.declarations.PropertyDefinition;
import fr.imag.adele.apam.declarations.Reporter;
import fr.imag.adele.apam.declarations.Reporter.Severity;
import fr.imag.adele.apam.declarations.encoding.Decoder;
import fr.imag.adele.apam.declarations.encoding.capability.CapabilityParser;
import fr.imag.adele.apam.declarations.references.components.ComponentReference;
import fr.imag.adele.apam.declarations.references.components.VersionedReference;
import fr.imag.adele.apam.declarations.repository.ComponentIndex;
import fr.imag.adele.apam.declarations.repository.Repository;
/**
* This class handles a component repository backed up by the felix OSGi Bundle Repository extended
* with APAM metadata.
*
* It is intended to be used both at build and runtime. It only relies on the OBR's ability to query
* its index, and not on installation or runtime platform resolution.
*
*
* TODO This class can be used at build-time by mocking-up some of the runtime OSGi dependencies,
* we rather should try to directly reuse the felix OBR resolver, even if that means depending
* on private classes.
*/
public class ApamComponentRepository implements Repository {
private final Reporter reporter;
private final RepositoryAdmin manager;
private final ComponentIndex cache;
public ApamComponentRepository(RepositoryAdmin manager, List<URL> repositories, Reporter reporter) throws Exception {
this.reporter = reporter;
this.cache = new ComponentIndex();
if (repositories == null || repositories.isEmpty()) {
info("No repository URLs specified");
this.manager = null;
return;
}
else {
this.manager = manager;
}
for (URL repository : repositories) {
try {
manager.addRepository(repository);
info("Repository location "+repository+ " added to ACR");
} catch (Exception exc) {
warning("Error when adding repository " + repository + ", reason " + exc.getMessage());
}
}
if (manager.listRepositories().length == 0) {
warning("No valid repository");
}
}
/**
* Get the bundle repository manager used by this component repository
*/
public RepositoryAdmin getManager() {
return manager;
}
/**
* Get the OBR filter corresponding to a given version range specification
*/
public static String filter(VersionedReference<?> referenceRange) throws ParseException {
String name = referenceRange.getName();
String range = referenceRange.getRange();
StringBuilder filter = new StringBuilder();
if (range == null) {
filter.append("(name=").append(name).append(")");
}
else {
filter.append("(& ");
filter.append("(name=").append(name).append(")");
int rangeSeparator = range.indexOf(",");
if(rangeSeparator != -1) {
if ( !range.startsWith("(") && !range.startsWith("["))
throw new ParseException("VersionedReference range does not start with a correct delimiter",1);
if ( !range.endsWith(")") && !range.endsWith("]"))
throw new ParseException("VersionedReference range does not end with a correct delimiter",range.length());
String start = range.substring(1,rangeSeparator).trim();
String end = range.substring(rangeSeparator+1,range.length()-1).trim();
version(start);
version(end);
limit(filter,start,true,range.startsWith("["));
limit(filter,end,false,range.endsWith("]"));
} else {
version(range);
filter.append("("+CST.VERSION+"=").append(range).append(")");
}
filter.append(")");
}
return filter.toString();
}
private static void limit(StringBuilder filter, String limit, boolean inferior, boolean closed) throws ParseException {
filter.append("(").append(CST.VERSION).append(inferior ? ">=" : "<=").append(limit).append(")");
if( !closed ) {
filter.append("(!").append("(").append(CST.VERSION).append("=").append(limit).append(")").append(")");
}
}
private static Version version(String version) throws ParseException {
if(version == null || version.isEmpty()) {
throw new ParseException("Version is empty ",0);
}
try{
return Version.parseVersion(version);
} catch (IllegalArgumentException exc) {
throw new ParseException(exc.getMessage(),0);
}
}
@Override
public <C extends ComponentDeclaration> C getComponent(ComponentReference<C> reference) {
return reference != null? getComponent(VersionedReference.any(reference)) : null;
}
@Override
public <C extends ComponentDeclaration> C getComponent(VersionedReference<C> referenceRange) {
/*
* Try to find a cached component declaration
*/
C component = cache.getComponent(referenceRange);
if (component != null)
return component;
/*
* If not found, look directly in the repository and load the cache
*/
try {
loadResources(referenceRange);
} catch (Exception error) {
error("Error loading resources from repository: "+error.getMessage());
}
return cache.getComponent(referenceRange);
}
/**
* Loads in the cache all declarations found in resources of ACR that contain the specified
* component version.
*/
private void loadResources(VersionedReference<?> referenceRange) throws Exception {
info("loading resources containing apam-component : "+referenceRange.getName()+", version range :"+referenceRange.getRange());
if(manager == null || manager.listRepositories() == null || manager.listRepositories().length == 0) {
info("No valid repository, returning empty capability list");
return;
}
info("requirement research (ldap filter) "+ filter(referenceRange));
Resource[] resources = manager.discoverResources(new Requirement[] {
manager.getHelper().requirement("apam-component", filter(referenceRange))}
);
if(resources == null || resources.length<1) {
return;
}
Decoder<Capability> parser = new CapabilityParser();
for(Resource resource : resources) {
info("Resource found : "+resource.getId());
for(Capability capability : resource.getCapabilities()) {
if (!CapabilityParser.isComponent(capability))
continue;
ComponentDeclaration component = parser.decode(capability,reporter);
/*
* Components coming from the ACR have inherited properties and default values added
* at build time, and the declaration stored in the repository is actually effective.
*
* However, we must allow members of a group to define properties that are not valued
* in the group, sowe remove then automatically the default values
*
* TODO We need a way to distinguish default values added at build time, from explicitly
* defined properties that happen to have the same value
*/
for (PropertyDefinition property : component.getPropertyDefinitions()) {
String defaultValue = property.getDefaultValue();
String value = component.getProperty(property.getName());
if (value != null && defaultValue != null && defaultValue.equals(value)) {
component.getProperties().remove(property.getName());
}
}
/*
* Add version if not specified
*/
addProperty(component,CST.VERSION, "version", resource.getVersion().toString());
cache.put(component);
}
}
}
/**
* Add a property to an existing component
*
* NOTE We may be modifying a component that has already property information attached (either because the
* component has already been built, and we are loading it as a dependency, or because the user has added
* the information manually) so we need to be careful not to override it
*
*/
private static final void addProperty(ComponentDeclaration component, String property, String type, String value) {
/*
*/
PropertyDefinition defintition = component.getPropertyDefinition(property);
if (defintition == null) {
defintition = new PropertyDefinition(component.getReference(), property, type, null);
component.getPropertyDefinitions().add(defintition);
}
String currentValue = component.getProperty(property);
if (currentValue == null) {
component.getProperties().put(property, value);
}
}
public final void error(String message) {
reporter.report(Severity.ERROR, message);
}
public final void warning(String message) {
reporter.report(Severity.WARNING, message);
}
public final void info(String message) {
reporter.report(Severity.INFO, message);
}
}