001/*
002 * Copyright (c) 2006-2014 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *     Bogdan Stefanescu
011 *     Florent Guillaume
012 */
013package org.nuxeo.ecm.core.api;
014
015import static org.nuxeo.ecm.core.api.security.SecurityConstants.SYSTEM_USERNAME;
016
017import java.io.Serializable;
018import java.security.Principal;
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.Map;
022import java.util.concurrent.ConcurrentHashMap;
023
024import org.nuxeo.ecm.core.api.impl.UserPrincipal;
025import org.nuxeo.ecm.core.api.local.ClientLoginModule;
026import org.nuxeo.ecm.core.api.local.LoginStack;
027import org.nuxeo.ecm.core.api.repository.RepositoryManager;
028import org.nuxeo.ecm.core.api.security.SecurityConstants;
029import org.nuxeo.runtime.api.Framework;
030import org.nuxeo.runtime.api.login.LoginComponent;
031
032import com.google.common.base.Predicate;
033import com.google.common.collect.Collections2;
034
035/**
036 * The CoreInstance is the main access point to a CoreSession.
037 */
038public class CoreInstance {
039
040    private static final CoreInstance INSTANCE = new CoreInstance();
041
042    public static class RegistrationInfo extends Throwable {
043        private static final long serialVersionUID = 1L;
044
045        public final CoreSession session;
046
047        public RegistrationInfo(CoreSession session) {
048            super("RegistrationInfo(" + Thread.currentThread().getName() + ", " + session.getSessionId() + ")");
049            this.session = session;
050        }
051
052    }
053
054    /**
055     * All open CoreSessionInfo, keyed by session id.
056     */
057    private final Map<String, RegistrationInfo> sessions = new ConcurrentHashMap<String, RegistrationInfo>();
058
059    private CoreInstance() {
060    }
061
062    /**
063     * Gets the CoreInstance singleton.
064     */
065    public static CoreInstance getInstance() {
066        return INSTANCE;
067    }
068
069    /**
070     * Opens a {@link CoreSession} for the currently logged-in user.
071     * <p>
072     * The session must be closed using {@link CoreSession#close}.
073     *
074     * @param repositoryName the repository name, or {@code null} for the default repository
075     * @return the session
076     * @since 5.9.3
077     */
078    public static CoreSession openCoreSession(String repositoryName) {
079        return openCoreSession(repositoryName, getPrincipal((String) null));
080    }
081
082    /**
083     * Opens a {@link CoreSession} for the given user.
084     * <p>
085     * The session must be closed using {@link CoreSession#close}.
086     *
087     * @param repositoryName the repository name, or {@code null} for the default repository
088     * @param username the user name
089     * @return the session
090     * @since 5.9.3
091     */
092    public static CoreSession openCoreSession(String repositoryName, String username) {
093        return openCoreSession(repositoryName, getPrincipal(username));
094    }
095
096    /**
097     * Opens a {@link CoreSession} for a system user.
098     * <p>
099     * The session must be closed using {@link CoreSession#close}.
100     *
101     * @param repositoryName the repository name, or {@code null} for the default repository
102     * @return the session
103     * @since 5.9.3
104     */
105    public static CoreSession openCoreSessionSystem(String repositoryName) {
106        return openCoreSession(repositoryName, getPrincipal((SecurityConstants.SYSTEM_USERNAME)));
107    }
108
109    /**
110     * @deprecated since 5.9.3, use {@link #openCoreSession} instead.
111     */
112    @Deprecated
113    public CoreSession open(String repositoryName, Map<String, Serializable> context) {
114        return openCoreSession(repositoryName, getPrincipal(context));
115    }
116
117    /**
118     * NOT PUBLIC, DO NOT CALL. Kept public for compatibility with old code.
119     * <p>
120     * Opens a {@link CoreSession} for the given context.
121     *
122     * @param repositoryName the repository name, or {@code null} for the default repository
123     * @param context the session open context
124     * @return the session
125     */
126    public static CoreSession openCoreSession(String repositoryName, Map<String, Serializable> context)
127            {
128        return openCoreSession(repositoryName, getPrincipal(context));
129    }
130
131    /**
132     * Opens a {@link CoreSession} for the given principal.
133     * <p>
134     * The session must be closed using {@link CoreSession#close}.
135     *
136     * @param repositoryName the repository name, or {@code null} for the default repository
137     * @param principal the principal
138     * @return the session
139     * @since 5.9.3
140     */
141    public static CoreSession openCoreSession(String repositoryName, Principal principal) {
142        if (principal instanceof NuxeoPrincipal) {
143            return openCoreSession(repositoryName, (NuxeoPrincipal) principal);
144        } else {
145            return openCoreSession(repositoryName, getPrincipal(principal.getName()));
146        }
147    }
148
149    /**
150     * Opens a {@link CoreSession} for the given principal.
151     * <p>
152     * The session must be closed using {@link CoreSession#close}.
153     *
154     * @param repositoryName the repository name, or {@code null} for the default repository
155     * @param principal the principal
156     * @return the session
157     * @since 5.9.3
158     */
159    public static CoreSession openCoreSession(String repositoryName, NuxeoPrincipal principal) {
160        if (repositoryName == null) {
161            RepositoryManager repositoryManager = Framework.getLocalService(RepositoryManager.class);
162            repositoryName = repositoryManager.getDefaultRepository().getName();
163        }
164        return getInstance().acquireCoreSession(repositoryName, principal);
165    }
166
167    protected CoreSession acquireCoreSession(String repositoryName, NuxeoPrincipal principal) {
168        CoreSession session = Framework.getLocalService(CoreSession.class);
169        session.connect(repositoryName, principal);
170        sessions.put(session.getSessionId(), new RegistrationInfo(session));
171        return session;
172    }
173
174    /**
175     * Gets an existing open session for the given session id.
176     * <p>
177     * The returned CoreSession must not be closed, as it is owned by someone else.
178     *
179     * @param sessionId the session id
180     * @return the session, which must not be closed
181     */
182    public CoreSession getSession(String sessionId) {
183        if (sessionId == null) {
184            throw new NullPointerException("null sessionId");
185        }
186        RegistrationInfo csi = sessions.get(sessionId);
187        return csi == null ? null : csi.session;
188    }
189
190    /**
191     * Use {@link CoreSession#close} instead.
192     *
193     * @since 5.9.3
194     */
195    public static void closeCoreSession(CoreSession session) {
196        getInstance().releaseCoreSession(session);
197    }
198
199    protected void releaseCoreSession(CoreSession session) {
200        String sessionId = session.getSessionId();
201        RegistrationInfo csi = sessions.remove(sessionId);
202        if (csi == null) {
203            throw new RuntimeException("Closing unknown CoreSession: " + sessionId);
204        }
205        session.destroy();
206    }
207
208    protected static NuxeoPrincipal getPrincipal(Map<String, Serializable> map) {
209        if (map == null) {
210            return getPrincipal((String) null); // logged-in principal
211        }
212        NuxeoPrincipal principal = (NuxeoPrincipal) map.get("principal");
213        if (principal == null) {
214            principal = getPrincipal((String) map.get("username"));
215        }
216        return principal;
217    }
218
219    protected static NuxeoPrincipal getPrincipal(String username) {
220        if (username != null) {
221            if (SYSTEM_USERNAME.equals(username)) {
222                return new SystemPrincipal(null);
223            } else {
224                return new UserPrincipal(username, new ArrayList<String>(), false, false);
225            }
226        } else {
227            LoginStack.Entry entry = ClientLoginModule.getCurrentLogin();
228            if (entry != null) {
229                Principal p = entry.getPrincipal();
230                if (p instanceof NuxeoPrincipal) {
231                    return (NuxeoPrincipal) p;
232                } else if (LoginComponent.isSystemLogin(p)) {
233                    return new SystemPrincipal(p.getName());
234                } else {
235                    throw new RuntimeException("Unsupported principal: " + p.getClass());
236                }
237            } else {
238                if (Framework.isTestModeSet()) {
239                    return new SystemPrincipal(null);
240                } else {
241                    throw new NuxeoException(
242                            "Cannot create a CoreSession outside a security context, " + " login() missing.");
243                }
244            }
245        }
246    }
247
248    /**
249     * @deprecated since 5.9.3, use {@link CoreSession#close} instead.
250     */
251    @Deprecated
252    public void close(CoreSession session) {
253        session.close(); // calls back closeCoreSession
254    }
255
256    /**
257     * Gets the number of open sessions.
258     *
259     * @since 5.4.2
260     */
261    public int getNumberOfSessions() {
262        return sessions.size();
263    }
264
265    public Collection<RegistrationInfo> getRegistrationInfos() {
266        return sessions.values();
267    }
268
269    public Collection<RegistrationInfo> getRegistrationInfosLive(final boolean onThread) {
270        return Collections2.filter(sessions.values(), new Predicate<RegistrationInfo>() {
271
272            @Override
273            public boolean apply(RegistrationInfo input) {
274                return input.session.isLive(onThread);
275            }
276
277        });
278    }
279
280    public void cleanupThisThread() {
281        NuxeoException errors = new NuxeoException("disconnecting from storage for you");
282        for (RegistrationInfo each : CoreInstance.getInstance().getRegistrationInfosLive(true)) {
283            each.session.destroy();
284            errors.addSuppressed(each);
285        }
286        if (errors.getSuppressed().length > 0) {
287            throw errors;
288        }
289    }
290}