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 static 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     * Default constructor
074     */
075    public SessionFactory() {
076    }
077
078    /**
079     * Initialize a session factory for the given Repository
080     *
081     * @param repo the repository
082     * @param batchService the transaction service
083     */
084    public SessionFactory(final FedoraRepository repo, final BatchService batchService) {
085        this.repo = repo;
086        this.batchService = batchService;
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 FedoraSession getInternalSession() {
103        return repo.login();
104    }
105
106    /**
107     * Get a JCR session for the given HTTP servlet request with a
108     * SecurityContext attached
109     *
110     * @param servletRequest the servlet request
111     * @return the Session
112     * @throws RuntimeException if the transaction could not be found
113     */
114    public HttpSession getSession(final HttpServletRequest servletRequest) {
115        final HttpSession session;
116        final String txId = getEmbeddedId(servletRequest, Prefix.TX);
117
118        try {
119            if (txId == null) {
120                session = createSession(servletRequest);
121            } else {
122                session = getSessionFromTransaction(servletRequest, txId);
123            }
124        } catch (final SessionMissingException e) {
125            LOGGER.warn("Transaction missing: {}", e.getMessage());
126            return null;
127        }
128
129        return session;
130    }
131
132    /**
133     * Create a JCR session for the given HTTP servlet request with a
134     * SecurityContext attached.
135     *
136     * @param servletRequest the servlet request
137     * @return a newly created JCR session
138     */
139    protected HttpSession createSession(final HttpServletRequest servletRequest) {
140
141        LOGGER.debug("Returning an authenticated session in the default workspace");
142        return  new HttpSession(repo.login(credentialsService.getCredentials(servletRequest)));
143    }
144
145    /**
146     * Retrieve a JCR session from an active transaction
147     *
148     * @param servletRequest the servlet request
149     * @param txId the transaction id
150     * @return a JCR session that is associated with the transaction
151     */
152    protected HttpSession getSessionFromTransaction(final HttpServletRequest servletRequest, final String txId) {
153
154        final Principal userPrincipal = servletRequest.getUserPrincipal();
155        final String userName = userPrincipal == null ? null : userPrincipal.getName();
156
157        final FedoraSession session = batchService.getSession(txId, userName);
158        final HttpSession batchSession = new HttpSession(session);
159        batchSession.makeBatchSession();
160        LOGGER.debug("Returning a session in the batch {} for user {}", batchSession, userName);
161        return batchSession;
162    }
163
164    /**
165     * Extract the id embedded at the beginning of a request path
166     *
167     * @param servletRequest the servlet request
168     * @param prefix the prefix for the id
169     * @return the found id or null
170     */
171    protected String getEmbeddedId(
172            final HttpServletRequest servletRequest, final Prefix prefix) {
173        String requestPath = servletRequest.getPathInfo();
174
175        // http://stackoverflow.com/questions/18963562/grizzlys-request-getpathinfo-returns-always-null
176        if (requestPath == null && servletRequest.getContextPath().isEmpty()) {
177            requestPath = servletRequest.getRequestURI();
178        }
179
180        String id = null;
181        if (requestPath != null) {
182            final String pathPrefix = prefix.getPrefix();
183            final String[] part = requestPath.split("/");
184            if (part.length > 1 && part[1].startsWith(pathPrefix)) {
185                id = part[1].substring(pathPrefix.length());
186            }
187        }
188        return id;
189    }
190
191}