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}