/*
* 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.apache.harmony.security.fortress;
import java.security.Provider;
import java.security.Security;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
/**
* This class contains information about all registered providers and preferred
* implementations for all "serviceName.algName".
*/
public class Services {
/**
* Save default SecureRandom service as well.
* Avoids similar provider/services iteration in SecureRandom constructor.
*/
private static Provider.Service cachedSecureRandomService;
/**
* Need refresh flag.
*/
private static boolean needRefresh;
/**
* The cacheVersion is changed on every update of service
* information. It is used by external callers to validate their
* own caches of Service information.
*/
private static int cacheVersion = 1;
/**
* Registered providers.
*/
private static final ArrayList<Provider> providers = new ArrayList<Provider>(20);
/**
* Try to load and register a provider by name from the given class-loader.
*/
private static boolean initProvider(String providerClassName, ClassLoader classLoader) {
try {
Class<?> providerClass = Class.forName(providerClassName.trim(), true, classLoader);
Provider p = (Provider) providerClass.newInstance();
providers.add(p);
providersNames.put(p.getName(), p);
return true;
} catch (ClassNotFoundException ignored) {
} catch (IllegalAccessException ignored) {
} catch (InstantiationException ignored) {
}
return false;
}
/**
* Hash for quick provider access by name.
*/
private static final HashMap<String, Provider> providersNames
= new HashMap<String, Provider>(20);
static {
String providerClassName = null;
int i = 1;
ClassLoader cl = Services.class.getClassLoader();
while ((providerClassName = Security.getProperty("security.provider." + i++)) != null) {
if (!initProvider(providerClassName, cl)) {
// Not on the boot classpath. Try the system class-loader.
// Note: DO NOT USE A LOCAL FOR GETSYSTEMCLASSLOADER! This will break compile-time
// initialization.
if (!initProvider(providerClassName, ClassLoader.getSystemClassLoader())) {
// TODO: Logging?
}
}
}
Engine.door.renumProviders();
setNeedRefresh();
}
/**
* Returns the actual registered providers.
*/
public static synchronized ArrayList<Provider> getProviders() {
return providers;
}
/**
* Returns the provider with the specified name.
*/
public static synchronized Provider getProvider(String name) {
if (name == null) {
return null;
}
return providersNames.get(name);
}
/**
* Inserts a provider at a specified 1-based position.
*/
public static synchronized int insertProviderAt(Provider provider, int position) {
int size = providers.size();
if ((position < 1) || (position > size)) {
position = size + 1;
}
providers.add(position - 1, provider);
providersNames.put(provider.getName(), provider);
setNeedRefresh();
return position;
}
/**
* Removes the provider at the specified 1-based position.
*/
public static synchronized void removeProvider(int providerNumber) {
Provider p = providers.remove(providerNumber - 1);
providersNames.remove(p.getName());
setNeedRefresh();
}
/**
* Looks up the requested service by type and algorithm. The service
* {@code type} and should be provided in the same format used when
* registering a service with a provider, for example, "KeyFactory.RSA".
* Callers can cache the returned service information but such caches should
* be validated against the result of Service.getCacheVersion() before use.
* Returns {@code null} if there are no services found.
*/
public static synchronized ArrayList<Provider.Service> getServices(String type,
String algorithm) {
ArrayList<Provider.Service> services = null;
for (Provider p : providers) {
Provider.Service s = p.getService(type, algorithm);
if (s != null) {
if (services == null) {
services = new ArrayList<>(providers.size());
}
services.add(s);
}
}
return services;
}
/**
* Finds the first service offered of {@code type} and returns it.
*/
private static synchronized Provider.Service getFirstServiceOfType(String type) {
for (Provider p : providers) {
Provider.Service s = Engine.door.getService(p, type);
if (s != null) {
return s;
}
}
return null;
}
/**
* Returns the default SecureRandom service description.
*/
public static synchronized Provider.Service getSecureRandomService() {
getCacheVersion(); // used for side effect of updating cache if needed
return cachedSecureRandomService;
}
/**
* In addition to being used here when the list of providers
* changes, this method is also used by the Provider
* implementation to indicate that a provides list of services has
* changed.
*/
public static synchronized void setNeedRefresh() {
needRefresh = true;
}
/**
* Returns the current cache version. This has the possible side
* effect of updating the cache if needed.
*/
public static synchronized int getCacheVersion() {
if (needRefresh) {
cacheVersion++;
cachedSecureRandomService = getFirstServiceOfType("SecureRandom");
needRefresh = false;
}
return cacheVersion;
}
}