/*
* (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed 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.
*
* Contributors:
* Florent Guillaume
*/
package org.nuxeo.ecm.core.storage.sql.ra;
import java.io.PrintWriter;
import java.util.Calendar;
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.nuxeo.ecm.core.storage.sql.RepositoryDescriptor;
import org.nuxeo.ecm.core.storage.sql.RepositoryImpl;
import org.nuxeo.ecm.core.storage.sql.RepositoryManagement;
import org.nuxeo.ecm.core.storage.sql.SessionImpl;
import org.nuxeo.ecm.core.storage.sql.coremodel.SQLRepositoryService;
import org.nuxeo.runtime.api.Framework;
/**
* 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;
// For JavaEE, called by the ra.xml, then the ds.xml provides properties
// through the Java Bean convention
public ManagedConnectionFactoryImpl() {
this(new RepositoryDescriptor());
}
public ManagedConnectionFactoryImpl(RepositoryDescriptor repositoryDescriptor) {
this.repositoryDescriptor = repositoryDescriptor;
}
/*
* ----- Java Bean -----
*/
public void setName(String name) {
repositoryDescriptor.name = name;
}
@Override
public String getName() {
return repositoryDescriptor.name;
}
/*
* ----- 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.
*/
@Override
public ManagedConnection createManagedConnection(Subject subject, ConnectionRequestInfo connectionRequestInfo)
throws ResourceException {
// subject unused
// connectionRequestInfo unused
initialize();
return new ManagedConnectionImpl(this);
}
/**
* 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 (!equals(managedConnection.getManagedConnectionFactory())) {
continue;
}
log.debug("matched: " + managedConnection);
return managedConnection;
}
return null;
}
/*
* ----- org.nuxeo.ecm.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 long getCacheSize() {
return repository.getCacheSize();
}
@Override
public long getCachePristineSize() {
return repository.getCachePristineSize();
}
@Override
public long getCacheSelectionSize() {
return repository.getCacheSelectionSize();
}
@Override
public void processClusterInvalidationsNext() {
if (repository != null) {
repository.processClusterInvalidationsNext();
}
}
@Override
public void markReferencedBinaries() {
if (repository != null) {
repository.markReferencedBinaries();
}
}
@Override
public int cleanupDeletedDocuments(int max, Calendar beforeTime) {
if (repository == null) {
return 0;
}
return repository.cleanupDeletedDocuments(max, beforeTime);
}
/*
* ----- -----
*/
private void initialize() throws ResourceException {
synchronized (this) {
if (repository == null) {
repositoryDescriptor.merge(getRepositoryDescriptor(repositoryDescriptor.name));
repository = new RepositoryImpl(repositoryDescriptor);
}
SessionImpl session = repository.getConnection();
session.close();
}
}
public void shutdown() {
try {
repository.close();
} finally {
repository = null;
}
}
/**
* Gets the repository descriptor provided by the repository extension point. It's where clustering, indexing, etc.
* are configured.
*/
protected static RepositoryDescriptor getRepositoryDescriptor(String name) {
SQLRepositoryService sqlRepositoryService = Framework.getLocalService(SQLRepositoryService.class);
return sqlRepositoryService.getRepositoryDescriptor(name);
}
/**
* Called by the {@link ManagedConnectionImpl} constructor to get a new physical connection.
*/
protected SessionImpl getConnection() {
return repository.getConnection();
}
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;
}
}