/* * 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.services; import static java.time.Instant.now; import static java.util.stream.Collectors.toSet; import static com.google.common.base.Strings.nullToEmpty; import static org.fcrepo.kernel.modeshape.FedoraSessionImpl.operationTimeout; import static org.slf4j.LoggerFactory.getLogger; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.fcrepo.kernel.api.FedoraSession; import org.fcrepo.kernel.api.exception.RepositoryRuntimeException; import org.fcrepo.kernel.api.exception.SessionMissingException; import org.fcrepo.kernel.api.services.BatchService; import com.google.common.annotations.VisibleForTesting; import org.slf4j.Logger; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; /** * This is part of the strawman implementation for Fedora batch operations This * service implements a simple {@link FedoraSession} service which is able to * create/commit/rollback {@link FedoraSession} objects. A {@link Scheduled} * annotation is used for removing timed out operations * * @author frank asseg * @author ajs6f * @author acoburn */ @Component public class BatchServiceImpl extends AbstractService implements BatchService { private static final Logger LOGGER = getLogger(BatchServiceImpl.class); /** * TODO since sessions have to be available on all nodes, they have to * be either persisted or written to a distributed map or sth, not just this * plain hashmap that follows */ private static Map<String, FedoraSession> sessions = new ConcurrentHashMap<>(); @VisibleForTesting public static final long REAP_INTERVAL = 1000; /** * Every REAP_INTERVAL milliseconds, check for expired sessions. If the * tx is expired, roll it back and remove it from the registry. */ @Override @Scheduled(fixedRate = REAP_INTERVAL) public void removeExpired() { final Set<String> reapable = sessions.entrySet().stream() .filter(e -> e.getValue().getExpires().isPresent()) .filter(e -> e.getValue().getExpires().get().isBefore(now())) .map(Map.Entry::getKey).collect(toSet()); reapable.forEach(key -> { final FedoraSession s = sessions.get(key); if (s != null) { try { s.expire(); } catch (final RepositoryRuntimeException e) { LOGGER.error("Got exception rolling back expired session {}: {}", s, e.getMessage()); } } sessions.remove(key); }); } @Override public void begin(final FedoraSession session, final String username) { sessions.put(getTxKey(session.getId(), username), session); } @Override public FedoraSession getSession(final String sessionId, final String username) { final FedoraSession session = sessions.get(getTxKey(sessionId, username)); if (session == null) { throw new SessionMissingException("Batch session with id: " + sessionId + " is not available"); } return session; } @Override public boolean exists(final String sessionId, final String username) { return sessions.containsKey(getTxKey(sessionId, username)); } @Override public void commit(final String sessionId, final String username) { final FedoraSession session = getSession(sessionId, username); session.commit(); sessions.remove(getTxKey(sessionId, username)); } @Override public void refresh(final String sessionId, final String username) { final FedoraSession session = getSession(sessionId, username); session.updateExpiry(operationTimeout()); } @Override public void abort(final String sessionId, final String username) { final FedoraSession session = getSession(sessionId, username); session.expire(); sessions.remove(getTxKey(sessionId, username)); } private static String getTxKey(final String sessionId, final String username) { return nullToEmpty(username) + ":" + sessionId; } }