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}