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}