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 } 084 085 /* 086 * ----- Java Bean ----- 087 */ 088 089 public void setName(String name) { 090 repositoryDescriptor.name = name; 091 } 092 093 @Override 094 public String getName() { 095 return repositoryDescriptor.name; 096 } 097 098 /* 099 * ----- javax.resource.spi.ResourceAdapterAssociation ----- 100 */ 101 102 /** 103 * Called by the application server exactly once to associate this ManagedConnectionFactory with a ResourceAdapter. 104 * The ResourceAdapter may then be used to look up configuration. 105 */ 106 @Override 107 public void setResourceAdapter(ResourceAdapter resourceAdapter) throws ResourceException { 108 this.resourceAdapter = resourceAdapter; 109 } 110 111 @Override 112 public ResourceAdapter getResourceAdapter() { 113 return resourceAdapter; 114 } 115 116 /* 117 * ----- javax.resource.spi.ManagedConnectionFactory ----- 118 */ 119 120 @Override 121 public void setLogWriter(PrintWriter out) { 122 this.out = out; 123 } 124 125 @Override 126 public PrintWriter getLogWriter() { 127 return out; 128 } 129 130 /* 131 * Used in non-managed scenarios. 132 */ 133 @Override 134 public Object createConnectionFactory() throws ResourceException { 135 return createConnectionFactory(new ConnectionManagerImpl()); 136 } 137 138 /* 139 * Used in managed scenarios. 140 */ 141 @Override 142 public Object createConnectionFactory(ConnectionManager connectionManager) throws ResourceException { 143 ConnectionFactoryImpl connectionFactory = new ConnectionFactoryImpl(this, connectionManager); 144 log.debug("Created repository factory (" + connectionFactory + ')'); 145 return connectionFactory; 146 } 147 148 /* 149 * Creates a new physical connection to the underlying storage. Called by the application server pool (or the 150 * non-managed ConnectionManagerImpl) when it needs a new connection. 151 */ 152 @Override 153 public ManagedConnection createManagedConnection(Subject subject, ConnectionRequestInfo connectionRequestInfo) 154 throws ResourceException { 155 // subject unused 156 // connectionRequestInfo unused 157 initialize(); 158 return new ManagedConnectionImpl(this); 159 } 160 161 /** 162 * Returns a matched connection from the candidate set of connections. 163 * <p> 164 * Called by the application server when it's looking for an appropriate connection to server from a pool. 165 */ 166 @Override 167 public ManagedConnection matchManagedConnections(Set set, Subject subject, ConnectionRequestInfo cri) 168 throws ResourceException { 169 for (Object candidate : set) { 170 if (!(candidate instanceof ManagedConnectionImpl)) { 171 continue; 172 } 173 ManagedConnectionImpl managedConnection = (ManagedConnectionImpl) candidate; 174 if (!equals(managedConnection.getManagedConnectionFactory())) { 175 continue; 176 } 177 log.debug("matched: " + managedConnection); 178 return managedConnection; 179 } 180 return null; 181 } 182 183 /* 184 * ----- org.nuxeo.ecm.core.storage.sql.RepositoryManagement ----- 185 */ 186 187 @Override 188 public int getActiveSessionsCount() { 189 if (repository == null) { 190 return 0; 191 } 192 return repository.getActiveSessionsCount(); 193 } 194 195 @Override 196 public int clearCaches() { 197 if (repository == null) { 198 return 0; 199 } 200 return repository.clearCaches(); 201 } 202 203 @Override 204 public long getCacheSize() { 205 return repository.getCacheSize(); 206 } 207 208 @Override 209 public long getCachePristineSize() { 210 return repository.getCachePristineSize(); 211 } 212 213 @Override 214 public long getCacheSelectionSize() { 215 return repository.getCacheSelectionSize(); 216 } 217 218 @Override 219 public void processClusterInvalidationsNext() { 220 if (repository != null) { 221 repository.processClusterInvalidationsNext(); 222 } 223 } 224 225 @Override 226 public void markReferencedBinaries() { 227 if (repository != null) { 228 repository.markReferencedBinaries(); 229 } 230 } 231 232 @Override 233 public int cleanupDeletedDocuments(int max, Calendar beforeTime) { 234 if (repository == null) { 235 return 0; 236 } 237 return repository.cleanupDeletedDocuments(max, beforeTime); 238 } 239 240 /* 241 * ----- ----- 242 */ 243 244 private void initialize() throws ResourceException { 245 synchronized (this) { 246 if (repository == null) { 247 repositoryDescriptor.merge(getRepositoryDescriptor(repositoryDescriptor.name)); 248 repository = new RepositoryImpl(repositoryDescriptor); 249 } 250 SessionImpl session = repository.getConnection(); 251 session.close(); 252 } 253 } 254 255 public void shutdown() { 256 try { 257 repository.close(); 258 } finally { 259 repository = null; 260 } 261 } 262 263 /** 264 * Gets the repository descriptor provided by the repository extension point. It's where clustering, indexing, etc. 265 * are configured. 266 */ 267 protected static RepositoryDescriptor getRepositoryDescriptor(String name) { 268 SQLRepositoryService sqlRepositoryService = Framework.getLocalService(SQLRepositoryService.class); 269 return sqlRepositoryService.getRepositoryDescriptor(name); 270 } 271 272 /** 273 * Called by the {@link ManagedConnectionImpl} constructor to get a new physical connection. 274 */ 275 protected SessionImpl getConnection() { 276 return repository.getConnection(); 277 } 278 279 private static final Pattern KEYVALUE = Pattern.compile("([^=]*)=(.*)"); 280 281 /** 282 * Parses a string of the form: <code>key1=val1;key2=val2;...</code> and collects the key/value pairs. 283 * <p> 284 * A ';' character may end the expression. If a value has to contain a ';', it can be escaped by doubling it. 285 * <p> 286 * Examples of valid expressions: <code>key1=val1</code>, <code>key1=val1;</code>, <code>key1=val1;key2=val2</code>, 287 * <code>key1=a=b;;c=d;key2=val2</code>. 288 * <p> 289 * Syntax errors are reported using the logger and will stop the parsing but already collected properties will be 290 * available. The ';' or '=' characters cannot be escaped in keys. 291 * 292 * @param expr the expression to parse 293 * @return a key/value map 294 */ 295 public static Map<String, String> parseProperties(String expr) { 296 String SPECIAL = "\u1fff"; // never present in the strings to parse 297 Map<String, String> props = new HashMap<String, String>(); 298 for (String kv : expr.replace(";;", SPECIAL).split(";")) { 299 kv = kv.replace(SPECIAL, ";"); 300 if ("".equals(kv)) { 301 // empty starting string 302 continue; 303 } 304 Matcher m = KEYVALUE.matcher(kv); 305 if (m == null || !m.matches()) { 306 log.error("Invalid property expression: " + kv); 307 continue; 308 } 309 props.put(m.group(1), m.group(2)); 310 } 311 return props; 312 } 313 314}