blob: 08e1d534f5181863a68b25b4c67a6552aa392238 [file] [log] [blame]
/*
* Copyright (c) 2013, 2015 Eike Stepper (Berlin, Germany) and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Christian W. Damus (CEA LIST) - initial API and implementation
*/
package org.eclipse.emf.cdo.server.internal.admin;
import org.eclipse.emf.cdo.common.lob.CDOClobWriter;
import org.eclipse.emf.cdo.common.lob.CDOLobUtil;
import org.eclipse.emf.cdo.eresource.CDOResource;
import org.eclipse.emf.cdo.security.Access;
import org.eclipse.emf.cdo.security.FilterPermission;
import org.eclipse.emf.cdo.security.Realm;
import org.eclipse.emf.cdo.security.Role;
import org.eclipse.emf.cdo.security.SecurityFactory;
import org.eclipse.emf.cdo.security.User;
import org.eclipse.emf.cdo.server.IRepository;
import org.eclipse.emf.cdo.server.admin.CDORepositoryConfigurationManager;
import org.eclipse.emf.cdo.server.internal.admin.bundle.OM;
import org.eclipse.emf.cdo.server.internal.admin.catalog.CatalogFactory;
import org.eclipse.emf.cdo.server.internal.admin.catalog.RepositoryCatalog;
import org.eclipse.emf.cdo.server.internal.admin.catalog.RepositoryConfiguration;
import org.eclipse.emf.cdo.server.security.ISecurityManager;
import org.eclipse.emf.cdo.server.security.ISecurityManager.RealmOperation;
import org.eclipse.emf.cdo.server.security.SecurityManagerUtil;
import org.eclipse.emf.cdo.spi.server.AuthenticationUtil;
import org.eclipse.emf.cdo.spi.server.IAppExtension;
import org.eclipse.emf.cdo.spi.server.IAppExtension2;
import org.eclipse.emf.cdo.spi.server.IAuthenticationProtocol;
import org.eclipse.emf.cdo.spi.server.InternalRepository;
import org.eclipse.emf.cdo.spi.server.InternalSessionManager;
import org.eclipse.emf.cdo.spi.server.RepositoryConfigurator;
import org.eclipse.emf.cdo.spi.server.RepositoryFactory;
import org.eclipse.emf.cdo.transaction.CDOTransaction;
import org.eclipse.net4j.util.ObjectUtil;
import org.eclipse.net4j.util.StringUtil;
import org.eclipse.net4j.util.WrappedException;
import org.eclipse.net4j.util.container.ContainerEventAdapter;
import org.eclipse.net4j.util.container.IContainer;
import org.eclipse.net4j.util.container.IManagedContainer;
import org.eclipse.net4j.util.container.IManagedContainer.ContainerAware;
import org.eclipse.net4j.util.container.IPluginContainer;
import org.eclipse.net4j.util.event.IListener;
import org.eclipse.net4j.util.factory.ProductCreationException;
import org.eclipse.net4j.util.lifecycle.ILifecycle;
import org.eclipse.net4j.util.lifecycle.Lifecycle;
import org.eclipse.net4j.util.lifecycle.LifecycleEventAdapter;
import org.eclipse.net4j.util.lifecycle.LifecycleUtil;
import org.eclipse.net4j.util.om.trace.ContextTracer;
import org.eclipse.net4j.util.security.IAuthenticator2;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.Platform;
import org.w3c.dom.Document;
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 java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* A default implementation of the {@link CDORepositoryConfigurationManager} interface
* that stores dynamically created repositories' configurations in XML files.
*
* @author Christian W. Damus (CEA LIST)
*/
public class DefaultCDORepositoryConfigurationManager extends Lifecycle
implements InternalCDORepositoryConfigurationManager, ContainerAware
{
private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG,
DefaultCDORepositoryConfigurationManager.class);
private IManagedContainer container = IPluginContainer.INSTANCE;
private InternalRepository adminRepository;
private String catalogPath = DEFAULT_CATALOG_PATH;
private RepositoryCatalog catalog;
private ISecurityManager securityManager;
private final IListener repositoryListener = new LifecycleEventAdapter()
{
@Override
protected void onActivated(ILifecycle lifecycle)
{
initializeCatalog();
}
@Override
protected void onDeactivated(ILifecycle lifecycle)
{
DefaultCDORepositoryConfigurationManager.this.deactivate();
}
};
public DefaultCDORepositoryConfigurationManager()
{
}
public IManagedContainer getManagedContainer()
{
return container;
}
public void setManagedContainer(IManagedContainer container)
{
this.container = container;
}
public void setAdminRepository(InternalRepository adminRepository)
{
this.adminRepository = adminRepository;
if (isActive())
{
initializeCatalog();
}
}
public void setCatalogPath(String catalogPath)
{
this.catalogPath = StringUtil.isEmpty(catalogPath) ? DEFAULT_CATALOG_PATH : catalogPath;
}
public IRepository addRepository(String name, Document configurationXML)
{
checkActive();
IManagedContainer container = requireContainer();
if (getRepository(container, name) != null)
{
throw new IllegalArgumentException("Repository already exists: " + name); //$NON-NLS-1$
}
try
{
RepositoryConfiguration configuration = createConfiguration(name, configurationXML);
return startRepository(configuration);
}
catch (RuntimeException e)
{
OM.LOG.error(e);
throw e;
}
catch (Exception e)
{
OM.LOG.error(e);
throw WrappedException.wrap(e);
}
}
public void removeRepository(IRepository repository)
{
checkActive();
deleteConfiguration(repository.getName());
}
public boolean canRemoveRepository(IRepository repository)
{
checkActive();
return isInCatalog(repository.getName());
}
public Map<String, IRepository> getRepositories()
{
checkActive();
Map<String, IRepository> result = new java.util.HashMap<String, IRepository>();
if (catalog != null)
{
for (RepositoryConfiguration configuration : catalog.getRepositories())
{
IRepository repository = getRepository(getManagedContainer(), configuration.getName());
if (repository != null)
{
result.put(repository.getName(), repository);
}
}
}
return result;
}
public void authenticateAdministrator()
{
requireSecurityManager();
IAuthenticationProtocol authProtocol = AuthenticationUtil.getAuthenticationProtocol();
if (authProtocol != null)
{
InternalSessionManager sessionManager = adminRepository.getSessionManager();
String userID = sessionManager.authenticateUser(authProtocol);
// If the user could authenticate without an ID, then there is no security manager
if (userID != null)
{
IAuthenticator2 auth = ObjectUtil.tryCast(sessionManager.getAuthenticator(), IAuthenticator2.class);
if (auth != null && !auth.isAdministrator(userID))
{
throw new SecurityException("Must be a server administrator to add or delete repositories.");
}
}
}
}
@Override
protected void doActivate() throws Exception
{
if (TRACER.isEnabled())
{
TRACER.format("Starting dynamically managed repositories.");
}
initializeCatalog();
}
@Override
protected void doDeactivate() throws Exception
{
if (TRACER.isEnabled())
{
TRACER.format("Stopping dynamically managed repositories.");
}
// Stop any repositories that I manage
for (IRepository repository : getRepositories().values())
{
try
{
LifecycleUtil.deactivate(repository);
}
catch (Exception e)
{
OM.LOG.error(e);
}
}
catalog = null;
}
protected IManagedContainer requireContainer()
{
IManagedContainer result = getManagedContainer();
if (result == null)
{
throw new IllegalStateException("No container."); //$NON-NLS-1$
}
return result;
}
protected IRepository startRepository(RepositoryConfiguration configuration) throws Exception
{
RepositoryConfigurator configurator = new RepositoryConfigurator(requireContainer());
IRepository[] result = configurator.configure(configuration.getConfigXML().getContents());
if (result.length == 1)
{
startExtensions(result[0], configuration);
}
return result.length == 0 ? null : result[0];
}
protected final ISecurityManager requireSecurityManager()
{
if (securityManager == null)
{
throw new IllegalStateException("The administrative repository does not have a security manager.");
}
return securityManager;
}
protected <T> T modify(CatalogOperation<T> operation)
{
return modify(operation, false);
}
protected <T> T modify(final CatalogOperation<T> operation, boolean waitUntilReadable)
{
checkActive();
final Object[] result = new Object[1];
requireSecurityManager().modify(new RealmOperation()
{
public void execute(Realm realm)
{
try
{
RepositoryCatalog localCatalog = realm.cdoView().getObject(catalog);
result[0] = operation.execute(localCatalog);
}
catch (Exception e)
{
throw WrappedException.wrap(e);
}
}
}, waitUntilReadable);
// This cast is known to be safe according to the operation signature
@SuppressWarnings("unchecked")
T resultAsT = (T)result[0];
return resultAsT;
}
private IRepository getRepository(IManagedContainer container, String name)
{
for (Object element : container.getElements(RepositoryFactory.PRODUCT_GROUP))
{
if (element instanceof IRepository)
{
IRepository repository = (IRepository)element;
if (repository.getName().equals(name))
{
return repository;
}
}
}
return null;
}
private void startExtensions(IRepository repository, RepositoryConfiguration configuration)
{
final List<IAppExtension2> extensions = new ArrayList<IAppExtension2>(3);
IExtensionRegistry registry = Platform.getExtensionRegistry();
@SuppressWarnings("restriction")
IConfigurationElement[] elements = registry
.getConfigurationElementsFor(org.eclipse.emf.cdo.internal.server.bundle.OM.BUNDLE_ID, IAppExtension.EXT_POINT);
for (final IConfigurationElement element : elements)
{
if ("appExtension".equals(element.getName())) //$NON-NLS-1$
{
try
{
IAppExtension extension = (IAppExtension)element.createExecutableExtension("class"); //$NON-NLS-1$
if (extension instanceof IAppExtension2)
{
IAppExtension2 extension2 = (IAppExtension2)extension;
extension2.startDynamic(configuration.getConfigXML().getContents());
extensions.add(extension2);
}
}
catch (Exception ex)
{
OM.LOG.error(ex);
}
}
}
if (!extensions.isEmpty())
{
// I have added some extensions, so stop them if and when the repository is shut down
repository.addListener(new LifecycleEventAdapter()
{
@Override
protected void onDeactivated(ILifecycle lifecycle)
{
for (IAppExtension extension : extensions)
{
try
{
extension.stop();
}
catch (Exception e)
{
OM.LOG.error(e);
}
}
}
});
}
}
private RepositoryConfiguration createConfiguration(final String repositoryName, final Document configuration)
throws Exception
{
modify(new CatalogOperation<RepositoryConfiguration>()
{
public RepositoryConfiguration execute(RepositoryCatalog catalog) throws Exception
{
RepositoryConfiguration result = CatalogFactory.eINSTANCE.createRepositoryConfiguration();
result.setName(repositoryName);
CDOClobWriter writer = CDOLobUtil.createClobWriter();
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); //$NON-NLS-1$ //$NON-NLS-2$
StreamResult streamResult = new StreamResult(writer);
DOMSource source = new DOMSource(configuration);
transformer.transform(source, streamResult);
writer.close();
result.setConfigXML(writer.getClob());
catalog.getRepositories().add(result);
return result;
}
}, true);
return catalog.getRepository(repositoryName);
}
private void deleteConfiguration(final String repositoryName)
{
modify(new CatalogOperation<Void>()
{
public Void execute(RepositoryCatalog catalog) throws Exception
{
RepositoryConfiguration configuration = catalog.getRepository(repositoryName);
if (configuration != null)
{
EcoreUtil.remove(configuration);
}
return null;
}
});
}
private boolean isInCatalog(String repositoryName)
{
return catalog.getRepository(repositoryName) != null;
}
private void initializeCatalog()
{
if (catalog != null)
{
// Already initialized
return;
}
if (adminRepository == null)
{
// Cannot initialize
return;
}
adminRepository.addListener(repositoryListener);
if (!LifecycleUtil.isActive(adminRepository))
{
// Cannot initialize now
return;
}
securityManager = SecurityManagerUtil.getSecurityManager(adminRepository);
if (securityManager == null)
{
// We are initializing ahead of the Security Manager. Wait for it
getManagedContainer().addListener(new ContainerEventAdapter<Object>()
{
@Override
protected void onAdded(IContainer<Object> container, Object element)
{
if (element instanceof ISecurityManager)
{
ISecurityManager securityManager = (ISecurityManager)element;
if (securityManager.getRepository() == adminRepository)
{
// This is my repository's security manager. Finish initialization
try
{
initializeCatalog();
}
finally
{
// Don't need any further events
container.removeListener(this);
}
}
}
}
});
// Double-check
securityManager = SecurityManagerUtil.getSecurityManager(adminRepository);
if (securityManager == null)
{
return;
}
}
requireSecurityManager().modify(new RealmOperation()
{
public void execute(Realm realm)
{
CDOTransaction initialTransaction = (CDOTransaction)realm.cdoView();
boolean firstTime = !initialTransaction.hasResource(catalogPath);
if (firstTime)
{
CDOResource resource = initialTransaction.createResource(catalogPath);
catalog = createCatalog(resource, realm);
OM.LOG.info("Repository catalog created in " + catalogPath);
}
else
{
CDOResource resource = initialTransaction.getResource(catalogPath);
catalog = (RepositoryCatalog)resource.getContents().get(0);
OM.LOG.info("Repository catalog loaded from " + catalogPath);
}
}
});
// Get the read-only view of the catalog now from the security manager's view
requireSecurityManager().read(new RealmOperation()
{
public void execute(Realm realm)
{
catalog = realm.cdoView().getObject(catalog);
}
});
// And load our repositories
if (catalog != null)
{
OM.LOG.info("Starting managed repositories.");
for (RepositoryConfiguration configuration : catalog.getRepositories())
{
try
{
startRepository(configuration);
}
catch (Exception e)
{
OM.LOG.error(e);
}
}
OM.LOG.info("Managed repositories started.");
}
}
private RepositoryCatalog createCatalog(CDOResource resource, Realm realm)
{
RepositoryCatalog result = CatalogFactory.eINSTANCE.createRepositoryCatalog();
resource.getContents().add(result);
// Give the Administrator read access to the catalog
Role serverAdmin = realm.addRole("Server Administration"); //$NON-NLS-1$
FilterPermission catalogAccess = SecurityFactory.eINSTANCE.createFilterPermission(Access.READ,
SecurityFactory.eINSTANCE.createResourceFilter(resource.getPath()));
serverAdmin.getPermissions().add(catalogAccess);
realm.getUser(User.ADMINISTRATOR).getRoles().add(serverAdmin);
return result;
}
/**
* @author Christian W. Damus (CEA LIST)
*/
public static class Factory extends CDORepositoryConfigurationManager.Factory
{
public static final String TYPE = "default"; //$NON-NLS-1$
public Factory()
{
super(TYPE);
}
@Override
public CDORepositoryConfigurationManager create(String description) throws ProductCreationException
{
DefaultCDORepositoryConfigurationManager result = new DefaultCDORepositoryConfigurationManager();
result.setCatalogPath(description);
return result;
}
}
protected static interface CatalogOperation<T>
{
public T execute(RepositoryCatalog catalog) throws Exception;
}
}