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