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}