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