/* * Copyright (c) 2014 Nuxeo SA (http://nuxeo.com/) and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Florent Guillaume */ package org.nuxeo.ecm.core.storage.dbs; import static java.lang.Boolean.FALSE; import java.io.IOException; import java.io.Serializable; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.naming.NamingException; import javax.transaction.RollbackException; import javax.transaction.Status; import javax.transaction.Synchronization; import javax.transaction.SystemException; import javax.transaction.Transaction; import org.nuxeo.ecm.core.api.DocumentException; import org.nuxeo.ecm.core.api.security.ACE; import org.nuxeo.ecm.core.api.security.SecurityConstants; import org.nuxeo.ecm.core.api.security.impl.ACLImpl; import org.nuxeo.ecm.core.api.security.impl.ACPImpl; import org.nuxeo.ecm.core.model.Document; import org.nuxeo.ecm.core.model.Session; import org.nuxeo.ecm.core.storage.binary.BinaryManager; import org.nuxeo.ecm.core.storage.binary.BinaryManagerDescriptor; import org.nuxeo.ecm.core.storage.binary.BinaryManagerService; import org.nuxeo.ecm.core.storage.binary.DefaultBinaryManager; import org.nuxeo.runtime.api.Framework; import org.nuxeo.runtime.transaction.TransactionHelper; /** * Provides sharing behavior for repository sessions and other basic functions. * * @since 5.9.4 */ public abstract class DBSRepositoryBase implements DBSRepository { public static final String TYPE_ROOT = "Root"; // change to have deterministic pseudo-UUID generation for debugging protected final boolean DEBUG_UUIDS = false; private static final String UUID_ZERO = "00000000-0000-0000-0000-000000000000"; private static final String UUID_ZERO_DEBUG = "UUID_0"; protected final String repositoryName; protected final BinaryManager binaryManager; public DBSRepositoryBase(String repositoryName) { this.repositoryName = repositoryName; binaryManager = newBinaryManager(); } @Override public void shutdown() { binaryManager.close(); } @Override public String getName() { return repositoryName; } /** * Initializes the root and its ACP. */ public void initRoot() { try { Session session = getSession(null); Document root = session.importDocument(getRootId(), null, "", TYPE_ROOT, new HashMap<String, Serializable>()); ACLImpl acl = new ACLImpl(); acl.add(new ACE(SecurityConstants.ADMINISTRATORS, SecurityConstants.EVERYTHING, true)); acl.add(new ACE(SecurityConstants.ADMINISTRATOR, SecurityConstants.EVERYTHING, true)); acl.add(new ACE(SecurityConstants.MEMBERS, SecurityConstants.READ, true)); ACPImpl acp = new ACPImpl(); acp.addACL(acl); session.setACP(root, acp, true); session.save(); session.close(); if (TransactionHelper.isTransactionActive()) { TransactionHelper.commitOrRollbackTransaction(); TransactionHelper.startTransaction(); } } catch (DocumentException e) { throw new RuntimeException(e); } } @Override public String getRootId() { return DEBUG_UUIDS ? UUID_ZERO_DEBUG : UUID_ZERO; } @Override public BinaryManager getBinaryManager() { return binaryManager; } public BinaryManager newBinaryManager() { BinaryManager binaryManager = new DefaultBinaryManager(); BinaryManagerDescriptor binaryManagerDescriptor = new BinaryManagerDescriptor(); try { binaryManagerDescriptor.repositoryName = repositoryName; binaryManagerDescriptor.storePath = null; // default binaryManager.initialize(binaryManagerDescriptor); BinaryManagerService bms = Framework.getLocalService(BinaryManagerService.class); bms.addBinaryManager(binaryManagerDescriptor.repositoryName, binaryManager); } catch (IOException e) { throw new RuntimeException(e); } return binaryManager; } @Override public int getActiveSessionsCount() { return 0; } @Override public Session getSession(String sessionId) throws DocumentException { Transaction transaction; try { transaction = TransactionHelper.lookupTransactionManager().getTransaction(); if (transaction != null && transaction.getStatus() != Status.STATUS_ACTIVE) { transaction = null; } } catch (SystemException | NamingException e) { transaction = null; } if (transaction == null) { // no active transaction, use a regular session return newSession(sessionId); } TransactionContext context = transactionContexts.get(transaction); if (context == null) { context = new TransactionContext(transaction, newSession(sessionId)); context.init(); } return context.newSession(sessionId); } protected DBSSession newSession(String sessionId) { return new DBSSession(this, sessionId); } public Map<Transaction, TransactionContext> transactionContexts = new ConcurrentHashMap<>(); public class TransactionContext implements Synchronization { protected final Transaction transaction; protected final DBSSession baseSession; protected final Set<Session> proxies; public TransactionContext(Transaction transaction, DBSSession baseSession) { this.transaction = transaction; this.baseSession = baseSession; proxies = new HashSet<>(); } public void init() { transactionContexts.put(transaction, this); // make sure it's closed (with handles) at transaction end try { transaction.registerSynchronization(this); } catch (RollbackException | SystemException e) { throw new RuntimeException(e); } } public Session newSession(String sessionId) { ClassLoader cl = getClass().getClassLoader(); DBSSessionInvoker invoker = new DBSSessionInvoker(this, sessionId); Session proxy = (Session) Proxy.newProxyInstance(cl, new Class[] { Session.class }, invoker); add(proxy); return proxy; } public void add(Session proxy) { proxies.add(proxy); } public boolean remove(Object proxy) { return proxies.remove(proxy); } @Override public void beforeCompletion() { try { baseSession.commit(); } catch (DocumentException e) { throw new RuntimeException(e); } } @Override public void afterCompletion(int status) { baseSession.close(); for (Session proxy : proxies.toArray(new Session[0])) { proxy.close(); } transactionContexts.remove(transaction); } } /** * An indirection to a {@link DBSSession} that has a different sessionId. */ public static class DBSSessionInvoker implements InvocationHandler { private static final String METHOD_HASHCODE = "hashCode"; private static final String METHOD_EQUALS = "equals"; private static final String METHOD_GETSESSIONID = "getSessionId"; private static final String METHOD_CLOSE = "close"; private static final String METHOD_ISLIVE = "isLive"; protected final TransactionContext context; protected final String sessionId; protected boolean closed; public DBSSessionInvoker(TransactionContext context, String sessionId) { this.context = context; this.sessionId = sessionId; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); if (methodName.equals(METHOD_HASHCODE)) { return doHashCode(); } if (methodName.equals(METHOD_EQUALS)) { return doEquals(args); } if (methodName.equals(METHOD_GETSESSIONID)) { return doGetSessionId(); } if (methodName.equals(METHOD_CLOSE)) { return doClose(proxy); } if (methodName.equals(METHOD_ISLIVE)) { return doIsLive(); } if (closed) { throw new DocumentException( "Cannot use closed connection handle: " + sessionId); } try { return method.invoke(context.baseSession, args); } catch (Throwable t) { if (t instanceof InvocationTargetException) { Throwable te = ((InvocationTargetException) t).getTargetException(); if (te != null) { t = te; } } if (t instanceof InterruptedException) { // restore interrupted state Thread.currentThread().interrupt(); } throw t; } } protected Integer doHashCode() { return Integer.valueOf(this.hashCode()); } protected Boolean doEquals(Object[] args) { if (args.length != 1 || args[0] == null) { return FALSE; } Object other = args[0]; if (!(Proxy.isProxyClass(other.getClass()))) { return FALSE; } InvocationHandler otherInvoker = Proxy.getInvocationHandler(other); return Boolean.valueOf(this.equals(otherInvoker)); } protected String doGetSessionId() { return sessionId; } protected Object doClose(Object proxy) { closed = true; context.remove(proxy); return null; } protected Boolean doIsLive() { if (closed) { return FALSE; } else { return Boolean.valueOf(context.baseSession.isLive()); } } } }