001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.crypto.key;
019
020import java.io.IOException;
021import java.security.NoSuchAlgorithmException;
022import java.util.concurrent.ExecutionException;
023import java.util.concurrent.TimeUnit;
024
025import com.google.common.cache.CacheBuilder;
026import com.google.common.cache.CacheLoader;
027import com.google.common.cache.LoadingCache;
028
029/**
030 * A <code>KeyProviderExtension</code> implementation providing a short lived
031 * cache for <code>KeyVersions</code> and <code>Metadata</code>to avoid burst
032 * of requests to hit the underlying <code>KeyProvider</code>.
033 */
034public class CachingKeyProvider extends
035    KeyProviderExtension<CachingKeyProvider.CacheExtension> {
036
037  static class CacheExtension implements KeyProviderExtension.Extension {
038    private final KeyProvider provider;
039    private LoadingCache<String, KeyVersion> keyVersionCache;
040    private LoadingCache<String, KeyVersion> currentKeyCache;
041    private LoadingCache<String, Metadata> keyMetadataCache;
042
043    CacheExtension(KeyProvider prov, long keyTimeoutMillis,
044        long currKeyTimeoutMillis) {
045      this.provider = prov;
046      keyVersionCache =
047          CacheBuilder.newBuilder().expireAfterAccess(keyTimeoutMillis,
048              TimeUnit.MILLISECONDS)
049              .build(new CacheLoader<String, KeyVersion>() {
050                @Override
051                public KeyVersion load(String key) throws Exception {
052                  KeyVersion kv = provider.getKeyVersion(key);
053                  if (kv == null) {
054                    throw new KeyNotFoundException();
055                  }
056                  return kv;
057                }
058              });
059      keyMetadataCache =
060          CacheBuilder.newBuilder().expireAfterAccess(keyTimeoutMillis,
061              TimeUnit.MILLISECONDS)
062              .build(new CacheLoader<String, Metadata>() {
063                @Override
064                public Metadata load(String key) throws Exception {
065                  Metadata meta = provider.getMetadata(key);
066                  if (meta == null) {
067                    throw new KeyNotFoundException();
068                  }
069                  return meta;
070                }
071              });
072      currentKeyCache =
073          CacheBuilder.newBuilder().expireAfterWrite(currKeyTimeoutMillis,
074          TimeUnit.MILLISECONDS)
075          .build(new CacheLoader<String, KeyVersion>() {
076            @Override
077            public KeyVersion load(String key) throws Exception {
078              KeyVersion kv = provider.getCurrentKey(key);
079              if (kv == null) {
080                throw new KeyNotFoundException();
081              }
082              return kv;
083            }
084          });
085    }
086  }
087
088  @SuppressWarnings("serial")
089  private static class KeyNotFoundException extends Exception { }
090
091  public CachingKeyProvider(KeyProvider keyProvider, long keyTimeoutMillis,
092      long currKeyTimeoutMillis) {
093    super(keyProvider, new CacheExtension(keyProvider, keyTimeoutMillis,
094        currKeyTimeoutMillis));
095  }
096
097  @Override
098  public KeyVersion getCurrentKey(String name) throws IOException {
099    try {
100      return getExtension().currentKeyCache.get(name);
101    } catch (ExecutionException ex) {
102      Throwable cause = ex.getCause();
103      if (cause instanceof KeyNotFoundException) {
104        return null;
105      } else if (cause instanceof IOException) {
106        throw (IOException) cause;
107      } else {
108        throw new IOException(cause);
109      }
110    }
111  }
112
113  @Override
114  public KeyVersion getKeyVersion(String versionName)
115      throws IOException {
116    try {
117      return getExtension().keyVersionCache.get(versionName);
118    } catch (ExecutionException ex) {
119      Throwable cause = ex.getCause();
120      if (cause instanceof KeyNotFoundException) {
121        return null;
122      } else if (cause instanceof IOException) {
123        throw (IOException) cause;
124      } else {
125        throw new IOException(cause);
126      }
127    }
128  }
129
130  @Override
131  public void deleteKey(String name) throws IOException {
132    getKeyProvider().deleteKey(name);
133    getExtension().currentKeyCache.invalidate(name);
134    getExtension().keyMetadataCache.invalidate(name);
135    // invalidating all key versions as we don't know
136    // which ones belonged to the deleted key
137    getExtension().keyVersionCache.invalidateAll();
138  }
139
140  @Override
141  public KeyVersion rollNewVersion(String name, byte[] material)
142      throws IOException {
143    KeyVersion key = getKeyProvider().rollNewVersion(name, material);
144    getExtension().currentKeyCache.invalidate(name);
145    getExtension().keyMetadataCache.invalidate(name);
146    return key;
147  }
148
149  @Override
150  public KeyVersion rollNewVersion(String name)
151      throws NoSuchAlgorithmException, IOException {
152    KeyVersion key = getKeyProvider().rollNewVersion(name);
153    getExtension().currentKeyCache.invalidate(name);
154    getExtension().keyMetadataCache.invalidate(name);
155    return key;
156  }
157
158  @Override
159  public Metadata getMetadata(String name) throws IOException {
160    try {
161      return getExtension().keyMetadataCache.get(name);
162    } catch (ExecutionException ex) {
163      Throwable cause = ex.getCause();
164      if (cause instanceof KeyNotFoundException) {
165        return null;
166      } else if (cause instanceof IOException) {
167        throw (IOException) cause;
168      } else {
169        throw new IOException(cause);
170      }
171    }
172  }
173
174}