001/* 002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the Eclipse Public License v1.0 006 * which accompanies this distribution, and is available at 007 * http://www.eclipse.org/legal/epl-v10.html 008 * 009 * Contributors: 010 * Florent Guillaume 011 */ 012 013package org.nuxeo.ecm.core.storage.sql.ra; 014 015import java.io.PrintWriter; 016import java.util.Calendar; 017import java.util.HashMap; 018import java.util.Map; 019import java.util.Set; 020import java.util.regex.Matcher; 021import java.util.regex.Pattern; 022 023import javax.resource.ResourceException; 024import javax.resource.cci.ConnectionFactory; 025import javax.resource.spi.ConnectionManager; 026import javax.resource.spi.ConnectionRequestInfo; 027import javax.resource.spi.ManagedConnection; 028import javax.resource.spi.ManagedConnectionFactory; 029import javax.resource.spi.ResourceAdapter; 030import javax.resource.spi.ResourceAdapterAssociation; 031import javax.security.auth.Subject; 032 033import org.apache.commons.logging.Log; 034import org.apache.commons.logging.LogFactory; 035import org.nuxeo.ecm.core.storage.sql.RepositoryDescriptor; 036import org.nuxeo.ecm.core.storage.sql.RepositoryImpl; 037import org.nuxeo.ecm.core.storage.sql.RepositoryManagement; 038import org.nuxeo.ecm.core.storage.sql.SessionImpl; 039import org.nuxeo.ecm.core.storage.sql.coremodel.SQLRepositoryService; 040import org.nuxeo.runtime.api.Framework; 041 042/** 043 * The managed connection factory receives requests from the application server to create new {@link ManagedConnection} 044 * (the physical connection). 045 * <p> 046 * It also is a factory for {@link ConnectionFactory}s. 047 * 048 * @author Florent Guillaume 049 */ 050public class ManagedConnectionFactoryImpl implements ManagedConnectionFactory, ResourceAdapterAssociation, 051 RepositoryManagement { 052 053 private static final Log log = LogFactory.getLog(ManagedConnectionFactoryImpl.class); 054 055 private static final long serialVersionUID = 1L; 056 057 private final RepositoryDescriptor repositoryDescriptor; 058 059 private transient ResourceAdapter resourceAdapter; 060 061 private transient PrintWriter out; 062 063 /** 064 * The instantiated repository. 065 */ 066 private RepositoryImpl repository; 067 068 // For JavaEE, called by the ra.xml, then the ds.xml provides properties 069 // through the Java Bean convention 070 public ManagedConnectionFactoryImpl() { 071 this(new RepositoryDescriptor()); 072 } 073 074 public ManagedConnectionFactoryImpl(RepositoryDescriptor repositoryDescriptor) { 075 this.repositoryDescriptor = repositoryDescriptor; 076 if (repositoryDescriptor.properties == null) { 077 repositoryDescriptor.properties = new HashMap<String, String>(); 078 } 079 } 080 081 /* 082 * ----- Java Bean ----- 083 */ 084 085 public void setName(String name) { 086 repositoryDescriptor.name = name; 087 } 088 089 @Override 090 public String getName() { 091 return repositoryDescriptor.name; 092 } 093 094 public void setXaDataSource(String xaDataSourceName) { 095 repositoryDescriptor.xaDataSourceName = xaDataSourceName; 096 } 097 098 public String getXaDataSource() { 099 return repositoryDescriptor.xaDataSourceName; 100 } 101 102 /** 103 * Properties are specified in the format key=val1[;key2=val2;...] 104 * <p> 105 * If a value has to contain a semicolon, it can be escaped by doubling it. 106 * 107 * @see #parseProperties(String) 108 * @param property 109 */ 110 public void setProperty(String property) { 111 repositoryDescriptor.properties.putAll(parseProperties(property)); 112 } 113 114 public String getProperty() { 115 return null; 116 } 117 118 /* 119 * ----- javax.resource.spi.ResourceAdapterAssociation ----- 120 */ 121 122 /** 123 * Called by the application server exactly once to associate this ManagedConnectionFactory with a ResourceAdapter. 124 * The ResourceAdapter may then be used to look up configuration. 125 */ 126 @Override 127 public void setResourceAdapter(ResourceAdapter resourceAdapter) throws ResourceException { 128 this.resourceAdapter = resourceAdapter; 129 } 130 131 @Override 132 public ResourceAdapter getResourceAdapter() { 133 return resourceAdapter; 134 } 135 136 /* 137 * ----- javax.resource.spi.ManagedConnectionFactory ----- 138 */ 139 140 @Override 141 public void setLogWriter(PrintWriter out) { 142 this.out = out; 143 } 144 145 @Override 146 public PrintWriter getLogWriter() { 147 return out; 148 } 149 150 /* 151 * Used in non-managed scenarios. 152 */ 153 @Override 154 public Object createConnectionFactory() throws ResourceException { 155 return createConnectionFactory(new ConnectionManagerImpl()); 156 } 157 158 /* 159 * Used in managed scenarios. 160 */ 161 @Override 162 public Object createConnectionFactory(ConnectionManager connectionManager) throws ResourceException { 163 ConnectionFactoryImpl connectionFactory = new ConnectionFactoryImpl(this, connectionManager); 164 log.debug("Created repository factory (" + connectionFactory + ')'); 165 return connectionFactory; 166 } 167 168 /* 169 * Creates a new physical connection to the underlying storage. Called by the application server pool (or the 170 * non-managed ConnectionManagerImpl) when it needs a new connection. 171 */ 172 @Override 173 public ManagedConnection createManagedConnection(Subject subject, ConnectionRequestInfo connectionRequestInfo) 174 throws ResourceException { 175 // subject unused 176 // connectionRequestInfo unused 177 initialize(); 178 return new ManagedConnectionImpl(this); 179 } 180 181 /** 182 * Returns a matched connection from the candidate set of connections. 183 * <p> 184 * Called by the application server when it's looking for an appropriate connection to server from a pool. 185 */ 186 @Override 187 public ManagedConnection matchManagedConnections(Set set, Subject subject, ConnectionRequestInfo cri) 188 throws ResourceException { 189 for (Object candidate : set) { 190 if (!(candidate instanceof ManagedConnectionImpl)) { 191 continue; 192 } 193 ManagedConnectionImpl managedConnection = (ManagedConnectionImpl) candidate; 194 if (!equals(managedConnection.getManagedConnectionFactory())) { 195 continue; 196 } 197 log.debug("matched: " + managedConnection); 198 if (log.isTraceEnabled()) { 199 log.trace("debug stack trace", new Exception()); 200 } 201 return managedConnection; 202 } 203 return null; 204 } 205 206 /* 207 * ----- org.nuxeo.ecm.core.storage.sql.RepositoryManagement ----- 208 */ 209 210 @Override 211 public int getActiveSessionsCount() { 212 if (repository == null) { 213 return 0; 214 } 215 return repository.getActiveSessionsCount(); 216 } 217 218 @Override 219 public int clearCaches() { 220 if (repository == null) { 221 return 0; 222 } 223 return repository.clearCaches(); 224 } 225 226 @Override 227 public long getCacheSize() { 228 return repository.getCacheSize(); 229 } 230 231 @Override 232 public long getCachePristineSize() { 233 return repository.getCachePristineSize(); 234 } 235 236 @Override 237 public long getCacheSelectionSize() { 238 return repository.getCacheSelectionSize(); 239 } 240 241 @Override 242 public void processClusterInvalidationsNext() { 243 if (repository != null) { 244 repository.processClusterInvalidationsNext(); 245 } 246 } 247 248 @Override 249 public void markReferencedBinaries() { 250 if (repository != null) { 251 repository.markReferencedBinaries(); 252 } 253 } 254 255 @Override 256 public int cleanupDeletedDocuments(int max, Calendar beforeTime) { 257 if (repository == null) { 258 return 0; 259 } 260 return repository.cleanupDeletedDocuments(max, beforeTime); 261 } 262 263 /* 264 * ----- ----- 265 */ 266 267 private void initialize() throws ResourceException { 268 synchronized (this) { 269 if (repository == null) { 270 repositoryDescriptor.merge(getRepositoryDescriptor(repositoryDescriptor.name)); 271 repository = new RepositoryImpl(repositoryDescriptor); 272 } 273 SessionImpl session = repository.getConnection(); 274 session.close(); 275 } 276 } 277 278 public void shutdown() { 279 try { 280 repository.close(); 281 } finally { 282 repository = null; 283 } 284 } 285 286 /** 287 * Gets the repository descriptor provided by the repository extension point. It's where clustering, indexing, etc. 288 * are configured. 289 */ 290 protected static RepositoryDescriptor getRepositoryDescriptor(String name) { 291 SQLRepositoryService sqlRepositoryService = Framework.getLocalService(SQLRepositoryService.class); 292 return sqlRepositoryService.getRepositoryDescriptor(name); 293 } 294 295 /** 296 * Called by the {@link ManagedConnectionImpl} constructor to get a new physical connection. 297 */ 298 protected SessionImpl getConnection() { 299 return repository.getConnection(); 300 } 301 302 private static final Pattern KEYVALUE = Pattern.compile("([^=]*)=(.*)"); 303 304 /** 305 * Parses a string of the form: <code>key1=val1;key2=val2;...</code> and collects the key/value pairs. 306 * <p> 307 * A ';' character may end the expression. If a value has to contain a ';', it can be escaped by doubling it. 308 * <p> 309 * Examples of valid expressions: <code>key1=val1</code>, <code>key1=val1;</code>, <code>key1=val1;key2=val2</code>, 310 * <code>key1=a=b;;c=d;key2=val2</code>. 311 * <p> 312 * Syntax errors are reported using the logger and will stop the parsing but already collected properties will be 313 * available. The ';' or '=' characters cannot be escaped in keys. 314 * 315 * @param expr the expression to parse 316 * @return a key/value map 317 */ 318 public static Map<String, String> parseProperties(String expr) { 319 String SPECIAL = "\u1fff"; // never present in the strings to parse 320 Map<String, String> props = new HashMap<String, String>(); 321 for (String kv : expr.replace(";;", SPECIAL).split(";")) { 322 kv = kv.replace(SPECIAL, ";"); 323 if ("".equals(kv)) { 324 // empty starting string 325 continue; 326 } 327 Matcher m = KEYVALUE.matcher(kv); 328 if (m == null || !m.matches()) { 329 log.error("Invalid property expression: " + kv); 330 continue; 331 } 332 props.put(m.group(1), m.group(2)); 333 } 334 return props; 335 } 336 337}