blob: 1dc17f4bcfe5526236be947d2bf07f16bcb3d084 [file] [log] [blame]
// 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 = l.getMetadata().getDefaultViewUrl();
this.defaultViewItemUrl = l.getMetadata().getDefaultViewItemUrl();
this.scopeId = l.getMetadata().getScopeID();
}
}
}