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