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.kernel.modeshape; 019 020import static java.lang.Long.parseLong; 021import static java.time.Duration.ofMillis; 022import static java.time.Duration.ofMinutes; 023import static java.time.Instant.now; 024import static java.util.Collections.singleton; 025import static java.util.Optional.of; 026import static java.util.UUID.randomUUID; 027 028import java.time.Duration; 029import java.time.Instant; 030import java.util.Collection; 031import java.util.Map; 032import java.util.Optional; 033import java.util.concurrent.ConcurrentHashMap; 034 035import javax.jcr.RepositoryException; 036import javax.jcr.Session; 037import javax.jcr.observation.ObservationManager; 038 039import com.fasterxml.jackson.core.JsonProcessingException; 040import com.fasterxml.jackson.databind.ObjectMapper; 041import com.fasterxml.jackson.databind.node.ObjectNode; 042import com.google.common.annotations.VisibleForTesting; 043import org.fcrepo.kernel.api.FedoraSession; 044import org.fcrepo.kernel.api.exception.AccessDeniedException; 045import org.fcrepo.kernel.api.exception.RepositoryRuntimeException; 046import org.fcrepo.kernel.modeshape.utils.NamespaceTools; 047 048/** 049 * An implementation of the FedoraSession abstraction 050 * @author acoburn 051 */ 052public class FedoraSessionImpl implements FedoraSession { 053 054 // The default timeout is 3 minutes 055 @VisibleForTesting 056 public static final String DEFAULT_TIMEOUT = Long.toString(ofMinutes(3).toMillis()); 057 058 @VisibleForTesting 059 public static final String TIMEOUT_SYSTEM_PROPERTY = "fcrepo.session.timeout"; 060 061 private final Session jcrSession; 062 private final String id; 063 private final Instant created; 064 private final ConcurrentHashMap<String, String> sessionData; 065 private Instant expires; 066 067 /** 068 * A key for looking up the transaction id in a session key-value pair 069 */ 070 public static final String FCREPO_TX_ID = "fcrepo.tx.id"; 071 072 private static final ObjectMapper mapper = new ObjectMapper(); 073 074 /** 075 * Create a Fedora session with a JCR session 076 * @param session the JCR session 077 */ 078 public FedoraSessionImpl(final Session session) { 079 this.jcrSession = session; 080 081 created = now(); 082 id = randomUUID().toString(); 083 expires = created.plus(operationTimeout()); 084 sessionData = new ConcurrentHashMap<>(); 085 } 086 087 @Override 088 public void commit() { 089 try { 090 if (jcrSession.isLive()) { 091 final ObservationManager obs = jcrSession.getWorkspace().getObservationManager(); 092 final ObjectNode json = mapper.createObjectNode(); 093 sessionData.forEach(json::put); 094 obs.setUserData(mapper.writeValueAsString(json)); 095 jcrSession.save(); 096 } 097 } catch (final javax.jcr.AccessDeniedException ex) { 098 throw new AccessDeniedException(ex); 099 } catch (final RepositoryException | JsonProcessingException ex) { 100 throw new RepositoryRuntimeException(ex); 101 } 102 } 103 104 @Override 105 public void expire() { 106 expires = now(); 107 try { 108 if (jcrSession.isLive()) { 109 jcrSession.refresh(false); 110 jcrSession.logout(); 111 } 112 } catch (final RepositoryException ex) { 113 throw new RepositoryRuntimeException(ex); 114 } 115 } 116 117 @Override 118 public Instant updateExpiry(final Duration amountToAdd) { 119 if (jcrSession.isLive()) { 120 expires = now().plus(amountToAdd); 121 } 122 return expires; 123 } 124 125 @Override 126 public Instant getCreated() { 127 return created; 128 } 129 130 @Override 131 public Optional<Instant> getExpires() { 132 return of(expires); 133 } 134 135 @Override 136 public String getId() { 137 return id; 138 } 139 140 @Override 141 public String getUserId() { 142 return jcrSession.getUserID(); 143 } 144 145 @Override 146 public Map<String, String> getNamespaces() { 147 return NamespaceTools.getNamespaces(jcrSession); 148 } 149 150 /** 151 * Add session data 152 * @param key the data key 153 * @param value the data value 154 * 155 * Note: while the FedoraSession interface permits multi-valued 156 * session data, this implementation constrains that to be single-valued. 157 * That is, calling obj.addSessionData("key", "value1") followed by 158 * obj.addSessionData("key", "value2") will result in only "value2" being associated 159 * with the given key. 160 */ 161 @Override 162 public void addSessionData(final String key, final String value) { 163 sessionData.put(key, value); 164 } 165 166 @Override 167 public Collection<String> getSessionData(final String key) { 168 return singleton(sessionData.get(key)); 169 } 170 171 @Override 172 public void removeSessionData(final String key, final String value) { 173 sessionData.remove(key, value); 174 } 175 176 @Override 177 public void removeSessionData(final String key) { 178 sessionData.remove(key); 179 } 180 181 /** 182 * Get the internal JCR session 183 * @return the internal JCR session 184 */ 185 public Session getJcrSession() { 186 return jcrSession; 187 } 188 189 /** 190 * Get the internal JCR session from an existing FedoraSession 191 * @param session the FedoraSession 192 * @return the JCR session 193 */ 194 public static Session getJcrSession(final FedoraSession session) { 195 if (session instanceof FedoraSessionImpl) { 196 return ((FedoraSessionImpl)session).getJcrSession(); 197 } 198 throw new ClassCastException("FedoraSession is not a " + FedoraSessionImpl.class.getCanonicalName()); 199 } 200 201 /** 202 * Retrieve the default operation timeout value 203 * @return the default timeout value 204 */ 205 public static Duration operationTimeout() { 206 return ofMillis(parseLong(System.getProperty(TIMEOUT_SYSTEM_PROPERTY, DEFAULT_TIMEOUT))); 207 } 208}