001/*
002 * Licensed to DuraSpace under one or more contributor license agreements.
003 * See the NOTICE file distributed with this work for additional information
004 * regarding copyright ownership.
005 *
006 * DuraSpace licenses this file to you under the Apache License,
007 * Version 2.0 (the "License"); you may not use this file except in
008 * compliance with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.fcrepo.http.commons.session;
019
020import static java.util.Objects.requireNonNull;
021import static org.slf4j.LoggerFactory.getLogger;
022
023import java.security.Principal;
024
025import javax.annotation.PostConstruct;
026import javax.inject.Inject;
027import javax.servlet.http.HttpServletRequest;
028
029import org.fcrepo.kernel.api.FedoraRepository;
030import org.fcrepo.kernel.api.FedoraSession;
031import org.fcrepo.kernel.api.exception.SessionMissingException;
032import org.fcrepo.kernel.api.services.CredentialsService;
033import org.fcrepo.kernel.api.services.BatchService;
034
035import org.slf4j.Logger;
036
037/**
038 * Factory for generating sessions for HTTP requests, taking
039 * into account transactions and authentication.
040 *
041 * @author awoods
042 * @author gregjan
043 * @author kaisternad
044 */
045public class SessionFactory {
046
047    protected enum Prefix{
048        TX("tx:");
049
050        private final String prefix;
051
052        Prefix(final String prefix) {
053            this.prefix = prefix;
054        }
055
056        public String getPrefix() {
057            return prefix;
058        }
059    }
060
061    private static final Logger LOGGER = getLogger(SessionFactory.class);
062
063    @Inject
064    private FedoraRepository repo;
065
066    @Inject
067    private BatchService batchService;
068
069    @Inject
070    private CredentialsService credentialsService;
071
072    /**
073     * Initialize a session factory for the given Repository
074     *
075     * @param repo the repository
076     * @param batchService the transaction service
077     */
078    public SessionFactory(final FedoraRepository repo, final BatchService batchService) {
079        this.repo = repo;
080        this.batchService = batchService;
081    }
082
083    /**
084     * Validate the spring wiring
085     */
086    @PostConstruct
087    public void init() {
088        requireNonNull(repo, "SessionFactory requires a Repository instance!");
089    }
090
091    /**
092     * Get a new JCR Session
093     *
094     * @return an internal session
095     */
096    public FedoraSession getInternalSession() {
097        return repo.login();
098    }
099
100    /**
101     * Get a JCR session for the given HTTP servlet request with a
102     * SecurityContext attached
103     *
104     * @param servletRequest the servlet request
105     * @return the Session
106     * @throws RuntimeException if the transaction could not be found
107     */
108    public HttpSession getSession(final HttpServletRequest servletRequest) {
109        final HttpSession session;
110        final String txId = getEmbeddedId(servletRequest, Prefix.TX);
111
112        try {
113            if (txId == null) {
114                session = createSession(servletRequest);
115            } else {
116                session = getSessionFromTransaction(servletRequest, txId);
117            }
118        } catch (final SessionMissingException e) {
119            LOGGER.warn("Transaction missing: {}", e.getMessage());
120            return null;
121        }
122
123        return session;
124    }
125
126    /**
127     * Create a JCR session for the given HTTP servlet request with a
128     * SecurityContext attached.
129     *
130     * @param servletRequest the servlet request
131     * @return a newly created JCR session
132     */
133    protected HttpSession createSession(final HttpServletRequest servletRequest) {
134
135        LOGGER.debug("Returning an authenticated session in the default workspace");
136        return  new HttpSession(repo.login(credentialsService.getCredentials(servletRequest)));
137    }
138
139    /**
140     * Retrieve a JCR session from an active transaction
141     *
142     * @param servletRequest the servlet request
143     * @param txId the transaction id
144     * @return a JCR session that is associated with the transaction
145     */
146    protected HttpSession getSessionFromTransaction(final HttpServletRequest servletRequest, final String txId) {
147
148        final Principal userPrincipal = servletRequest.getUserPrincipal();
149        final String userName = userPrincipal == null ? null : userPrincipal.getName();
150
151        final FedoraSession session = batchService.getSession(txId, userName);
152        final HttpSession batchSession = new HttpSession(session);
153        batchSession.makeBatchSession();
154        LOGGER.debug("Returning a session in the batch {} for user {}", batchSession, userName);
155        return batchSession;
156    }
157
158    /**
159     * Extract the id embedded at the beginning of a request path
160     *
161     * @param servletRequest the servlet request
162     * @param prefix the prefix for the id
163     * @return the found id or null
164     */
165    protected String getEmbeddedId(
166            final HttpServletRequest servletRequest, final Prefix prefix) {
167        String requestPath = servletRequest.getPathInfo();
168
169        // http://stackoverflow.com/questions/18963562/grizzlys-request-getpathinfo-returns-always-null
170        if (requestPath == null && servletRequest.getContextPath().isEmpty()) {
171            requestPath = servletRequest.getRequestURI();
172        }
173
174        String id = null;
175        if (requestPath != null) {
176            final String pathPrefix = prefix.getPrefix();
177            final String[] part = requestPath.split("/");
178            if (part.length > 1 && part[1].startsWith(pathPrefix)) {
179                id = part[1].substring(pathPrefix.length());
180            }
181        }
182        return id;
183    }
184
185}