1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package org.apache.harmony.security.fortress;
19
20import java.security.Provider;
21import java.security.Security;
22import java.util.ArrayList;
23import java.util.HashMap;
24import java.util.List;
25import java.util.Locale;
26import java.util.Map;
27
28
29/**
30 * This class contains information about all registered providers and preferred
31 * implementations for all "serviceName.algName".
32 */
33public class Services {
34
35    /**
36     * The HashMap that contains information about preferred implementations for
37     * all serviceName.algName in the registered providers.
38     * Set the initial size to 600 so we don't grow to 1024 by default because
39     * initialization adds a few entries more than the growth threshold.
40     */
41    private static final Map<String, Provider.Service> services
42            = new HashMap<String, Provider.Service>(600);
43
44    /**
45     * Save default SecureRandom service as well.
46     * Avoids similar provider/services iteration in SecureRandom constructor.
47     */
48    private static Provider.Service cachedSecureRandomService;
49
50    /**
51     * Need refresh flag.
52     */
53    private static boolean needRefresh;
54
55    /**
56     * The cacheVersion is changed on every update of service
57     * information. It is used by external callers to validate their
58     * own caches of Service information.
59     */
60    private static int cacheVersion = 1;
61
62    /**
63     * Registered providers.
64     */
65    private static final List<Provider> providers = new ArrayList<Provider>(20);
66
67    /**
68     * Hash for quick provider access by name.
69     */
70    private static final Map<String, Provider> providersNames = new HashMap<String, Provider>(20);
71    static {
72        String providerClassName = null;
73        int i = 1;
74        ClassLoader cl = ClassLoader.getSystemClassLoader();
75
76        while ((providerClassName = Security.getProperty("security.provider." + i++)) != null) {
77            try {
78                Class providerClass = Class.forName(providerClassName.trim(), true, cl);
79                Provider p = (Provider) providerClass.newInstance();
80                providers.add(p);
81                providersNames.put(p.getName(), p);
82                initServiceInfo(p);
83            } catch (ClassNotFoundException ignored) {
84            } catch (IllegalAccessException ignored) {
85            } catch (InstantiationException ignored) {
86            }
87        }
88        Engine.door.renumProviders();
89    }
90
91    /**
92     * Returns a copy of the registered providers as an array.
93     */
94    public static synchronized Provider[] getProviders() {
95        return providers.toArray(new Provider[providers.size()]);
96    }
97
98    /**
99     * Returns a copy of the registered providers as a list.
100     */
101    public static synchronized List<Provider> getProvidersList() {
102        return new ArrayList<Provider>(providers);
103    }
104
105    /**
106     * Returns the provider with the specified name.
107     */
108    public static synchronized Provider getProvider(String name) {
109        if (name == null) {
110            return null;
111        }
112        return providersNames.get(name);
113    }
114
115    /**
116     * Inserts a provider at a specified 1-based position.
117     */
118    public static synchronized int insertProviderAt(Provider provider, int position) {
119        int size = providers.size();
120        if ((position < 1) || (position > size)) {
121            position = size + 1;
122        }
123        providers.add(position - 1, provider);
124        providersNames.put(provider.getName(), provider);
125        setNeedRefresh();
126        return position;
127    }
128
129    /**
130     * Removes the provider at the specified 1-based position.
131     */
132    public static synchronized void removeProvider(int providerNumber) {
133        Provider p = providers.remove(providerNumber - 1);
134        providersNames.remove(p.getName());
135        setNeedRefresh();
136    }
137
138    /**
139     * Adds information about provider services into HashMap.
140     */
141    public static synchronized void initServiceInfo(Provider p) {
142        for (Provider.Service service : p.getServices()) {
143            String type = service.getType();
144            if (cachedSecureRandomService == null && type.equals("SecureRandom")) {
145                cachedSecureRandomService = service;
146            }
147            String key = type + "." + service.getAlgorithm().toUpperCase(Locale.US);
148            if (!services.containsKey(key)) {
149                services.put(key, service);
150            }
151            for (String alias : Engine.door.getAliases(service)) {
152                key = type + "." + alias.toUpperCase(Locale.US);
153                if (!services.containsKey(key)) {
154                    services.put(key, service);
155                }
156            }
157        }
158    }
159
160    /**
161     * Returns true if services contain any provider information.
162     */
163    public static synchronized boolean isEmpty() {
164        return services.isEmpty();
165    }
166
167    /**
168     * Looks up the requested service by type and algorithm. The
169     * service key should be provided in the same format used when
170     * registering a service with a provider, for example,
171     * "KeyFactory.RSA".
172     *
173     * Callers can cache the returned service information but such
174     * caches should be validated against the result of
175     * Service.getCacheVersion() before use.
176     */
177    public static synchronized Provider.Service getService(String key) {
178        return services.get(key);
179    }
180
181    /**
182     * Returns the default SecureRandom service description.
183     */
184    public static synchronized Provider.Service getSecureRandomService() {
185        getCacheVersion();  // used for side effect of updating cache if needed
186        return cachedSecureRandomService;
187    }
188
189    /**
190     * In addition to being used here when the list of providers
191     * changes, this method is also used by the Provider
192     * implementation to indicate that a provides list of services has
193     * changed.
194     */
195    public static synchronized void setNeedRefresh() {
196        needRefresh = true;
197    }
198
199    /**
200     * Returns the current cache version. This has the possible side
201     * effect of updating the cache if needed.
202     */
203    public static synchronized int getCacheVersion() {
204        if (needRefresh) {
205            cacheVersion++;
206            synchronized (services) {
207                services.clear();
208            }
209            cachedSecureRandomService = null;
210            for (Provider p : providers) {
211                initServiceInfo(p);
212            }
213            needRefresh = false;
214        }
215        return cacheVersion;
216    }
217}
218