blob: d7324f6306ab8cf70d6e3fae3614c5437bea2a2d [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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.apache.rya.api.instance;
import static java.util.Objects.requireNonNull;
import edu.umd.cs.findbugs.annotations.DefaultAnnotation;
import edu.umd.cs.findbugs.annotations.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.rya.api.instance.RyaDetailsRepository.ConcurrentUpdateException;
import org.apache.rya.api.instance.RyaDetailsRepository.RyaDetailsRepositoryException;
import org.apache.rya.api.instance.RyaDetailsUpdater.RyaDetailsMutator.CouldNotApplyMutationException;
/**
* A utility that will attempt to commit a change to a Rya instance's details
* until it either takes or the mutation can no longer be applied. This can
* can be used in place of boilerplate code that handles the concurrent nature
* of details updates.
*/
@DefaultAnnotation(NonNull.class)
public class RyaDetailsUpdater {
private static final Logger log = LoggerFactory.getLogger(RyaDetailsUpdater.class);
/**
* Applies a mutation to a an instance of {@link RyaDetails}.
*/
@DefaultAnnotation(NonNull.class)
public static interface RyaDetailsMutator {
/**
* Applies a mutation to a {@link RyaDetails} object.
*
* @param originalDetails - The {@link RyaDetails} that were just fetched
* from the {@link RyaDetailsRepository}.
* @return The updated details.
* @throws CouldNotApplyMutationException The mutation could not be applied
* to the supplied {@link RyaDetails}. This can be used to break out of
* the update loop when the details are in a state the mutation can not
* be applied to.
*/
public RyaDetails mutate(RyaDetails originalDetails) throws CouldNotApplyMutationException;
/**
* Indicates a mutation could not be applied to an instance of {@link RyaDetails}.
*/
public static class CouldNotApplyMutationException extends Exception {
private static final long serialVersionUID = 1L;
/**
* Constructs a new exception with the specified detail message. The
* cause is not initialized, and may subsequently be initialized by
* a call to {@link #initCause(Throwable)}.
*
* @param message the detail message. The detail message is saved for
* later retrieval by the {@link #getMessage()} method.
*/
public CouldNotApplyMutationException(final String message) {
super(message);
}
}
}
private final RyaDetailsRepository repo;
/**
* Constructs an instance of {@link RyaDetailsUpdater}.
*
* @param repo - The repository that this updater will commit changes to. (not null)
*/
public RyaDetailsUpdater(final RyaDetailsRepository repo) {
this.repo = requireNonNull(repo);
}
/**
* Updates an instance of {@link RyaDetails} by fetching the details from
* the {@link RyaDetailsRepository} that was supplied at construction time,
* applying the {@link RyaDetailsMutator} to them, and them committing the
* change. If the update failed because of a concurrent update problem, then
* it will try again. The updater will continue to do this until the changes
* take or another exception type is thrown.
*
* @param mutator - The mutator that will be used to apply a chagne to the
* repository's {@link RyaDetails}.
* @throws RyaDetailsRepositoryException A repository side error caused
* the update to fail. This could be a communications problem with the
* store repository, uninitialized, etc.
* @throws CouldNotApplyMutationException An application side error caused
* the update to fail. This is thrown by the mutator when the details
* would be in an illegal state if the mutation were to be applied.
*/
public void update(final RyaDetailsMutator mutator) throws RyaDetailsRepositoryException, CouldNotApplyMutationException {
requireNonNull(mutator);
boolean updated = false;
while(!updated) {
try {
final RyaDetails original = repo.getRyaInstanceDetails();
final RyaDetails mutated = mutator.mutate(original);
repo.update(original, mutated);
updated = true;
} catch(final ConcurrentUpdateException e) {
log.debug("Failed to update the details because another application changed them. Trying again.", e);
}
}
}
}