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