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}