/*
* Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) 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:
* Florent Guillaume
*/
package org.eclipse.ecr.core.storage.sql;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.naming.Reference;
import javax.resource.cci.ConnectionSpec;
import javax.resource.cci.RecordFactory;
import javax.resource.cci.ResourceAdapterMetaData;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.ecr.core.event.EventService;
import org.eclipse.ecr.core.schema.SchemaManager;
import org.eclipse.ecr.core.storage.Credentials;
import org.eclipse.ecr.core.storage.StorageException;
import org.eclipse.ecr.core.storage.sql.RepositoryDescriptor.ServerDescriptor;
import org.eclipse.ecr.core.storage.sql.Session.PathResolver;
import org.eclipse.ecr.core.storage.sql.jdbc.JDBCBackend;
import org.eclipse.ecr.core.storage.sql.net.BinaryManagerClient;
import org.eclipse.ecr.core.storage.sql.net.BinaryManagerServlet;
import org.eclipse.ecr.core.storage.sql.net.MapperClientInfo;
import org.eclipse.ecr.core.storage.sql.net.MapperServlet;
import org.eclipse.ecr.core.storage.sql.net.NetBackend;
import org.eclipse.ecr.core.storage.sql.net.NetServer;
import org.eclipse.ecr.runtime.api.Framework;
/**
* {@link Repository} implementation, to be extended by backend-specific
* initialization code.
*
* @see RepositoryBackend
*/
public class RepositoryImpl implements Repository {
private static final long serialVersionUID = 1L;
private static final Log log = LogFactory.getLog(RepositoryImpl.class);
public static final String RUNTIME_SERVER_HOST = "org.eclipse.ecr.runtime.server.host";
public static final String SERVER_PATH_VCS = "vcs";
public static final String SERVER_PATH_BINARY = "binary";
protected final RepositoryDescriptor repositoryDescriptor;
protected final MultiThreadedHttpConnectionManager connectionManager;
protected final HttpClient httpClient;
protected final SchemaManager schemaManager;
protected final EventService eventService;
protected final BinaryManager binaryManager;
private final RepositoryBackend backend;
private final Collection<SessionImpl> sessions;
private LockManager lockManager;
/** Propagator of invalidations to all local mappers' caches. */
private final InvalidationsPropagator cachePropagator;
/**
* Propagator of event invalidations to all event queues (only one queue if
* there are not remote client repositories).
*/
private final InvalidationsPropagator eventPropagator;
/** Single event queue global to the repository. */
private final InvalidationsQueue repositoryEventQueue;
private Model model;
private boolean serverStarted;
private boolean binaryServerStarted;
/**
* Transient id for this repository assigned by the server on first
* connection. This is not persisted.
*/
public String repositoryId;
public RepositoryImpl(RepositoryDescriptor repositoryDescriptor)
throws StorageException {
this.repositoryDescriptor = repositoryDescriptor;
sessions = new CopyOnWriteArrayList<SessionImpl>();
cachePropagator = new InvalidationsPropagator();
eventPropagator = new InvalidationsPropagator();
repositoryEventQueue = new InvalidationsQueue("repo-"
+ repositoryDescriptor.name);
try {
schemaManager = Framework.getService(SchemaManager.class);
} catch (Exception e) {
throw new StorageException(e);
}
try {
eventService = Framework.getService(EventService.class);
} catch (Exception e) {
throw new StorageException(e);
}
connectionManager = new MultiThreadedHttpConnectionManager();
HttpConnectionManagerParams params = connectionManager.getParams();
params.setDefaultMaxConnectionsPerHost(20);
params.setMaxTotalConnections(20);
httpClient = new HttpClient(connectionManager);
binaryManager = createBinaryManager();
backend = createBackend();
createServer();
}
public HttpClient getHttpClient() {
return httpClient;
}
protected BinaryManager createBinaryManager() throws StorageException {
try {
Class<? extends BinaryManager> klass = repositoryDescriptor.binaryManagerClass;
if (klass == null) {
klass = DefaultBinaryManager.class;
}
BinaryManager binaryManager = klass.newInstance();
binaryManager.initialize(repositoryDescriptor);
if (repositoryDescriptor.binaryManagerConnect) {
List<ServerDescriptor> connect = repositoryDescriptor.connect;
if (connect.isEmpty() || connect.get(0).disabled) {
log.error("Repository descriptor specifies binaryManager connect "
+ "without a global connect");
} else {
binaryManager = new BinaryManagerClient(binaryManager,
httpClient);
binaryManager.initialize(repositoryDescriptor);
}
}
if (repositoryDescriptor.binaryManagerListen) {
activateBinaryManagerServlet(binaryManager);
}
return binaryManager;
} catch (Exception e) {
throw new StorageException(e);
}
}
protected RepositoryBackend createBackend() throws StorageException {
Class<? extends RepositoryBackend> backendClass = repositoryDescriptor.backendClass;
List<ServerDescriptor> connect = repositoryDescriptor.connect;
if (backendClass == null) {
if (!connect.isEmpty()) {
backendClass = NetBackend.class;
} else {
backendClass = JDBCBackend.class;
}
} else {
if (!connect.isEmpty()) {
log.error("Repository descriptor specifies both backendClass and connect,"
+ " only the backend will be used.");
}
}
try {
RepositoryBackend backend = backendClass.newInstance();
backend.initialize(this);
return backend;
} catch (StorageException e) {
throw e;
} catch (Exception e) {
throw new StorageException(e);
}
}
protected void createServer() {
ServerDescriptor serverDescriptor = repositoryDescriptor.listen;
if (serverDescriptor != null && !serverDescriptor.disabled) {
activateServletMapper();
}
}
protected void activateServletMapper() {
if (!serverStarted) {
MapperServlet servlet = new MapperServlet(repositoryDescriptor.name);
String servletName = MapperServlet.getName(repositoryDescriptor.name);
String url = NetServer.add(repositoryDescriptor.listen,
servletName, servlet, SERVER_PATH_VCS);
log.info(String.format(
"VCS server for repository '%s' started on: %s",
repositoryDescriptor.name, url));
serverStarted = true;
}
}
protected void deactivateServletMapper() {
if (serverStarted) {
String servletName = MapperServlet.getName(repositoryDescriptor.name);
NetServer.remove(repositoryDescriptor.listen, servletName);
serverStarted = false;
}
}
protected void activateBinaryManagerServlet(BinaryManager binaryManager) {
if (!binaryServerStarted) {
ServerDescriptor serverDescriptor = repositoryDescriptor.listen;
if (serverDescriptor == null || serverDescriptor.disabled) {
log.error("Repository descriptor specifies binaryManager listen "
+ "without a global listen");
} else {
BinaryManagerServlet servlet = new BinaryManagerServlet(
binaryManager);
String servletName = BinaryManagerServlet.getName(binaryManager);
String url = NetServer.add(serverDescriptor, servletName,
servlet, SERVER_PATH_BINARY);
log.info(String.format(
"VCS server for binary manager of repository '%s' started on: %s",
repositoryDescriptor.name, url));
binaryServerStarted = true;
}
}
}
protected void deactivateBinaryManagerServlet() {
if (binaryServerStarted) {
String servletName = BinaryManagerServlet.getName(binaryManager);
NetServer.remove(repositoryDescriptor.listen, servletName);
binaryServerStarted = false;
}
}
@Override
public boolean isServerActivated() {
return serverStarted;
}
@Override
public String getServerURL() {
String host = Framework.getProperty(RUNTIME_SERVER_HOST, "localhost");
return String.format("http://%s:%d/%s", host,
repositoryDescriptor.listen.port,
repositoryDescriptor.listen.path);
}
@Override
public void activateServer() {
activateServletMapper();
activateBinaryManagerServlet(binaryManager);
}
@Override
public void deactivateServer() {
deactivateServletMapper();
deactivateBinaryManagerServlet();
}
@Override
public Collection<MapperClientInfo> getClientInfos() {
if (!serverStarted) {
return Collections.emptyList();
}
MapperServlet servlet = (MapperServlet) NetServer.get(
repositoryDescriptor.listen,
MapperServlet.getName(repositoryDescriptor.name));
return servlet.getClientInfos();
}
public RepositoryDescriptor getRepositoryDescriptor() {
return repositoryDescriptor;
}
public BinaryManager getBinaryManager() {
return binaryManager;
}
public LockManager getLockManager() {
return lockManager;
}
/*
* ----- javax.resource.cci.ConnectionFactory -----
*/
/**
* Gets a new connection by logging in to the repository with default
* credentials.
*
* @return the session
* @throws StorageException
*/
@Override
public SessionImpl getConnection() throws StorageException {
return getConnection(null);
}
/**
* Gets a new connection by logging in to the repository with given
* connection information (credentials).
*
* @param connectionSpec the parameters to use to connnect
* @return the session
* @throws StorageException
*/
@Override
public synchronized SessionImpl getConnection(ConnectionSpec connectionSpec)
throws StorageException {
assert connectionSpec == null
|| connectionSpec instanceof ConnectionSpecImpl;
Credentials credentials = connectionSpec == null ? null
: ((ConnectionSpecImpl) connectionSpec).getCredentials();
boolean create = model == null;
if (create) {
log.debug("Initializing");
ModelSetup modelSetup = new ModelSetup();
modelSetup.repositoryDescriptor = repositoryDescriptor;
modelSetup.schemaManager = schemaManager;
backend.initializeModelSetup(modelSetup);
model = new Model(modelSetup);
backend.initializeModel(model);
Mapper lockManagerMapper = backend.newMapper(model, null,
credentials, true);
lockManager = new LockManager(lockManagerMapper,
repositoryDescriptor.clusteringEnabled);
}
SessionPathResolver pathResolver = new SessionPathResolver();
Mapper mapper = backend.newMapper(model, pathResolver, credentials,
false);
SessionImpl session = newSession(mapper, credentials);
pathResolver.setSession(session);
sessions.add(session);
return session;
}
protected SessionImpl newSession(Mapper mapper, Credentials credentials)
throws StorageException {
mapper = new CachingMapper(mapper, cachePropagator, eventPropagator,
repositoryEventQueue);
return new SessionImpl(this, model, mapper, credentials);
}
public static class SessionPathResolver implements PathResolver {
private Session session;
protected void setSession(Session session) {
this.session = session;
}
@Override
public Serializable getIdForPath(String path) throws StorageException {
Node node = session.getNodeByPath(path, null);
return node == null ? null : node.getId();
}
}
/*
* -----
*/
@Override
public ResourceAdapterMetaData getMetaData() {
throw new UnsupportedOperationException();
}
@Override
public RecordFactory getRecordFactory() {
throw new UnsupportedOperationException();
}
/*
* ----- javax.resource.Referenceable -----
*/
private Reference reference;
@Override
public void setReference(Reference reference) {
this.reference = reference;
}
@Override
public Reference getReference() {
return reference;
}
/*
* ----- Repository -----
*/
@Override
public synchronized void close() throws StorageException {
closeAllSessions();
model = null;
deactivateServletMapper();
deactivateBinaryManagerServlet();
backend.shutdown();
connectionManager.shutdown();
}
protected synchronized void closeAllSessions() throws StorageException {
for (SessionImpl session : sessions) {
if (!session.isLive()) {
continue;
}
session.closeSession();
}
sessions.clear();
if (lockManager != null) {
lockManager.shutdown();
}
}
/*
* ----- RepositoryManagement -----
*/
@Override
public String getName() {
return repositoryDescriptor.name;
}
@Override
public int getActiveSessionsCount() {
return sessions.size();
}
@Override
public int clearCaches() {
int n = 0;
for (SessionImpl session : sessions) {
n += session.clearCaches();
}
if (lockManager != null) {
lockManager.clearCaches();
}
return n;
}
@Override
public void processClusterInvalidationsNext() {
// TODO pass through or something
}
/*
* ----- -----
*/
// callback by session at close time
protected void closeSession(SessionImpl session) {
sessions.remove(session);
}
}