/* * * * Copyright (c) 2016. David Sowerby * * * * Licensed 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 uk.q3c.krail.core.option; import org.apache.shiro.authz.UnauthorizedException; import uk.q3c.krail.core.persist.cache.option.DefaultOptionCacheLoader; import uk.q3c.krail.core.persist.cache.option.OptionCache; import uk.q3c.krail.core.persist.cache.option.OptionCacheKey; import uk.q3c.krail.core.persist.common.option.OptionDaoDelegate; import uk.q3c.krail.core.shiro.SubjectIdentifier; import uk.q3c.krail.core.shiro.SubjectProvider; import uk.q3c.krail.core.user.profile.RankOption; import uk.q3c.krail.core.user.profile.UserHierarchy; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.Optional; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static uk.q3c.krail.core.user.profile.RankOption.*; /** * Base implementation for {@link Option}. Uses {@link OptionCache}, which is configured to use some form of * persistence. All calls reference an implementation of {@link UserHierarchy}, either directly as a method * parameter, or by defaulting to {@link #hierarchy}. The get() and set() default to using the highest rank * from {@link UserHierarchy}. For getting or setting values at a specific hierarchyRank use the getSpecific() and * setSpecific() methods. The delete() method is always specific * <br> * To create a hierarchy specific implementation, simply sub-class with the alternative hierarchy injected into it. * <br> * Permission is required to execute {@link #set(OptionKey, int, Object)}, {@link #set(OptionKey, Object)} or {@link #delete(OptionKey, int)}. Permission * required is represented by an instance of {@link OptionPermission}. If permissions are required to view, these would need to be applied at the user * interface.<br> * <b>NOTE:</b> All values to and from {@link Option} are natively typed. All values to and from {@link OptionCache}, {@link DefaultOptionCacheLoader} and * {@link OptionDaoDelegate} are wrapped in Optional. * </p> * Created by David Sowerby on 03/12/14. */ public abstract class OptionBase implements Option { private UserHierarchy hierarchy; private OptionCache optionCache; private SubjectIdentifier subjectIdentifier; private SubjectProvider subjectProvider; protected OptionBase(OptionCache optionCache, UserHierarchy hierarchy, SubjectProvider subjectProvider, SubjectIdentifier subjectIdentifier) { this.hierarchy = hierarchy; this.optionCache = optionCache; this.subjectProvider = subjectProvider; this.subjectIdentifier = subjectIdentifier; } @Override public UserHierarchy getHierarchy() { return hierarchy; } @Override public <T> void set(@Nonnull OptionKey<T> optionKey, T value) { set(optionKey, 0, value); } @Override public synchronized <T> void set(@Nonnull OptionKey<T> optionKey, int hierarchyRank, @Nonnull T value) { checkArgument(hierarchyRank >= 0); checkNotNull(optionKey); OptionPermission permission = new OptionPermission(OptionPermission.Action.EDIT, hierarchy, hierarchyRank, optionKey, subjectIdentifier.userId()); if (subjectProvider.get() .isPermitted(permission)) { optionCache.write(new OptionCacheKey<>(hierarchy, SPECIFIC_RANK, hierarchyRank, optionKey), Optional.of(value)); } else { throw new UnauthorizedException(); } } @Override @Nonnull public synchronized <T> T get(@Nonnull OptionKey<T> optionKey) { checkNotNull(optionKey); return getRankedValue(optionKey, HIGHEST_RANK); } private <T> T getRankedValue(@Nonnull OptionKey<T> optionKey, RankOption rank) { T defaultValue = optionKey.getDefaultValue(); Optional<T> optionalValue = optionCache.get(Optional.of(defaultValue), new OptionCacheKey<>(hierarchy, rank, 0, optionKey)); if (optionalValue == null) { return defaultValue; } if (optionalValue.isPresent()) { return optionalValue.get(); } else { return defaultValue; } } @Nonnull @Override public synchronized <T> T getLowestRanked(@Nonnull OptionKey<T> optionKey) { checkNotNull(optionKey); return getRankedValue(optionKey, LOWEST_RANK); } @Nonnull @Override public synchronized <T> T getSpecificRanked(int hierarchyRank, @Nonnull OptionKey<T> optionKey) { checkNotNull(optionKey); T defaultValue = optionKey.getDefaultValue(); Optional<T> optionalValue = optionCache.get(Optional.of(defaultValue), new OptionCacheKey(hierarchy, SPECIFIC_RANK, hierarchyRank, optionKey)); if (optionalValue == null) { return defaultValue; } if (optionalValue.isPresent()) { return optionalValue.get(); } else { return defaultValue; } } @Override @Nullable public <T> T delete(@Nonnull OptionKey<T> optionKey, int hierarchyRank) { checkArgument(hierarchyRank >= 0); checkNotNull(optionKey); OptionPermission permission = new OptionPermission(OptionPermission.Action.EDIT, hierarchy, hierarchyRank, optionKey, subjectIdentifier.userId()); if (subjectProvider.get() .isPermitted(permission)) { //noinspection unchecked return (T) optionCache.delete(new OptionCacheKey(hierarchy, SPECIFIC_RANK, hierarchyRank, optionKey)); } else { throw new UnauthorizedException(); } } }