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