/* * 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.ra; import java.io.FileInputStream; import java.io.PrintWriter; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.resource.ResourceException; import javax.resource.cci.ConnectionFactory; import javax.resource.spi.ConnectionManager; import javax.resource.spi.ConnectionRequestInfo; import javax.resource.spi.ManagedConnection; import javax.resource.spi.ManagedConnectionFactory; import javax.resource.spi.ResourceAdapter; import javax.resource.spi.ResourceAdapterAssociation; import javax.security.auth.Subject; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.eclipse.ecr.core.NXCore; import org.eclipse.ecr.core.storage.StorageException; import org.eclipse.ecr.core.storage.sql.ConnectionSpecImpl; import org.eclipse.ecr.core.storage.sql.RepositoryDescriptor; import org.eclipse.ecr.core.storage.sql.RepositoryImpl; import org.eclipse.ecr.core.storage.sql.RepositoryManagement; import org.eclipse.ecr.core.storage.sql.SessionImpl; import org.eclipse.ecr.core.storage.sql.net.MapperClientInfo; import org.nuxeo.common.xmap.XMap; /** * The managed connection factory receives requests from the application server * to create new {@link ManagedConnection} (the physical connection). * <p> * It also is a factory for {@link ConnectionFactory}s. * * @author Florent Guillaume */ public class ManagedConnectionFactoryImpl implements ManagedConnectionFactory, ResourceAdapterAssociation, RepositoryManagement { private static final Log log = LogFactory.getLog(ManagedConnectionFactoryImpl.class); private static final long serialVersionUID = 1L; private final RepositoryDescriptor repositoryDescriptor; private transient ResourceAdapter resourceAdapter; private transient PrintWriter out; /** * The instantiated repository. */ private RepositoryImpl repository; public ManagedConnectionFactoryImpl() { this(new RepositoryDescriptor()); } public ManagedConnectionFactoryImpl( RepositoryDescriptor repositoryDescriptor) { this.repositoryDescriptor = repositoryDescriptor; if (repositoryDescriptor.properties == null) { repositoryDescriptor.properties = new HashMap<String, String>(); } } /* * ----- Java Bean ----- */ public void setName(String name) { repositoryDescriptor.name = name; } @Override public String getName() { return repositoryDescriptor.name; } public void setXaDataSource(String xaDataSourceName) { repositoryDescriptor.xaDataSourceName = xaDataSourceName; } public String getXaDataSource() { return repositoryDescriptor.xaDataSourceName; } /** * Properties are specified in the format key=val1[;key2=val2;...] * <p> * If a value has to contain a semicolon, it can be escaped by doubling it. * * @see #parseProperties(String) * @param property */ public void setProperty(String property) { repositoryDescriptor.properties.putAll(parseProperties(property)); } public String getProperty() { return null; } /* * ----- javax.resource.spi.ResourceAdapterAssociation ----- */ /** * Called by the application server exactly once to associate this * ManagedConnectionFactory with a ResourceAdapter. The ResourceAdapter may * then be used to look up configuration. */ @Override public void setResourceAdapter(ResourceAdapter resourceAdapter) throws ResourceException { this.resourceAdapter = resourceAdapter; } @Override public ResourceAdapter getResourceAdapter() { return resourceAdapter; } /* * ----- javax.resource.spi.ManagedConnectionFactory ----- */ @Override public void setLogWriter(PrintWriter out) { this.out = out; } @Override public PrintWriter getLogWriter() { return out; } /* * Used in non-managed scenarios. */ @Override public Object createConnectionFactory() throws ResourceException { return createConnectionFactory(new ConnectionManagerImpl()); } /* * Used in managed scenarios. */ @Override public Object createConnectionFactory(ConnectionManager connectionManager) throws ResourceException { ConnectionFactoryImpl connectionFactory = new ConnectionFactoryImpl( this, connectionManager); log.debug("Created repository factory (" + connectionFactory + ')'); return connectionFactory; } /* * Creates a new physical connection to the underlying storage. Called by * the application server pool (or the non-managed ConnectionManagerImpl) * when it needs a new connection. */ /* * If connectionRequestInfo is null then it means that the call is made by * the application server for the recovery case (6.5.3.5). */ @Override public ManagedConnection createManagedConnection(Subject subject, ConnectionRequestInfo connectionRequestInfo) throws ResourceException { assert connectionRequestInfo instanceof ConnectionRequestInfoImpl; initialize(); return new ManagedConnectionImpl(this, (ConnectionRequestInfoImpl) connectionRequestInfo); } /** * Returns a matched connection from the candidate set of connections. * <p> * Called by the application server when it's looking for an appropriate * connection to server from a pool. */ @Override public ManagedConnection matchManagedConnections(Set set, Subject subject, ConnectionRequestInfo cri) throws ResourceException { for (Object candidate : set) { if (!(candidate instanceof ManagedConnectionImpl)) { continue; } ManagedConnectionImpl managedConnection = (ManagedConnectionImpl) candidate; if (!this.equals(managedConnection.getManagedConnectionFactory())) { continue; } log.debug("matched: " + managedConnection); if (log.isTraceEnabled()) { log.trace("debug stack trace", new Exception()); } return managedConnection; } return null; } /* * ----- org.eclipse.ecr.core.storage.sql.RepositoryManagement ----- */ @Override public int getActiveSessionsCount() { if (repository == null) { return 0; } return repository.getActiveSessionsCount(); } @Override public int clearCaches() { if (repository == null) { return 0; } return repository.clearCaches(); } @Override public void processClusterInvalidationsNext() { if (repository != null) { repository.processClusterInvalidationsNext(); } } /* * ----- ----- */ private void initialize() throws StorageException { synchronized (this) { if (repository == null) { repositoryDescriptor.mergeFrom(getRepositoryDescriptor(repositoryDescriptor.name)); repository = new RepositoryImpl(repositoryDescriptor); } } } public void shutdown() { synchronized (this) { if (repository != null) { try { repository.close(); } catch (StorageException e) { log.error("Cannot close repository", e); } } } } /** * Gets the repository descriptor provided by the repository extension * point. It's where clustering, indexing, etc. are configured. */ protected static RepositoryDescriptor getRepositoryDescriptor(String name) throws StorageException { org.eclipse.ecr.core.repository.RepositoryDescriptor d = NXCore.getRepositoryService().getRepositoryManager().getDescriptor( name); try { XMap xmap = new XMap(); xmap.register(RepositoryDescriptor.class); return (RepositoryDescriptor) xmap.load(new FileInputStream( d.getConfigurationFile())); } catch (Exception e) { throw new StorageException(e); } } /** * Called by the {@link ManagedConnectionImpl} constructor to get a new * physical connection. */ protected SessionImpl getConnection(ConnectionSpecImpl connectionSpec) throws StorageException { return repository.getConnection(connectionSpec); } private static final Pattern KEYVALUE = Pattern.compile("([^=]*)=(.*)"); /** * Parses a string of the form: <code>key1=val1;key2=val2;...</code> and * collects the key/value pairs. * <p> * A ';' character may end the expression. If a value has to contain a ';', * it can be escaped by doubling it. * <p> * Examples of valid expressions: <code>key1=val1</code>, * <code>key1=val1;</code>, <code>key1=val1;key2=val2</code>, * <code>key1=a=b;;c=d;key2=val2</code>. * <p> * Syntax errors are reported using the logger and will stop the parsing but * already collected properties will be available. The ';' or '=' characters * cannot be escaped in keys. * * @param expr the expression to parse * @return a key/value map */ public static Map<String, String> parseProperties(String expr) { String SPECIAL = "\u1fff"; // never present in the strings to parse Map<String, String> props = new HashMap<String, String>(); for (String kv : expr.replace(";;", SPECIAL).split(";")) { kv = kv.replace(SPECIAL, ";"); if ("".equals(kv)) { // empty starting string continue; } Matcher m = KEYVALUE.matcher(kv); if (m == null || !m.matches()) { log.error("Invalid property expression: " + kv); continue; } props.put(m.group(1), m.group(2)); } return props; } @Override public void activateServer() { repository.activateServer(); } @Override public void deactivateServer() { repository.deactivateServer(); } @Override public Collection<MapperClientInfo> getClientInfos() { return repository.getClientInfos(); } @Override public boolean isServerActivated() { return repository.isServerActivated(); } @Override public String getServerURL() { return repository.getServerURL(); } }