// Copyright 2013 Google Inc. All Rights Reserved. // // 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 com.google.enterprise.adaptor.sharepoint; import com.google.common.annotations.VisibleForTesting; import com.google.common.cache.CacheBuilder; import com.google.common.cache.LoadingCache; import com.microsoft.schemas.sharepoint.soap.List; import com.microsoft.schemas.sharepoint.soap.PolicyUser; import com.microsoft.schemas.sharepoint.soap.TrueFalseType; import com.microsoft.schemas.sharepoint.soap.VirtualServer; import com.microsoft.schemas.sharepoint.soap.Web; import java.io.IOException; import java.util.Arrays; import java.util.Locale; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; /** * Cache with items that rarely change, so items have a long lifetime in the * cache. */ class RareModificationCache { private final Executor executor; private final VirtualServerKey virtualServerKey; private final LoadingCache<CacheKey<?>, Object> cache = CacheBuilder.newBuilder() .expireAfterAccess(5, TimeUnit.MINUTES) .expireAfterWrite(30, TimeUnit.MINUTES) .build(new AsyncCacheLoader<CacheKey<?>, Object>() { @Override protected Executor executor() { return executor; } @Override public Object load(CacheKey<?> key) throws IOException { return key.computeValue(); } }); public RareModificationCache(SiteDataClient virtualServerSiteDataClient, Executor executor) { if (virtualServerSiteDataClient == null || executor == null) { throw new NullPointerException(); } this.executor = executor; this.virtualServerKey = new VirtualServerKey(virtualServerSiteDataClient); } /** * Getter that handles type-safety, and expected to be used for all retrievals * from the cache. */ @VisibleForTesting <T> T get(CacheKey<T> key) throws IOException { try { @SuppressWarnings("unchecked") T t = (T) cache.get(key); return t; } catch (ExecutionException e) { throw new IOException(e); } } public CachedVirtualServer getVirtualServer() throws IOException { return get(virtualServerKey); } public CachedWeb getWeb(SiteDataClient siteDataClient) throws IOException { return get(new WebKey(siteDataClient)); } public CachedList getList(SiteDataClient siteDataClient, String listId) throws IOException { return get(new ListKey(siteDataClient, listId)); } /** * The key used to identify a cache entry. Therefore, {@link Object#equals} * and {@link Object#hashCode} must be valid. */ @VisibleForTesting interface CacheKey<V> { public V computeValue() throws IOException; } /** * Key for the Virtual Server, of which there is only one instance. Therefore, * it does not need to override equals and hashCode. */ @VisibleForTesting static final class VirtualServerKey implements CacheKey<CachedVirtualServer> { private final SiteDataClient siteDataClient; public VirtualServerKey(SiteDataClient siteDataClient) { this.siteDataClient = siteDataClient; } @Override public CachedVirtualServer computeValue() throws IOException { return new CachedVirtualServer(siteDataClient.getContentVirtualServer()); } } public static final class CachedVirtualServer { public final long anonymousDenyMask; public final boolean policyContainsDeny; private CachedVirtualServer(VirtualServer vs) { this.anonymousDenyMask = vs.getPolicies().getAnonymousDenyMask().longValue(); boolean policyContainsDeny = false; for (PolicyUser policyUser : vs.getPolicies().getPolicyUser()) { long deny = policyUser.getDenyMask().longValue(); // If at least one necessary bit is masked, then deny user. if ((SharePointAdaptor.LIST_ITEM_MASK & deny) != 0) { policyContainsDeny = true; break; } } this.policyContainsDeny = policyContainsDeny; } } @VisibleForTesting static final class WebKey implements CacheKey<CachedWeb> { private final SiteDataClient siteDataClient; public WebKey(SiteDataClient siteDataClient) { this.siteDataClient = siteDataClient; } @Override public CachedWeb computeValue() throws IOException { return new CachedWeb(siteDataClient.getContentWeb()); } @Override public boolean equals(Object o) { if (!(o instanceof WebKey)) { return false; } WebKey webKey = (WebKey) o; return siteDataClient.equals(webKey.siteDataClient); } @Override public int hashCode() { return siteDataClient.hashCode(); } } public static final class CachedWeb { public final String noIndex; public final TrueFalseType allowAnonymousAccess; public final TrueFalseType anonymousViewListItems; public final long anonymousPermMask; public final String webTitle; public CachedWeb(Web w) { this.noIndex = w.getMetadata().getNoIndex(); this.allowAnonymousAccess = w.getMetadata().getAllowAnonymousAccess(); this.anonymousViewListItems = w.getMetadata().getAnonymousViewListItems(); this.anonymousPermMask = w.getMetadata().getAnonymousPermMask().longValue(); this.webTitle = w.getMetadata().getTitle(); } } @VisibleForTesting static final class ListKey implements CacheKey<CachedList> { private final SiteDataClient siteDataClient; private final String listId; public ListKey(SiteDataClient siteDataClient, String listId) { this.siteDataClient = siteDataClient; this.listId = listId.toUpperCase(Locale.ENGLISH); } @Override public CachedList computeValue() throws IOException { return new CachedList(siteDataClient.getContentList(listId)); } @Override public boolean equals(Object o) { if (!(o instanceof ListKey)) { return false; } ListKey listKey = (ListKey) o; return siteDataClient.equals(listKey.siteDataClient) && listId.equals(listKey.listId); } @Override public int hashCode() { return Arrays.hashCode(new Object[] {siteDataClient, listId}); } } public static final class CachedList { public final TrueFalseType noIndex; public final int readSecurity; public final TrueFalseType allowAnonymousAccess; public final TrueFalseType anonymousViewListItems; public final long anonymousPermMask; public final String rootFolder; public final String defaultViewUrl; public final String defaultViewItemUrl; /** * This field must not be used for general ACL inheritance of direct * decendants of the list. This is intended only for use when determining * whether anonymous access is permitted for list items and attachments. */ public final String scopeId; public CachedList(List l) { this.noIndex = l.getMetadata().getNoIndex(); this.readSecurity = l.getMetadata().getReadSecurity(); this.allowAnonymousAccess = l.getMetadata().getAllowAnonymousAccess(); this.anonymousViewListItems = l.getMetadata().getAnonymousViewListItems(); this.anonymousPermMask = l.getMetadata().getAnonymousPermMask().longValue(); this.rootFolder = l.getMetadata().getRootFolder(); this.defaultViewUrl = "/".equals(l.getMetadata().getDefaultViewUrl()) ? l.getMetadata().getRootFolder() : l.getMetadata().getDefaultViewUrl(); this.defaultViewItemUrl = l.getMetadata().getDefaultViewItemUrl(); this.scopeId = l.getMetadata().getScopeID(); } } }