001/**
002 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003 *
004 * Copyright (c) 2005 Sun Microsystems Inc. All Rights Reserved
005 *
006 * The contents of this file are subject to the terms
007 * of the Common Development and Distribution License
008 * (the License). You may not use this file except in
009 * compliance with the License.
010 *
011 * You can obtain a copy of the License at
012 * https://opensso.dev.java.net/public/CDDLv1.0.html or
013 * opensso/legal/CDDLv1.0.txt
014 * See the License for the specific language governing
015 * permission and limitations under the License.
016 *
017 * When distributing Covered Code, include this CDDL
018 * Header Notice in each file and include the License file
019 * at opensso/legal/CDDLv1.0.txt.
020 * If applicable, add the following below the CDDL Header,
021 * with the fields enclosed by brackets [] replaced by
022 * your own identifying information:
023 * "Portions Copyrighted [year] [name of copyright owner]"
024 *
025 * $Id: SystemProperties.java,v 1.21 2009/10/12 17:55:06 alanchu Exp $
026 *
027 */
028
029/*
030 * Portions Copyrighted 2010-2013 ForgeRock, Inc.
031 */
032package com.iplanet.am.util;
033
034import com.iplanet.sso.SSOToken;
035import com.sun.identity.common.configuration.ServerConfiguration;
036import com.sun.identity.security.AdminTokenAction;
037import com.sun.identity.common.AttributeStruct;
038import com.sun.identity.common.PropertiesFinder;
039import com.sun.identity.shared.Constants;
040import com.sun.identity.sm.SMSEntry;
041import org.forgerock.openam.cts.api.CoreTokenConstants;
042
043import java.io.ByteArrayOutputStream;
044import java.io.FileInputStream;
045import java.io.IOException;
046import java.io.PrintStream;
047import java.net.InetAddress;
048import java.security.AccessController;
049import java.util.Enumeration;
050import java.util.HashMap;
051import java.util.HashSet;
052import java.util.Iterator;
053import java.util.Map;
054import java.util.MissingResourceException;
055import java.util.Properties;
056import java.util.ResourceBundle;
057import java.util.Set;
058import java.util.concurrent.locks.ReentrantReadWriteLock;
059
060/**
061 * This class provides functionality that allows single-point-of-access to all
062 * related system properties.
063 * <p>
064 * The system properties can be set in couple of ways: programmatically by
065 * calling the <code>initializeProperties</code> method, or can be statically
066 * loaded at startup from a file named: 
067 * <code>AMConfig.[class,properties]</code>.
068 * Setting the properties through the API takes precedence and will replace the
069 * properties loaded via file. For statically loading the properties via a file,
070 * this class tries to first find a class, <code>AMConfig.class</code>, and
071 * then a file, <code>AMConfig.properties</code> in the CLASSPATH accessible
072 * to this code. The <code>AMConfig.class</code> takes precedence over the
073 * flat file <code>AMConfig.properties</code>.
074 * <p>
075 * If multiple servers are running, each may have their own configuration file.
076 * The naming convention for such scenarios is
077 * <code>AMConfig-&lt;serverName></code>.
078 * @supported.all.api
079 */
080public class SystemProperties {
081    private static String instanceName;
082    private static ReentrantReadWriteLock rwLock = new
083        ReentrantReadWriteLock();
084    private static Map attributeMap = new HashMap();
085    private static boolean sitemonitorDisabled = false;
086    private final static String TRUE = "true";
087    
088    static {
089        initAttributeMapping();
090    }
091    
092    private static void initAttributeMapping() {
093        try {
094        ResourceBundle rb = ResourceBundle.getBundle("serverAttributeMap");
095        for (Enumeration e = rb.getKeys(); e.hasMoreElements(); ) {
096            String propertyName = (String)e.nextElement();
097            attributeMap.put(propertyName, new AttributeStruct(
098                rb.getString(propertyName)));
099        }
100        } catch(java.util.MissingResourceException mse) {
101            // No Resource Bundle Found, Continue.
102            // Could be in Test Mode.
103        }
104    }
105    
106
107    private static Properties props;
108
109    private static long lastModified;
110
111    private static String initError;
112
113    private static String initSecondaryError;
114
115    private static final String SERVER_NAME_PROPERTY = "server.name";
116
117    private static final String CONFIG_NAME_PROPERTY = "amconfig";
118
119    private static final String AMCONFIG_FILE_NAME = "AMConfig";
120
121    /**
122     * Runtime flag to be set, in order to override the path of the
123     * configuration file.
124     */
125    public static final String CONFIG_PATH = "com.iplanet.services.configpath";
126
127    /**
128     * Default name of the configuration file.
129     */
130    public static final String CONFIG_FILE_NAME = "serverconfig.xml";
131
132    /**
133     * New configuration file extension
134     */
135    public static final String PROPERTIES = "properties";
136
137    public static final String NEWCONFDIR = "NEW_CONF_DIR";
138
139    private static Map mapTagswap = new HashMap();
140    private static Map tagswapValues;
141
142    /**
143     * Initialization to load the properties file for config information before
144     * anything else starts.
145     */
146    static {
147        mapTagswap.put("%SERVER_PORT%",  Constants.AM_SERVER_PORT);
148        mapTagswap.put("%SERVER_URI%",   Constants.AM_SERVICES_DEPLOYMENT_DESCRIPTOR);
149        mapTagswap.put("%SERVER_HOST%",  Constants.AM_SERVER_HOST);
150        mapTagswap.put("%SERVER_PROTO%", Constants.AM_SERVER_PROTOCOL);
151        mapTagswap.put("%BASE_DIR%", CONFIG_PATH);
152        mapTagswap.put("%SESSION_ROOT_SUFFIX%",
153                CoreTokenConstants.SYS_PROPERTY_SESSION_HA_REPOSITORY_ROOT_SUFFIX);
154        mapTagswap.put("%SESSION_STORE_TYPE%",
155                CoreTokenConstants.SYS_PROPERTY_SESSION_HA_REPOSITORY_TYPE);
156
157        try {
158            // Initialize properties
159            props = new Properties();
160
161            // Load properties from file
162            String serverName = System.getProperty(SERVER_NAME_PROPERTY);
163            String configName = System.getProperty(CONFIG_NAME_PROPERTY,
164                    AMCONFIG_FILE_NAME);
165            String fname = null;
166            FileInputStream fis = null;
167            if (serverName != null) {
168                serverName = serverName.replace('.', '_');
169                fname = configName + "-" + serverName;
170            } else {
171                fname = configName;
172            }
173            initializeProperties(fname);
174
175            // Get the location of the new configuration file in case
176            // of single war deployment
177            try {
178                String newConfigFileLoc = props
179                        .getProperty(Constants.AM_NEW_CONFIGFILE_PATH);
180                if ((newConfigFileLoc != null) &&
181                    (newConfigFileLoc.length() > 0) && 
182                    !newConfigFileLoc.equals(NEWCONFDIR)
183                ) {
184                    String hostName = InetAddress.getLocalHost().getHostName()
185                            .toLowerCase();
186                    String serverURI = props.getProperty(
187                            Constants.AM_SERVICES_DEPLOYMENT_DESCRIPTOR);
188                    serverURI = serverURI.replace('/', '_').toLowerCase();
189                    StringBuilder fileName = new StringBuilder();
190                    fileName.append(newConfigFileLoc).append("/").append(
191                            AMCONFIG_FILE_NAME).append(serverURI).append(
192                            hostName).append(
193                            props.getProperty(Constants.AM_SERVER_PORT))
194                            .append(".").append(PROPERTIES);
195                    Properties modProp = new Properties();
196                    try {
197                        fis = new FileInputStream(fileName.toString());
198                        modProp.load(fis);
199                        props.putAll(modProp);
200                    } catch (IOException ioe) {
201                        StringBuilder fileNameOrig = new StringBuilder();
202                        fileNameOrig.append(newConfigFileLoc).append("/")
203                                .append(AMCONFIG_FILE_NAME).append(".").append(
204                                        PROPERTIES);
205                        try {
206                            fis = new FileInputStream(fileNameOrig.toString());
207                            modProp.load(fis);
208                            props.putAll(modProp);
209                        } catch (IOException ioexp) {
210                            saveException(ioexp);
211                        }
212                    } finally {
213                        if (fis != null) {
214                            fis.close();
215                        }
216                    }
217                }
218            } catch (Exception ex) {
219                saveException(ex);
220            }
221        } catch (MissingResourceException e) {
222            // Can't print the message to debug due to dependency
223            // Save it as a String and provide when requested.
224            ByteArrayOutputStream baos = new ByteArrayOutputStream();
225            e.printStackTrace(new PrintStream(baos));
226            initError = baos.toString();
227            try {
228                baos.close();
229            } catch (IOException ioe) {
230                // Should not happend, ignore the exception
231            }
232        }
233        sitemonitorDisabled = Boolean.valueOf(getProp(
234               Constants.SITEMONITOR_DISABLED, "false")).booleanValue();
235    }
236
237    /**
238     * Helper function to handle associated exceptions during initialization of
239     * properties using external properties file in a single war deployment.
240     */
241    static void saveException(Exception ex) {
242        // Save it as a String and provide when requested.
243        ByteArrayOutputStream baos = new ByteArrayOutputStream();
244        ex.printStackTrace(new PrintStream(baos));
245        initSecondaryError = baos.toString();
246        try {
247            baos.close();
248        } catch (IOException ioe) {
249            // Should not happend, ignore the exception
250        }
251    }
252
253    /**
254     * This method lets you query for a system property whose value is same as
255     * <code>String</code> key. The method first tries to read the property
256     * from java.lang.System followed by a lookup in the config file.
257     * 
258     * @param key
259     *            type <code>String</code>, the key whose value one is
260     *            looking for.
261     * @return the value if the key exists; otherwise returns <code>null</code>
262     */
263    public static String get(String key) {
264        rwLock.readLock().lock();
265
266        try {
267            String answer = null;
268
269            // look up values in SMS services only if in server mode.
270            if (isServerMode() || sitemonitorDisabled) {
271                AttributeStruct ast = (AttributeStruct) attributeMap.get(key);
272                if (ast != null) {
273                    answer = PropertiesFinder.getProperty(key, ast);
274                }
275            }
276
277            if (answer == null) {
278                answer = getProp(key);
279
280                if ((answer != null) && (tagswapValues != null)) {
281                    Set set = new HashSet();
282                    set.addAll(tagswapValues.keySet());
283
284                    for (Iterator i = set.iterator(); i.hasNext();) {
285                        String k = (String) i.next();
286                        String val = (String) tagswapValues.get(k);
287
288                        if (k.equals("%SERVER_URI%")) {
289                            if ((val != null) && (val.length() > 0)) {
290                                if (val.charAt(0) == '/') {
291                                    answer = answer.replaceAll("/%SERVER_URI%",
292                                        val);
293                                    String lessSlash = val.substring(1);
294                                    answer = answer.replaceAll("%SERVER_URI%",
295                                        lessSlash);
296                                } else {
297                                    answer = answer.replaceAll(k, val);
298                                }
299                            }
300                        } else {
301                            answer = answer.replaceAll(k, val);
302                        }
303                    }
304
305                    if (answer.indexOf("%ROOT_SUFFIX%") != -1) {
306                        answer = answer.replaceAll("%ROOT_SUFFIX%",
307                            SMSEntry.getAMSdkBaseDN());
308                    }
309                }
310            }
311
312
313            return (answer);
314        } finally {
315            rwLock.readLock().unlock();
316        }
317    }
318
319    private static String getProp(String key, String def) {
320        String value = getProp(key);
321        return ((value == null) ? def : value);
322    }
323
324    private static String getProp(String key) {
325        String answer = System.getProperty(key);
326        if (answer == null) {
327            answer = props.getProperty(key);
328        }
329        return answer;
330    }
331    
332    /**
333     * This method lets you query for a system property whose value is same as
334     * <code>String</code> key.
335     * 
336     * @param key the key whose value one is looking for.
337     * @param def the default value if the key does not exist.
338     * @return the value if the key exists; otherwise returns default value.
339     */
340    public static String get(String key, String def) {
341        String value = get(key);
342        return ((value == null) ? def : value);
343    }
344
345    /**
346     * Returns the property value as a boolean
347     *
348     * @param key the key whose value one is looking for.
349     * @return the boolean value if the key exists; otherwise returns false
350     */
351    public static boolean getAsBoolean(String key) {
352        String value = get(key);
353
354        if (value == null)
355            return false;
356
357        return (value.equalsIgnoreCase(TRUE) ? true : false);
358    }
359
360    /**
361     * Returns the property value as a boolean
362     *
363     * @param key
364     * @param defaultValue value if key is not found.
365     * @return the boolean value if the key exists; otherwise the default value
366     */
367    public static boolean getAsBoolean(String key, boolean defaultValue) {
368        String value = get(key);
369
370        if (value == null)
371            { return defaultValue; }
372
373        return (value.equalsIgnoreCase(TRUE) ? true : false);
374    }
375    
376    /**
377     * Returns all the properties defined and their values.
378     * 
379     * @return Properties object with all the key value pairs.
380     */
381    public static Properties getProperties() {
382        rwLock.readLock().lock();
383        try {
384            Properties properties = new Properties();
385            properties.putAll(props);
386            return properties;
387        } finally {
388            rwLock.readLock().unlock();
389        }
390    }
391    
392    /**
393     * This method lets you get all the properties defined and their values. The
394     * method first tries to load the properties from java.lang.System followed
395     * by a lookup in the config file.
396     * 
397     * @return Properties object with all the key value pairs.
398     * 
399     */
400    public static Properties getAll() {
401        rwLock.readLock().lock();
402
403        try {
404            Properties properties = new Properties();
405            properties.putAll(props);
406            // Iterate over the System Properties & add them in result obj
407            Iterator it = System.getProperties().entrySet().iterator();
408            while (it.hasNext()) {
409                Map.Entry entry = (Map.Entry) it.next();
410                String key = (String) entry.getKey();
411                String val = (String) entry.getValue();
412                if ((key != null) && (key.length() > 0)) {
413                    properties.setProperty(key, val);
414                }
415            }
416            return properties;
417        } finally {
418            rwLock.readLock().unlock();
419        }
420    }
421
422    /**
423     * This method lets you query for all the platform properties defined and
424     * their values. Returns a Properties object with all the key value pairs.
425     * 
426     * @deprecated use <code>getAll()</code>
427     * 
428     * @return the platform properties
429     */
430    public static Properties getPlatform() {
431        return getAll();
432    }
433
434    private static void updateTagswapMap(Properties properties) {
435        tagswapValues = new HashMap();
436        for (Iterator i = mapTagswap.keySet().iterator(); i.hasNext(); ) {
437            String key = (String)i.next();
438            String rgKey = (String)mapTagswap.get(key);
439            String val = System.getProperty(rgKey);
440            if (val == null) {
441                val = (String)properties.get(rgKey);
442            }
443            tagswapValues.put(key, val);
444        }
445    }
446
447    /**
448     * Initializes properties bundle from the <code>file<code> 
449     * passed.
450     *
451     * @param file type <code>String</code>, file name for the resource bundle
452     * @exception MissingResourceException
453     */
454    public static void initializeProperties(String file)
455        throws MissingResourceException {
456        rwLock.writeLock().lock();
457        try {
458            ResourceBundle bundle = ResourceBundle.getBundle(file);
459            // Copy the properties to props
460            Enumeration e = bundle.getKeys();
461            Properties newProps = new Properties();
462            newProps.putAll(props);
463            while (e.hasMoreElements()) {
464                String key = (String) e.nextElement();
465                newProps.put(key, bundle.getString(key));
466            }
467            // Reset the last modified time
468            props = newProps;
469            updateTagswapMap(props);
470            lastModified = System.currentTimeMillis();
471        } finally {
472            rwLock.writeLock().unlock();
473        }
474    }
475
476    public static void initializeProperties(Properties properties){
477        initializeProperties(properties, false);
478    }
479    
480    /**
481     * Initializes the properties to be used by OpenSSO. Ideally this
482     * must be called first before any other method is called within OpenSSO
483     * Enterprise. This method provides a programmatic way to set the
484     * properties, and will override similar properties if loaded for a
485     * properties file.
486     * 
487     * @param properties properties for OpenSSO
488     * @param reset <code>true</code> to reset existing properties.
489     */
490    public static void initializeProperties(
491        Properties properties,
492        boolean reset) 
493    {
494        initializeProperties(properties, reset, false);
495    }
496    
497    /**
498     * Initializes the properties to be used by OpenSSO. Ideally this
499     * must be called first before any other method is called within OpenSSO
500     * Enterprise. This method provides a programmatic way to set the
501     * properties, and will override similar properties if loaded for a
502     * properties file.
503     * 
504     * @param properties properties for OpenSSO.
505     * @param reset <code>true</code> to reset existing properties.
506     * @param withDefaults <code>true</code> to include default properties.
507     */
508    public static void initializeProperties(
509        Properties properties,
510        boolean reset,
511        boolean withDefaults) {
512        Properties defaultProp = null;
513        if (withDefaults) {
514            SSOToken appToken = (SSOToken) AccessController.doPrivileged(
515                AdminTokenAction.getInstance());
516            defaultProp = ServerConfiguration.getDefaults(appToken);
517        }
518
519        rwLock.writeLock().lock();
520
521        try {
522            Properties newProps = new Properties();
523            if (defaultProp != null) {
524                newProps.putAll(defaultProp);
525            }
526
527
528            if (!reset) {
529                newProps.putAll(props);
530            }
531
532            newProps.putAll(properties);
533            props = newProps;
534            updateTagswapMap(props);
535            lastModified = System.currentTimeMillis();
536        } finally {
537            rwLock.writeLock().unlock();
538        }
539    }
540
541    /**
542     * Initializes the property to be used by OpenSSO. Ideally this
543     * must be called first before any other method is called within OpenSSO
544     * Enterprise.
545     * This method provides a programmatic way to set a specific property, and
546     * will override similar property if loaded for a properties file.
547     * 
548     * @param propertyName property name.
549     * @param propertyValue property value.
550     */
551    public static void initializeProperties(
552        String propertyName,
553        String propertyValue
554    ) {
555        rwLock.writeLock().lock();
556
557        try {
558            Properties newProps = new Properties();
559            newProps.putAll(props);
560            newProps.put(propertyName, propertyValue);
561            props = newProps;
562            updateTagswapMap(props);
563            lastModified = System.currentTimeMillis();
564        } finally {
565            rwLock.writeLock().unlock();
566        }
567    }
568
569    /**
570     * Returns a counter for last modification. The counter is incremented if
571     * the properties are changed by calling the following method
572     * <code>initializeProperties</code>. This is a convenience methods for
573     * applications to track changes to OpenSSO properties.
574     * 
575     * @return counter of the last modification
576     */
577    public static long lastModified() {
578        return (lastModified);
579    }
580
581    /**
582     * Returns error messages during initialization, else <code>null</code>.
583     * 
584     * @return error messages during initialization
585     */
586    public static String getInitializationError() {
587        return (initError);
588    }
589
590    /**
591     * Returns error messages during initialization using the single war
592     * deployment, else <code>null</code>.
593     * 
594     * @return error messages during initialization of AM as single war
595     */
596    public static String getSecondaryInitializationError() {
597        return (initSecondaryError);
598    }
599    
600    /**
601     * Sets the server instance name of which properties are retrieved
602     * to initialized this object.
603     *
604     * @param name Server instance name.
605     */
606    public static void setServerInstanceName(String name) {
607        instanceName = name;
608    }
609
610    /**
611     * Returns the server instance name of which properties are retrieved
612     * to initialized this object.
613     *
614     * @return Server instance name.
615     */
616    public static String getServerInstanceName() {
617        return instanceName;
618    }
619    
620    /**
621     * Returns <code>true</code> if instance is running in server mode.
622     *
623     * @return <code>true</code> if instance is running in server mode.
624     */
625    public static boolean isServerMode() {
626        // use getProp and not get method to avoid infinite loop
627        return Boolean.valueOf(getProp(
628            Constants.SERVER_MODE, "false")).booleanValue();
629    }
630    
631    /**
632     * Returns the property name to service attribute schema name mapping.
633     *
634     * @return Property name to service attribute schema name mapping.
635     */
636    public static Map getAttributeMap() {
637        rwLock.readLock().lock();
638        try {
639            return attributeMap;
640        } finally {
641            rwLock.readLock().unlock();
642        }
643    }
644}