org.jclouds.chef.config.ChefHttpApiModule.java Source code

Java tutorial

Introduction

Here is the source code for org.jclouds.chef.config.ChefHttpApiModule.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.jclouds.chef.config;

import static com.google.common.base.Suppliers.compose;
import static com.google.common.base.Suppliers.memoizeWithExpiration;
import static com.google.common.base.Throwables.propagate;
import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
import static org.jclouds.chef.config.ChefProperties.CHEF_VALIDATOR_CREDENTIAL;
import static org.jclouds.chef.config.ChefProperties.CHEF_VALIDATOR_NAME;
import static org.jclouds.crypto.Pems.privateKeySpec;

import java.io.IOException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.util.concurrent.TimeUnit;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;

import org.jclouds.chef.ChefApi;
import org.jclouds.chef.domain.BootstrapConfig;
import org.jclouds.chef.domain.Client;
import org.jclouds.chef.functions.BootstrapConfigForGroup;
import org.jclouds.chef.functions.ClientForGroup;
import org.jclouds.chef.handlers.ChefApiErrorRetryHandler;
import org.jclouds.chef.handlers.ChefErrorHandler;
import org.jclouds.crypto.Crypto;
import org.jclouds.crypto.Pems;
import org.jclouds.date.DateService;
import org.jclouds.date.TimeStamp;
import org.jclouds.domain.Credentials;
import org.jclouds.http.HttpErrorHandler;
import org.jclouds.http.HttpRetryHandler;
import org.jclouds.http.annotation.ClientError;
import org.jclouds.http.annotation.Redirection;
import org.jclouds.http.annotation.ServerError;
import org.jclouds.rest.ConfiguresHttpApi;
import org.jclouds.rest.config.HttpApiModule;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Charsets;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Supplier;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.io.ByteSource;
import com.google.inject.ConfigurationException;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Provides;
import com.google.inject.name.Names;

/**
 * Configures the Chef connection.
 */
@ConfiguresHttpApi
public class ChefHttpApiModule extends HttpApiModule<ChefApi> {

    @Provides
    @TimeStamp
    protected final String guiceProvideTimeStamp(@TimeStamp Supplier<String> cache) {
        return provideTimeStamp(cache);
    }

    protected String provideTimeStamp(@TimeStamp Supplier<String> cache) {
        return cache.get();
    }

    /**
     * borrowing concurrency code to ensure that caching takes place properly
     */
    @Provides
    @TimeStamp
    final Supplier<String> provideTimeStampCache(@Named(PROPERTY_SESSION_INTERVAL) long seconds,
            final DateService dateService) {
        return memoizeWithExpiration(new Supplier<String>() {
            @Override
            public String get() {
                return dateService.iso8601SecondsDateFormat();
            }
        }, seconds, TimeUnit.SECONDS);
    }

    // TODO: potentially change this
    @Provides
    @Singleton
    public final Supplier<PrivateKey> supplyKey(final LoadingCache<Credentials, PrivateKey> keyCache,
            @org.jclouds.location.Provider final Supplier<Credentials> creds) {
        return compose(new Function<Credentials, PrivateKey>() {
            @Override
            public PrivateKey apply(Credentials in) {
                return keyCache.getUnchecked(in);
            }
        }, creds);
    }

    @Provides
    @Singleton
    final LoadingCache<Credentials, PrivateKey> privateKeyCache(PrivateKeyForCredentials loader) {
        // throw out the private key related to old credentials
        return CacheBuilder.newBuilder().maximumSize(2).build(loader);
    }

    /**
     * it is relatively expensive to extract a private key from a PEM. cache the
     * relationship between current credentials so that the private key is only
     * recalculated once.
     */
    @VisibleForTesting
    @Singleton
    private static class PrivateKeyForCredentials extends CacheLoader<Credentials, PrivateKey> {
        private final Crypto crypto;

        @Inject
        private PrivateKeyForCredentials(Crypto crypto) {
            this.crypto = crypto;
        }

        @Override
        public PrivateKey load(Credentials in) {
            try {
                return crypto.rsaKeyFactory()
                        .generatePrivate(privateKeySpec(ByteSource.wrap(in.credential.getBytes(Charsets.UTF_8))));
            } catch (InvalidKeySpecException e) {
                throw propagate(e);
            } catch (IOException e) {
                throw propagate(e);
            }
        }
    }

    @Provides
    @Singleton
    @Validator
    public final Optional<String> provideValidatorName(Injector injector) {
        // Named properties can not be injected as optional here, so let's use the
        // injector to bypass it
        Key<String> key = Key.get(String.class, Names.named(CHEF_VALIDATOR_NAME));
        try {
            return Optional.<String>of(injector.getInstance(key));
        } catch (ConfigurationException ex) {
            return Optional.<String>absent();
        }
    }

    @Provides
    @Singleton
    @Validator
    public final Optional<PrivateKey> provideValidatorCredential(Crypto crypto, Injector injector)
            throws InvalidKeySpecException, IOException {
        // Named properties can not be injected as optional here, so let's use the
        // injector to bypass it
        Key<String> key = Key.get(String.class, Names.named(CHEF_VALIDATOR_CREDENTIAL));
        try {
            String validatorCredential = injector.getInstance(key);
            PrivateKey validatorKey = crypto.rsaKeyFactory().generatePrivate(
                    Pems.privateKeySpec(ByteSource.wrap(validatorCredential.getBytes(Charsets.UTF_8))));
            return Optional.<PrivateKey>of(validatorKey);
        } catch (ConfigurationException ex) {
            return Optional.<PrivateKey>absent();
        }
    }

    @Provides
    @Singleton
    final CacheLoader<String, BootstrapConfig> bootstrapConfigForGroup(
            BootstrapConfigForGroup bootstrapConfigForGroup) {
        return CacheLoader.from(bootstrapConfigForGroup);
    }

    @Provides
    @Singleton
    final CacheLoader<String, Client> groupToClient(ClientForGroup clientForGroup) {
        return CacheLoader.from(clientForGroup);
    }

    @Override
    protected void bindErrorHandlers() {
        bind(HttpErrorHandler.class).annotatedWith(Redirection.class).to(ChefErrorHandler.class);
        bind(HttpErrorHandler.class).annotatedWith(ClientError.class).to(ChefErrorHandler.class);
        bind(HttpErrorHandler.class).annotatedWith(ServerError.class).to(ChefErrorHandler.class);
    }

    @Override
    protected void bindRetryHandlers() {
        bind(HttpRetryHandler.class).annotatedWith(ClientError.class).to(ChefApiErrorRetryHandler.class);
    }

}