001/*
002 * Copyright 2015 DuraSpace, Inc.
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 */
016package org.fcrepo.http.commons.session;
017
018import static java.util.Objects.requireNonNull;
019import static org.slf4j.LoggerFactory.getLogger;
020
021import java.security.Principal;
022
023import javax.annotation.PostConstruct;
024import javax.inject.Inject;
025import javax.jcr.Repository;
026import javax.jcr.RepositoryException;
027import javax.jcr.Session;
028import javax.servlet.http.HttpServletRequest;
029import javax.ws.rs.BadRequestException;
030
031import org.fcrepo.kernel.api.exception.RepositoryRuntimeException;
032import org.fcrepo.kernel.api.exception.SessionMissingException;
033import org.fcrepo.kernel.api.Transaction;
034import org.fcrepo.kernel.api.services.TransactionService;
035
036import org.modeshape.jcr.api.ServletCredentials;
037import org.slf4j.Logger;
038
039/**
040 * Factory for generating sessions for HTTP requests, taking
041 * into account transactions and authentication.
042 *
043 * @author awoods
044 * @author gregjan
045 * @author kaisternad
046 */
047public class SessionFactory {
048
049    protected static enum Prefix{
050        TX("tx:");
051
052        private final String prefix;
053
054        Prefix(final String prefix) {
055            this.prefix = prefix;
056        }
057
058        public String getPrefix() {
059            return prefix;
060        }
061    }
062
063    private static final Logger LOGGER = getLogger(SessionFactory.class);
064
065    @Inject
066    private Repository repo;
067
068    @Inject
069    private TransactionService transactionService;
070
071    /**
072     * Default constructor
073     */
074    public SessionFactory() {
075    }
076
077    /**
078     * Initialize a session factory for the given Repository
079     *
080     * @param repo the repository
081     * @param transactionService the transaction service
082     */
083    public SessionFactory(final Repository repo,
084            final TransactionService transactionService) {
085        this.repo = repo;
086        this.transactionService = transactionService;
087    }
088
089    /**
090     * Validate the spring wiring
091     */
092    @PostConstruct
093    public void init() {
094        requireNonNull(repo, "SessionFactory requires a Repository instance!");
095    }
096
097    /**
098     * Get a new JCR Session
099     *
100     * @return an internal session
101     */
102    public Session getInternalSession() {
103        try {
104            return repo.login();
105        } catch (final RepositoryException e) {
106            throw new RepositoryRuntimeException(e);
107        }
108    }
109
110    /**
111     * Get a JCR session for the given HTTP servlet request with a
112     * SecurityContext attached
113     *
114     * @param servletRequest the servlet request
115     * @return the Session
116     * @throws RuntimeException if the transaction could not be found
117     */
118    public Session getSession(final HttpServletRequest servletRequest) {
119        final Session session;
120        final String txId = getEmbeddedId(servletRequest, Prefix.TX);
121
122        try {
123            if (txId == null) {
124                session = createSession(servletRequest);
125            } else {
126                session = getSessionFromTransaction(servletRequest, txId);
127            }
128        } catch (final SessionMissingException e) {
129            LOGGER.warn("Transaction missing: {}", e.getMessage());
130            return null;
131        } catch (final RepositoryException e) {
132            throw new BadRequestException(e);
133        }
134
135        return session;
136    }
137
138    /**
139     * Create a JCR session for the given HTTP servlet request with a
140     * SecurityContext attached.
141     *
142     * @param servletRequest the servlet request
143     * @return a newly created JCR session
144     * @throws RepositoryException if the session could not be created
145     */
146    protected Session createSession(final HttpServletRequest servletRequest) throws RepositoryException {
147
148        final ServletCredentials creds =
149                new ServletCredentials(servletRequest);
150
151        LOGGER.debug("Returning an authenticated session in the default workspace");
152        return  repo.login(creds);
153    }
154
155    /**
156     * Retrieve a JCR session from an active transaction
157     *
158     * @param servletRequest the servlet request
159     * @param txId the transaction id
160     * @return a JCR session that is associated with the transaction
161     */
162    protected Session getSessionFromTransaction(final HttpServletRequest servletRequest, final String txId) {
163
164        final Principal userPrincipal = servletRequest.getUserPrincipal();
165
166        String userName = null;
167        if (userPrincipal != null) {
168            userName = userPrincipal.getName();
169        }
170
171        final Transaction transaction =
172                transactionService.getTransaction(txId, userName);
173        LOGGER.debug(
174                "Returning a session in the transaction {} for user {}",
175                transaction, userName);
176        return transaction.getSession();
177
178    }
179
180    /**
181     * Extract the id embedded at the beginning of a request path
182     *
183     * @param servletRequest the servlet request
184     * @param prefix the prefix for the id
185     * @return the found id or null
186     */
187    protected String getEmbeddedId(
188            final HttpServletRequest servletRequest, final Prefix prefix) {
189        String requestPath = servletRequest.getPathInfo();
190
191        // http://stackoverflow.com/questions/18963562/grizzlys-request-getpathinfo-returns-always-null
192        if (requestPath == null && servletRequest.getContextPath().isEmpty()) {
193            requestPath = servletRequest.getRequestURI();
194        }
195
196        String id = null;
197        if (requestPath != null) {
198            final String pathPrefix = prefix.getPrefix();
199            final String[] part = requestPath.split("/");
200            if (part.length > 1 && part[1].startsWith(pathPrefix)) {
201                id = part[1].substring(pathPrefix.length());
202            }
203        }
204        return id;
205    }
206
207}