/*
* Licensed to DuraSpace under one or more contributor license agreements.
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership.
*
* DuraSpace licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fcrepo.kernel.modeshape;
import static java.lang.Long.parseLong;
import static java.time.Duration.ofMillis;
import static java.time.Duration.ofMinutes;
import static java.time.Instant.now;
import static java.util.Collections.singleton;
import static java.util.Optional.of;
import static java.util.UUID.randomUUID;
import java.time.Duration;
import java.time.Instant;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.observation.ObservationManager;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.annotations.VisibleForTesting;
import org.fcrepo.kernel.api.FedoraSession;
import org.fcrepo.kernel.api.exception.AccessDeniedException;
import org.fcrepo.kernel.api.exception.RepositoryRuntimeException;
import org.fcrepo.kernel.modeshape.utils.NamespaceTools;
/**
* An implementation of the FedoraSession abstraction
* @author acoburn
*/
public class FedoraSessionImpl implements FedoraSession {
// The default timeout is 3 minutes
@VisibleForTesting
public static final String DEFAULT_TIMEOUT = Long.toString(ofMinutes(3).toMillis());
@VisibleForTesting
public static final String TIMEOUT_SYSTEM_PROPERTY = "fcrepo.session.timeout";
private final Session jcrSession;
private final String id;
private final Instant created;
private final ConcurrentHashMap<String, String> sessionData;
private Instant expires;
/**
* A key for looking up the transaction id in a session key-value pair
*/
public static final String FCREPO_TX_ID = "fcrepo.tx.id";
private static final ObjectMapper mapper = new ObjectMapper();
/**
* Create a Fedora session with a JCR session
* @param session the JCR session
*/
public FedoraSessionImpl(final Session session) {
this.jcrSession = session;
created = now();
id = randomUUID().toString();
expires = created.plus(operationTimeout());
sessionData = new ConcurrentHashMap<>();
}
@Override
public void commit() {
try {
if (jcrSession.isLive()) {
final ObservationManager obs = jcrSession.getWorkspace().getObservationManager();
final ObjectNode json = mapper.createObjectNode();
sessionData.forEach(json::put);
obs.setUserData(mapper.writeValueAsString(json));
jcrSession.save();
}
} catch (final javax.jcr.AccessDeniedException ex) {
throw new AccessDeniedException(ex);
} catch (final RepositoryException | JsonProcessingException ex) {
throw new RepositoryRuntimeException(ex);
}
}
@Override
public void expire() {
expires = now();
try {
if (jcrSession.isLive()) {
jcrSession.refresh(false);
jcrSession.logout();
}
} catch (final RepositoryException ex) {
throw new RepositoryRuntimeException(ex);
}
}
@Override
public Instant updateExpiry(final Duration amountToAdd) {
if (jcrSession.isLive()) {
expires = now().plus(amountToAdd);
}
return expires;
}
@Override
public Instant getCreated() {
return created;
}
@Override
public Optional<Instant> getExpires() {
return of(expires);
}
@Override
public String getId() {
return id;
}
@Override
public String getUserId() {
return jcrSession.getUserID();
}
@Override
public Map<String, String> getNamespaces() {
return NamespaceTools.getNamespaces(jcrSession);
}
/**
* Add session data
* @param key the data key
* @param value the data value
*
* Note: while the FedoraSession interface permits multi-valued
* session data, this implementation constrains that to be single-valued.
* That is, calling obj.addSessionData("key", "value1") followed by
* obj.addSessionData("key", "value2") will result in only "value2" being associated
* with the given key.
*/
@Override
public void addSessionData(final String key, final String value) {
sessionData.put(key, value);
}
@Override
public Collection<String> getSessionData(final String key) {
return singleton(sessionData.get(key));
}
@Override
public void removeSessionData(final String key, final String value) {
sessionData.remove(key, value);
}
@Override
public void removeSessionData(final String key) {
sessionData.remove(key);
}
/**
* Get the internal JCR session
* @return the internal JCR session
*/
public Session getJcrSession() {
return jcrSession;
}
/**
* Get the internal JCR session from an existing FedoraSession
* @param session the FedoraSession
* @return the JCR session
*/
public static Session getJcrSession(final FedoraSession session) {
if (session instanceof FedoraSessionImpl) {
return ((FedoraSessionImpl)session).getJcrSession();
}
throw new ClassCastException("FedoraSession is not a " + FedoraSessionImpl.class.getCanonicalName());
}
/**
* Retrieve the default operation timeout value
* @return the default timeout value
*/
public static Duration operationTimeout() {
return ofMillis(parseLong(System.getProperty(TIMEOUT_SYSTEM_PROPERTY, DEFAULT_TIMEOUT)));
}
}