001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.commons.secret;
018
019import java.util.*;
020
021import org.apache.juneau.commons.inject.*;
022
023/**
024 * Resolution helpers for selecting the active {@link SecretStore}.
025 *
026 * <p>
027 * The active store is resolved from a {@link BeanStore}, defaulting to an {@link InMemorySecretStore} when none has
028 * been contributed &mdash; the same {@code beanStore.getBean(...)}-with-drop-in-default idiom other framework-owned
029 * components use.  An explicit {@link BeanStore} contribution always wins.
030 *
031 * <p>
032 * {@link #fromServiceLoader()} is the separate classpath-contribution path, mirroring
033 * {@link org.apache.juneau.commons.settings.Settings}'s opt-in {@code useServiceLoader()}.  It is deliberately
034 * <b>not</b> consulted by {@link #resolve(BeanStore)}, so the no-contribution default stays a deterministic
035 * {@link InMemorySecretStore} rather than silently becoming whatever store happens to be on the classpath (which, for
036 * an environment-variable-backed provider, would quietly turn arbitrary env vars into secrets).  A consumer that
037 * <i>wants</i> a classpath-contributed store can seed its {@link BeanStore} from {@link #fromServiceLoader()}.
038 *
039 * @since 10.0.0
040 */
041public final class SecretStores {
042
043   private SecretStores() {}
044
045   /**
046    * Resolves the active secret store from the specified bean store, defaulting to a new
047    * {@link InMemorySecretStore} when none is contributed.
048    *
049    * @param beanStore The bean store to resolve from.  Can be <jk>null</jk>, in which case the default is returned.
050    * @return The resolved store.  Never <jk>null</jk>.
051    */
052   public static SecretStore resolve(BeanStore beanStore) {
053      if (beanStore == null)
054         return new InMemorySecretStore();
055      return beanStore.getBean(SecretStore.class).orElseGet(InMemorySecretStore::new);
056   }
057
058   /**
059    * Discovers a secret store contributed on the classpath via {@link SecretStoreProvider} and {@code ServiceLoader}.
060    *
061    * <p>
062    * Providers are sorted by {@link SecretStoreProvider#order()} (lowest first) and the first one that yields a
063    * non-<jk>null</jk> store wins.
064    *
065    * @return The discovered store, or {@link Optional#empty()} if none is registered on the classpath.
066    */
067   public static Optional<SecretStore> fromServiceLoader() {
068      return ServiceLoader.load(SecretStoreProvider.class).stream()
069         .map(ServiceLoader.Provider::get)
070         .sorted(Comparator.comparingInt(SecretStoreProvider::order))
071         .map(SecretStoreProvider::create)
072         .filter(Objects::nonNull)
073         .findFirst();
074   }
075}