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}