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