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     */
018    package org.apache.hadoop.crypto.key;
019    
020    import java.io.IOException;
021    import java.security.NoSuchAlgorithmException;
022    import java.util.concurrent.ExecutionException;
023    import java.util.concurrent.TimeUnit;
024    
025    import com.google.common.cache.CacheBuilder;
026    import com.google.common.cache.CacheLoader;
027    import 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     */
034    public 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    }