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