blob: 397c94c857f30c33fc8f92052d71e04ea081838c [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.ace.client.repository.impl;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.ace.client.repository.ObjectRepository;
import org.apache.ace.client.repository.RepositoryAdmin;
import org.apache.ace.client.repository.RepositoryAdminLoginContext;
import org.apache.ace.client.repository.RepositoryObject;
import org.apache.ace.client.repository.SessionFactory;
import org.apache.ace.client.repository.RepositoryObject.WorkingState;
import org.apache.ace.client.repository.helper.ArtifactHelper;
import org.apache.ace.client.repository.impl.RepositoryAdminLoginContextImpl.RepositorySetDescriptor;
import org.apache.ace.client.repository.object.Artifact2GroupAssociation;
import org.apache.ace.client.repository.object.ArtifactObject;
import org.apache.ace.client.repository.object.DeploymentVersionObject;
import org.apache.ace.client.repository.object.GatewayObject;
import org.apache.ace.client.repository.object.Group2LicenseAssociation;
import org.apache.ace.client.repository.object.GroupObject;
import org.apache.ace.client.repository.object.License2GatewayAssociation;
import org.apache.ace.client.repository.object.LicenseObject;
import org.apache.ace.client.repository.repository.Artifact2GroupAssociationRepository;
import org.apache.ace.client.repository.repository.ArtifactRepository;
import org.apache.ace.client.repository.repository.DeploymentVersionRepository;
import org.apache.ace.client.repository.repository.GatewayRepository;
import org.apache.ace.client.repository.repository.Group2LicenseAssociationRepository;
import org.apache.ace.client.repository.repository.GroupRepository;
import org.apache.ace.client.repository.repository.License2GatewayAssociationRepository;
import org.apache.ace.client.repository.repository.LicenseRepository;
import org.apache.ace.repository.Repository;
import org.apache.ace.repository.ext.BackupRepository;
import org.apache.ace.repository.ext.CachedRepository;
import org.apache.ace.repository.impl.CachedRepositoryImpl;
import org.apache.ace.repository.impl.FilebasedBackupRepository;
import org.apache.ace.repository.impl.RemoteRepository;
import org.apache.felix.dm.DependencyManager;
import org.apache.felix.dm.service.Service;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.service.event.EventConstants;
import org.osgi.service.event.EventHandler;
import org.osgi.service.log.LogService;
import org.osgi.service.prefs.Preferences;
import org.osgi.service.prefs.PreferencesService;
import org.osgi.service.useradmin.User;
/**
* An implementation of RepositoryAdmin, responsible for managing <code>ObjectRepositoryImpl</code>
* descendants.<br>
* The actual repository managing is delegated to <code>RepositorySet</code>s, while the logic
* for binding these sets together is located in this class. Set actual <code>RepositorySet</code>s
* to be used are defined in <code>login(...)</code>.<br>
*/
public class RepositoryAdminImpl implements RepositoryAdmin {
/**
* Maps from interface classes of the ObjectRepositories to their implementations.
*/
@SuppressWarnings("unchecked")
private Map<Class<? extends ObjectRepository>, ObjectRepositoryImpl> m_repositories;
private final ChangeNotifier m_changeNotifier;
private volatile BundleContext m_context; /* Injected by dependency manager */
private volatile PreferencesService m_preferences; /* Injected by dependency manager */
private volatile LogService m_log; /* Injected by dependency manager */
private final Object m_lock = new Object();
private final static String PREFS_LOCAL_FILE_ROOT = "ClientRepositoryAdmin";
private final static String PREFS_LOCAL_FILE_LOCATION = "FileLocation";
private final static String PREFS_LOCAL_FILE_CURRENT = "current";
private final static String PREFS_LOCAL_FILE_BACKUP = "backup";
private User m_user;
private RepositorySet[] m_repositorySets;
private volatile DependencyManager m_manager;
List<Service[]> m_services;
private ArtifactRepositoryImpl m_artifactRepositoryImpl;
private GroupRepositoryImpl m_groupRepositoryImpl;
private Artifact2GroupAssociationRepositoryImpl m_artifact2GroupAssociationRepositoryImpl;
private LicenseRepositoryImpl m_licenseRepositoryImpl;
private Group2LicenseAssociationRepositoryImpl m_group2LicenseAssociationRepositoryImpl;
private GatewayRepositoryImpl m_gatewayRepositoryImpl;
private License2GatewayAssociationRepositoryImpl m_license2GatewayAssociationRepositoryImpl;
private DeploymentVersionRepositoryImpl m_deploymentVersionRepositoryImpl;
private ChangeNotifierManager m_changeNotifierManager;
private final String m_sessionID;
private final Properties m_sessionProps;
public RepositoryAdminImpl(String sessionID) {
m_sessionID = sessionID;
m_sessionProps = new Properties();
m_sessionProps.put(SessionFactory.SERVICE_SID, sessionID);
m_changeNotifierManager = new ChangeNotifierManager();
m_changeNotifier = m_changeNotifierManager.getConfiguredNotifier(RepositoryAdmin.PRIVATE_TOPIC_ROOT, RepositoryAdmin.PUBLIC_TOPIC_ROOT, RepositoryAdmin.TOPIC_ENTITY_ROOT, m_sessionID);
}
public Properties getSessionProps() {
return m_sessionProps;
}
/**
* Returns a list of instances that make up this composition. Instances are used to
* inject dependencies into.
*
* @return list of instances
*/
public Object[] getInstances() {
return new Object[] { this, m_changeNotifierManager };
}
@SuppressWarnings("unchecked")
void initialize(Map<Class<? extends ObjectRepository>, ObjectRepositoryImpl> repositories) {
m_repositories = repositories;
}
public void start() {
initialize(publishRepositories());
}
public void stop() {
pullRepositories();
if (loggedIn()) {
try {
logout(true);
}
catch (IOException ioe) {
m_log.log(LogService.LOG_ERROR, "Failed to log out of the repositories.", ioe);
}
}
}
@SuppressWarnings("unchecked")
synchronized Map<Class<? extends ObjectRepository>, ObjectRepositoryImpl> publishRepositories() {
// create the repository objects, if this is the first time this method is called.
if (m_artifactRepositoryImpl == null) {
m_artifactRepositoryImpl = new ArtifactRepositoryImpl(m_changeNotifierManager.getConfiguredNotifier(RepositoryObject.PRIVATE_TOPIC_ROOT, RepositoryObject.PUBLIC_TOPIC_ROOT, ArtifactObject.TOPIC_ENTITY_ROOT, m_sessionID));
m_groupRepositoryImpl = new GroupRepositoryImpl(m_changeNotifierManager.getConfiguredNotifier(RepositoryObject.PRIVATE_TOPIC_ROOT, RepositoryObject.PUBLIC_TOPIC_ROOT, GroupObject.TOPIC_ENTITY_ROOT, m_sessionID));
m_artifact2GroupAssociationRepositoryImpl = new Artifact2GroupAssociationRepositoryImpl(m_artifactRepositoryImpl, m_groupRepositoryImpl, m_changeNotifierManager.getConfiguredNotifier(RepositoryObject.PRIVATE_TOPIC_ROOT, RepositoryObject.PUBLIC_TOPIC_ROOT, Artifact2GroupAssociation.TOPIC_ENTITY_ROOT, m_sessionID));
m_licenseRepositoryImpl = new LicenseRepositoryImpl(m_changeNotifierManager.getConfiguredNotifier(RepositoryObject.PRIVATE_TOPIC_ROOT, RepositoryObject.PUBLIC_TOPIC_ROOT, LicenseObject.TOPIC_ENTITY_ROOT, m_sessionID));
m_group2LicenseAssociationRepositoryImpl = new Group2LicenseAssociationRepositoryImpl(m_groupRepositoryImpl, m_licenseRepositoryImpl, m_changeNotifierManager.getConfiguredNotifier(RepositoryObject.PRIVATE_TOPIC_ROOT, RepositoryObject.PUBLIC_TOPIC_ROOT, Group2LicenseAssociation.TOPIC_ENTITY_ROOT, m_sessionID));
m_gatewayRepositoryImpl = new GatewayRepositoryImpl(m_changeNotifierManager.getConfiguredNotifier(RepositoryObject.PRIVATE_TOPIC_ROOT, RepositoryObject.PUBLIC_TOPIC_ROOT, GatewayObject.TOPIC_ENTITY_ROOT, m_sessionID));
m_license2GatewayAssociationRepositoryImpl = new License2GatewayAssociationRepositoryImpl(m_licenseRepositoryImpl, m_gatewayRepositoryImpl, m_changeNotifierManager.getConfiguredNotifier(RepositoryObject.PRIVATE_TOPIC_ROOT, RepositoryObject.PUBLIC_TOPIC_ROOT, License2GatewayAssociation.TOPIC_ENTITY_ROOT, m_sessionID));
m_deploymentVersionRepositoryImpl = new DeploymentVersionRepositoryImpl(m_changeNotifierManager.getConfiguredNotifier(RepositoryObject.PRIVATE_TOPIC_ROOT, RepositoryObject.PUBLIC_TOPIC_ROOT, DeploymentVersionObject.TOPIC_ENTITY_ROOT, m_sessionID));
}
// first, register the artifact repository manually; it needs some special care.
Service artifactRepoService = m_manager.createComponent()
.setInterface(ArtifactRepository.class.getName(), m_sessionProps)
.setImplementation(m_artifactRepositoryImpl)
.add(m_manager.createServiceDependency().setService(LogService.class).setRequired(false))
.add(m_manager.createServiceDependency().setService(ArtifactHelper.class).setRequired(false).setAutoConfig(false).setCallbacks(this, "addArtifactHelper", "removeArtifactHelper"));
Dictionary topic = new Hashtable();
topic.put(EventConstants.EVENT_TOPIC, new String[] {});
topic.put(EventConstants.EVENT_FILTER, "(" + SessionFactory.SERVICE_SID + "=" + m_sessionID + ")");
Service artifactHandlerService = m_manager.createComponent()
.setInterface(EventHandler.class.getName(), topic)
.setImplementation(m_artifactRepositoryImpl);
m_manager.add(artifactRepoService);
m_manager.add(artifactHandlerService);
m_services = new ArrayList<Service[]>();
m_services.add(new Service[] {artifactRepoService, artifactHandlerService});
// register all repositories are services. Keep the service objects around, we need them to pull the services later.
m_services.add(registerRepository(Artifact2GroupAssociationRepository.class, m_artifact2GroupAssociationRepositoryImpl, new String[] {createPrivateObjectTopic(ArtifactObject.TOPIC_ENTITY_ROOT), createPrivateObjectTopic(GroupObject.TOPIC_ENTITY_ROOT)}));
m_services.add(registerRepository(GroupRepository.class, m_groupRepositoryImpl, new String[] {}));
m_services.add(registerRepository(Group2LicenseAssociationRepository.class, m_group2LicenseAssociationRepositoryImpl, new String[] {createPrivateObjectTopic(GroupObject.TOPIC_ENTITY_ROOT), createPrivateObjectTopic(LicenseObject.TOPIC_ENTITY_ROOT)}));
m_services.add(registerRepository(LicenseRepository.class, m_licenseRepositoryImpl, new String[] {}));
m_services.add(registerRepository(License2GatewayAssociationRepository.class, m_license2GatewayAssociationRepositoryImpl, new String[] {createPrivateObjectTopic(LicenseObject.TOPIC_ENTITY_ROOT), createPrivateObjectTopic(GatewayObject.TOPIC_ENTITY_ROOT)}));
m_services.add(registerRepository(GatewayRepository.class, m_gatewayRepositoryImpl, new String[] {}));
m_services.add(registerRepository(DeploymentVersionRepository.class, m_deploymentVersionRepositoryImpl, new String[] {}));
// prepare the results.
Map<Class<? extends ObjectRepository>, ObjectRepositoryImpl> result = new HashMap<Class<? extends ObjectRepository>, ObjectRepositoryImpl>();
result.put(ArtifactRepository.class, m_artifactRepositoryImpl);
result.put(Artifact2GroupAssociationRepository.class, m_artifact2GroupAssociationRepositoryImpl);
result.put(GroupRepository.class, m_groupRepositoryImpl);
result.put(Group2LicenseAssociationRepository.class, m_group2LicenseAssociationRepositoryImpl);
result.put(LicenseRepository.class, m_licenseRepositoryImpl);
result.put(License2GatewayAssociationRepository.class, m_license2GatewayAssociationRepositoryImpl);
result.put(GatewayRepository.class, m_gatewayRepositoryImpl);
result.put(DeploymentVersionRepository.class, m_deploymentVersionRepositoryImpl);
return result;
}
/**
* Pulls all repository services; is used to make sure the repositories go away before the RepositoryAdmin does.
*/
synchronized void pullRepositories() {
for (Service[] services : m_services) {
for (Service service : services) {
m_manager.remove(service);
}
}
}
@SuppressWarnings("unchecked")
private <T extends RepositoryObject> Service[] registerRepository(Class<? extends ObjectRepository<T>> iface, ObjectRepositoryImpl<?, T> imp, String[] topics) {
Service repositoryService = m_manager.createComponent()
.setInterface(iface.getName(), m_sessionProps)
.setImplementation(imp)
.add(m_manager.createServiceDependency().setService(LogService.class).setRequired(false));
Dictionary topic = new Hashtable();
topic.put(EventConstants.EVENT_TOPIC, topics);
topic.put(EventConstants.EVENT_FILTER, "(" + SessionFactory.SERVICE_SID + "=" + m_sessionID + ")");
Service handlerService = m_manager.createComponent()
.setInterface(EventHandler.class.getName(), topic)
.setImplementation(imp);
m_manager.add(repositoryService);
m_manager.add(handlerService);
return new Service[] {repositoryService, handlerService};
}
/**
* Helper method for use in publishRepositories
*/
private static String createPrivateObjectTopic(String entityRoot) {
return RepositoryObject.PRIVATE_TOPIC_ROOT + entityRoot + RepositoryObject.TOPIC_ALL_SUFFIX;
}
public void checkout() throws IOException {
synchronized (m_lock) {
ensureLogin();
for (RepositorySet set : m_repositorySets) {
set.checkout();
}
m_changeNotifier.notifyChanged(TOPIC_REFRESH_SUFFIX, null);
}
}
public void commit() throws IOException {
synchronized (m_lock) {
ensureLogin();
for (RepositorySet set : m_repositorySets) {
set.commit();
}
m_changeNotifier.notifyChanged(TOPIC_REFRESH_SUFFIX, null);
}
}
public void flush() throws IOException {
synchronized (m_lock) {
ensureLogin();
for (RepositorySet set : m_repositorySets) {
set.writeLocal();
set.savePreferences();
}
m_changeNotifier.notifyChanged(TOPIC_FLUSHED_SUFFIX, null);
}
}
public void revert() throws IOException {
ensureLogin();
synchronized (m_lock) {
for (RepositorySet set : m_repositorySets) {
set.revert();
}
m_changeNotifier.notifyChanged(TOPIC_REFRESH_SUFFIX, null);
}
}
public boolean isCurrent() throws IOException {
ensureLogin();
synchronized (m_lock) {
boolean result = true;
for (RepositorySet set : m_repositorySets) {
result &= (set.isCurrent() || !set.writeAccess());
}
return result;
}
}
public boolean isModified() {
ensureLogin();
boolean result = false;
for (RepositorySet set : m_repositorySets) {
result |= set.isModified();
}
return result;
}
public RepositoryAdminLoginContext createLoginContext(User user) {
if (user == null) {
throw new IllegalArgumentException("User may not be null.");
}
return new RepositoryAdminLoginContextImpl(user, m_sessionID);
}
public void login(RepositoryAdminLoginContext context) throws IOException {
if (!(context instanceof RepositoryAdminLoginContextImpl)) {
throw new IllegalArgumentException("Only the RepositoryAdminLoginContext returned by createLoginContext can be used.");
}
RepositoryAdminLoginContextImpl impl = ((RepositoryAdminLoginContextImpl) context);
RepositorySet[] repositorySets = getRepositorySets(impl);
// TODO I don't like this line, it should not be here...
((ArtifactRepositoryImpl) m_repositories.get(ArtifactRepository.class)).setObrBase(impl.getObrBase());
login(impl.getUser(), repositorySets);
}
/**
* Helper method for login; also allows injection of custom RepositorySet objects for
* testing purposes.
* @throws IOException
*/
void login(User user, RepositorySet[] sets) throws IOException {
synchronized(m_lock) {
if (m_user != null) {
throw new IllegalStateException("Another user is logged in.");
}
m_user = user;
m_repositorySets = sets;
for (RepositorySet set : m_repositorySets) {
set.readLocal();
set.loadPreferences();
}
}
m_changeNotifier.notifyChanged(TOPIC_LOGIN_SUFFIX, null);
}
public void logout(boolean force) throws IOException {
IOException exception = null;
synchronized (m_lock) {
ensureLogin();
try {
flush();
}
catch (IOException e) {
if (!force) {
throw e;
}
else {
exception = e;
}
}
for (RepositorySet set : m_repositorySets) {
set.clearRepositories();
set.unregisterHandler();
}
m_user = null;
m_repositorySets = new RepositorySet[0];
}
m_changeNotifier.notifyChanged(TOPIC_LOGOUT_SUFFIX, null);
if (exception != null) {
throw exception;
}
}
boolean loggedIn() {
return m_user != null;
}
/**
* Helper method to make sure a user is logged in.
* @throws IllegalStateException
*/
private void ensureLogin() throws IllegalStateException {
if (!loggedIn()) {
throw new IllegalStateException("This operation requires a user to be logged in.");
}
}
/**
* Helper method, creates RepositorySets based on the Login context.
*/
@SuppressWarnings("unchecked")
private RepositorySet[] getRepositorySets(RepositoryAdminLoginContextImpl context) throws IOException {
// First, some sanity checks on the list of descriptors.
for (RepositorySetDescriptor rsd : context.getDescriptors()) {
for (Class c : rsd.m_objectRepositories) {
// Do we have an impl for each repository class?
if (!m_repositories.containsKey(c)) {
throw new IllegalArgumentException(rsd.toString() + " references repository class " + c.getName() + " for which no implementation is available.");
}
// Do other sets have a reference to this same class?
for (RepositorySetDescriptor other : context.getDescriptors()) {
if (other != rsd) {
for (Class otherC : other.m_objectRepositories) {
if (c.equals(otherC)) {
throw new IllegalArgumentException(rsd.toString() + " references repository class " + c.getName() + ", but so does " + other.toString());
}
}
}
}
}
}
RepositorySet[] result = new RepositorySet[context.getDescriptors().size()];
/*
* Create the lists of repositories and topics, and create and register
* the sets with these.
*/
for (int nRsd = 0; nRsd < result.length; nRsd++) {
RepositorySetDescriptor rsd = context.getDescriptors().get(nRsd);
ObjectRepositoryImpl[] impls = new ObjectRepositoryImpl[rsd.m_objectRepositories.length];
String[] topics = new String[rsd.m_objectRepositories.length];
for (int nRepo = 0; nRepo < impls.length; nRepo++) {
impls[nRepo] = m_repositories.get(rsd.m_objectRepositories[nRepo]);
topics[nRepo] = impls[nRepo].getTopicAll(true);
}
result[nRsd] = loadRepositorySet(context.getUser(), rsd, impls);
result[nRsd].registerHandler(m_context, m_sessionID, topics);
}
return result;
}
/**
* Helper method for login.
*/
private Preferences getRepositoryPrefs(Preferences userPrefs, URL location, String customer, String name) {
// Note: we can only use the getAuthority part of the URL for indexing, because the full URL will contain
// in the protocol part.
Preferences repoPref = userPrefs.node(location.getAuthority() + location.getPath());
Preferences customerPref = repoPref.node(customer);
return customerPref.node(name);
}
/**
* Helper method for login.
* @throws IOException
*/
private File getFileFromPreferences(Preferences repositoryPrefs, String type) throws IOException {
String sessionLocation = PREFS_LOCAL_FILE_LOCATION + m_sessionID;
String directory = repositoryPrefs.get(sessionLocation, "");
if ((directory == "") || !m_context.getDataFile(PREFS_LOCAL_FILE_ROOT + "/" + directory).isDirectory()) {
if (!m_context.getDataFile(PREFS_LOCAL_FILE_ROOT + "/" + directory).isDirectory() && (directory != "")) {
m_log.log(LogService.LOG_WARNING, "Directory '" + directory + "' should exist according to the preferences, but it does not.");
}
// The file did not exist, so create a new one.
File directoryFile = null;
try {
File bundleDataDir = m_context.getDataFile(PREFS_LOCAL_FILE_ROOT);
if (!bundleDataDir.isDirectory()) {
if (!bundleDataDir.mkdir()) {
throw new IOException("Error creating the local repository root directory.");
}
}
directoryFile = File.createTempFile("repo", "", bundleDataDir);
}
catch (IOException e) {
// We cannot create the temp file? Then something is seriously wrong, so rethrow.
throw e;
}
directoryFile.delete(); // No problem if this goes wrong, it just means it wasn't there yet.
if (!directoryFile.mkdir()) {
throw new IOException("Error creating the local repository storage directory.");
}
repositoryPrefs.put(sessionLocation, directoryFile.getName());
return new File(directoryFile, type);
}
else {
// Get the given file from that location.
return m_context.getDataFile(PREFS_LOCAL_FILE_ROOT + "/" + directory + "/" + type);
}
}
/**
* Helper method for login.
* @throws IOException
*/
private BackupRepository getBackupFromPreferences(Preferences repositoryPrefs) throws IOException {
File current = getFileFromPreferences(repositoryPrefs, PREFS_LOCAL_FILE_CURRENT);
File backup = getFileFromPreferences(repositoryPrefs, PREFS_LOCAL_FILE_BACKUP);
return new FilebasedBackupRepository(current, backup);
}
/**
* Helper method for login.
* @throws IOException
*/
private CachedRepository getCachedRepositoryFromPreferences(User user, Repository repository, Preferences repositoryPrefs) throws IOException {
long mostRecentVersion = repositoryPrefs.getLong("version", CachedRepositoryImpl.UNCOMMITTED_VERSION);
return new CachedRepositoryImpl(user, repository, getBackupFromPreferences(repositoryPrefs), mostRecentVersion);
}
/**
* Helper method for login, which loads a set of repositories.
* @param user A <code>User</code> object
* @param rsd A RepositorySetDescriptor, defining the set to be created.
* @param repos An array of <code>ObjectRepositoryImpl</code> which this set should manage. Each
* @return The newly created repository set.
* @throws IOException
*/
@SuppressWarnings("unchecked")
public RepositorySet loadRepositorySet(User user, RepositorySetDescriptor rsd, ObjectRepositoryImpl[] repos) throws IOException {
Repository repo = new RemoteRepository(rsd.m_location, rsd.m_customer, rsd.m_name);
Preferences prefs = m_preferences.getUserPreferences(user.getName());
Preferences repoPrefs = getRepositoryPrefs(prefs, rsd.m_location, rsd.m_customer, rsd.m_name);
return new RepositorySet(m_changeNotifier, m_log, user, repoPrefs, repos, getCachedRepositoryFromPreferences(user, repo, repoPrefs), rsd.m_name, rsd.m_writeAccess);
}
public int getNumberWithWorkingState(Class<? extends RepositoryObject> clazz, WorkingState state) {
int result = 0;
for (RepositorySet set : m_repositorySets) {
result += set.getNumberWithWorkingState(clazz, state);
}
return result;
}
public WorkingState getWorkingState(RepositoryObject object) {
for (RepositorySet set : m_repositorySets) {
WorkingState result = set.getWorkingState(object);
if (result != null) {
return result;
}
}
return WorkingState.Unchanged;
}
public void addArtifactHelper(ServiceReference ref, ArtifactHelper helper) {
String mimetype = (String) ref.getProperty(ArtifactHelper.KEY_MIMETYPE);
m_artifactRepositoryImpl.addHelper(mimetype, helper);
}
public synchronized void removeArtifactHelper(ServiceReference ref, ArtifactHelper helper) {
String mimetype = (String) ref.getProperty(ArtifactHelper.KEY_MIMETYPE);
m_artifactRepositoryImpl.removeHelper(mimetype, helper);
}
}