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