001/**
002 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003 *
004 * Copyright (c) 2006 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: Stats.java,v 1.5 2008/08/08 00:40:59 ww203982 Exp $
026 *
027 */
028
029package com.sun.identity.shared.stats;
030
031import com.sun.identity.common.SystemTimer;
032import com.sun.identity.shared.Constants;
033import com.sun.identity.shared.configuration.SystemPropertiesManager;
034import org.forgerock.util.thread.listener.ShutdownListener;
035import org.forgerock.util.thread.listener.ShutdownManager;
036
037import java.io.BufferedWriter;
038import java.io.File;
039import java.io.FileOutputStream;
040import java.io.IOException;
041import java.io.OutputStreamWriter;
042import java.io.PrintWriter;
043import java.io.StringWriter;
044import java.text.DateFormat;
045import java.text.SimpleDateFormat;
046import java.util.Date;
047import java.util.HashMap;
048import java.util.Map;
049import java.util.MissingResourceException;
050import java.util.ResourceBundle;
051
052// NOTE: Since JVM specs guarantee atomic access/updates to int variables
053// (actually all variables except double and long), the design consciously
054// avoids synchronized methods, particularly for message(). This is done to
055// reduce the performance overhead of synchronized message() when statistics
056// is disabled. This does not have serious side-effects other than an occasional
057// invocation of message() missing concurrent update of 'statsState'.
058
059/*******************************************************************************
060 * <p>
061 * Allows a uniform interface to statistics information in a uniform format.
062 * <code>Stats</code> supports different states of filing stats information:
063 * <code>OFF</code>, <code>FILE</code> and <code>CONSOLE</code>. <BR>
064 * <li> <code>OFF</code> statistics is turned off.
065 * <li> <code>FILE</code> statistics information is written to a file
066 * <li> <code>CONSOLE</code> statistics information is written on console
067 * <p>
068 * Stats service uses the property file, <code>AMConfig.properties</code>, to
069 * set the default stats level and the output directory where the stats files
070 * will be placed. The properties file is located (using
071 * {@link java.util.ResourceBundle} semantics) from one of the directories in
072 * the CLASSPATH.
073 * <p>
074 * The following keys are used to configure the Stats service. Possible values
075 * for the key 'state' are: off | off | file | console The key 'directory'
076 * specifies the output directory where the stats files will be created.
077 * 
078 * <blockquote>
079 * 
080 * <pre>
081 *  com.iplanet.services.stats.state
082 *  com.iplanet.services.stats.directory
083 * </pre>
084 * 
085 * </blockquote>
086 * 
087 * If there is an error reading or loading the properties, all the information
088 * is redirected to <code>System.out</code>
089 * 
090 * If these properties are changed, the server must be restarted for the changes
091 * to take effect.
092 * 
093 * <p>
094 * <b>NOTE:</b> Printing Statistics is an IO intensive operation and may hurt
095 * application performance when abused. Particularly, note that Java evaluates
096 * the arguments to <code>message()</code> and <code>warning()</code> even
097 * when statistics is turned off. It is recommended that the stats state be
098 * checked before invoking any <code>message()</code> or
099 * <code>warning()</code> methods to avoid unnecessary argument evaluation and
100 * to maximize application performance.
101 * </p>
102 * @supported.all.api
103 */
104public class Stats implements ShutdownListener {
105    /** flags the disabled stats state. */
106    public static final int OFF = 0;
107
108    /**
109     * Flags the state where all the statistic information is printed to a file
110     */
111    public static final int FILE = 1;
112
113    /**
114     * Flags the state where printing to a file is disabled. All printing is
115     * done on System.out.
116     */
117    public static final int CONSOLE = 2;
118
119    /**
120     * statsMap is a container of all active Stats objects. Log file name is the
121     * key and Stats is the value of this map.
122     */
123    private static Map statsMap = new HashMap();
124
125    /** serviceInitialized indicates if the service is already initialized. */
126    private static boolean serviceInitialized = false;
127
128    private static DateFormat dateFormat;
129
130    /**
131     * The default stats level for the entire service and the level that is used
132     * when a Stats object is first created and before its level is modified.
133     * Don't initialize the following two variables in a static
134     * initializer/block because other components may initialize Stats in their
135     * static initializers (as opposed to constructors or methods). The order of
136     * execution of static blocks is not guaranteed by JVM. So if we set the
137     * following two static variables to some default values here, then it will
138     * interfere with the execution of {@link #initService}.
139     */
140    private static String defaultStatsLevel;
141
142    private static String outputDirectory;
143
144    private final String statsName;
145
146    private PrintWriter statsFile = null;
147
148    private int statsState;
149
150    private static StatsRunner statsListeners = new StatsRunner();
151
152    /**
153     * Initializes the Stats service so that Stats objects can be created. At
154     * startup (when the first Stats object is ever created in a JVM), this
155     * method reads <code>AMConfig.properties</code> file (using
156     * {@link java.util.ResourceBundle} semantics) from one of the directories
157     * in the <code>CLASSPATH</code>, and loads the properties. It creates
158     * the stats directory. If all the directories in output dir don't have
159     * adequate permissions then the creation of the stats directory will fail
160     * and all the stats files will be located in the "current working
161     * directory" of the process running stats code. If there is an error
162     * reading or loading the properties, it will set the stats service to
163     * redirect all stats information to <code>System.out</code>
164     */
165    private static void initService() {
166        /*
167         * We will use the double-checked locking pattern. Rarely entered block.
168         * Push synchronization inside it. This is the first check.
169         */
170        if (!serviceInitialized) {
171            /*
172             * Only 1 thread at a time gets past the next point. Rarely executed
173             * synchronization statement and hence synchronization penalty is
174             * not paid every time this method is called.
175             */
176            synchronized (Stats.class) {
177                /*
178                 * If a second thread was waiting to get here, it will now find
179                 * that the instance has already been initialized, and it will
180                 * not re-initialize the instance variable. This is the (second)
181                 * double-check.
182                 */
183                if (!serviceInitialized) {
184                    dateFormat = new SimpleDateFormat(
185                            "MM/dd/yyyy hh:mm:ss:SSS a zzz");
186                    try {
187                        defaultStatsLevel = SystemPropertiesManager.get(
188                            Constants.SERVICES_STATS_STATE);
189                        outputDirectory = SystemPropertiesManager.get(
190                            Constants.SERVICES_STATS_DIRECTORY);
191                        ResourceBundle bundle = 
192                            com.sun.identity.shared.locale.Locale
193                                .getInstallResourceBundle("amUtilMsgs");
194                        if (outputDirectory != null) {
195                            File createDir = new File(outputDirectory);
196                            if (!createDir.exists()) {
197                                if (!createDir.mkdirs()) {
198                                    System.err.println(bundle.getString(
199                                           "com.iplanet.services.stats.nodir"));
200                                }
201                            }
202
203                        }
204                    } catch (MissingResourceException e) {
205                        System.err.println(e.getMessage());
206                        e.printStackTrace();
207
208                        // If there is any error in getting the level or
209                        // outputDirectory, defaultStatsLevel will be set to
210                        // ON so that output will go to
211                        // System.out
212
213                        defaultStatsLevel = "console";
214                        outputDirectory = null;
215                    } catch (SecurityException se) {
216                        System.err.println(se.getMessage());
217                    }
218                    
219                    SystemTimer.getTimer().schedule(statsListeners, new Date(((
220                        System.currentTimeMillis() +
221                        statsListeners.getRunPeriod()) / 1000) * 1000));
222
223                    serviceInitialized = true;
224                }
225            }
226        }
227    }
228
229    /**
230     * This constructor takes as an argument the name of the stats file. The
231     * stats file is neither created nor opened until the first time
232     * <code>message()</code>, <code>warning()</code> or
233     * <code>error()</code> is invoked and the stats state is neither
234     * <code>OFF</code> nor <code>ON</code>.
235     * <p>
236     * <b>NOTE:</b>The recommended and preferred method to create Stats objects
237     * is <code>getInstance(String)</code>. This constructor may be
238     * deprecated in future.
239     * </p>
240     * 
241     * @param statsName name of the stats file to create or use
242     */
243    private Stats(String statsName) {
244        // Initialize the stats service the first time a Stats object is
245        // created.
246
247        initService();
248
249        // Now initialize this instance itself
250
251        this.statsName = statsName;
252        setStats(defaultStatsLevel);
253
254        synchronized (statsMap) {
255            // explicitly ignore any duplicate instances.
256            statsMap.put(statsName, this);
257        }
258        ShutdownManager shutdownMan = com.sun.identity.common.ShutdownManager.getInstance();
259        shutdownMan.addShutdownListener(this);
260
261    }
262
263    /**
264     * Returns an existing instance of Stats for the specified stats file or a
265     * new one if no such instance already exists. If a Stats object has to be
266     * created, its level is set to the level defined in the
267     * <code>AMConfig.properties</code> file. The level can be changed later
268     * by using {@link #setStats(int)} or {@link #setStats(String)}
269     * 
270     * @param statsName
271     *            name of statistic instance.
272     * @return an existing instance of Stats for the specified stats file.
273     */
274    public static synchronized Stats getInstance(String statsName) {
275        Stats statsObj = (Stats) statsMap.get(statsName);
276        if (statsObj == null) {
277            statsObj = new Stats(statsName);
278        }
279        return statsObj;
280    }
281
282    /**
283     * Checks if statistics is enabled.
284     * 
285     * <p>
286     * <b>NOTE:</b> It is recommended that <code>isEnabled()</code> be used
287     * instead of <code>isEnabled()</code> as the former is more intuitive.
288     * 
289     * @return <code>true</code> if statistics is enabled <code>false</code>
290     *         if statistics is disabled
291     * 
292     */
293    public boolean isEnabled() {
294        return (statsState > Stats.OFF);
295    }
296
297    /**
298     * Returns one of the 3 possible values.
299     * <ul>
300     * <li><code>Stats.OFF</code>
301     * <li><code>Stats.FILE</code>
302     * <li><code>Stats.CONSOLE</code>
303     * </ul>
304     * 
305     * @return state of Stats.
306     */
307    public int getState() {
308        return statsState;
309    }
310
311    /**
312     * Prints messages only when the stats state is either
313     * <code>Stats.FILE</code> or <code>Stats.CONSOLE</code>.
314     * 
315     * <p>
316     * <b>NOTE:</b> Printing Statistics is an IO intensive operation and may
317     * hurt application performance when abused. Particularly, note that Java
318     * evaluates arguments to <code>message()</code> even when statistics is
319     * turned off. So when the argument to this method involves the String
320     * concatenation operator '+' or any other method invocation,
321     * <code>isEnabled</code> <b>MUST</b> be used. It is recommended that the
322     * stats state be checked by invoking <code>isEnabled()</code> before
323     * invoking any <code>message()</code> methods to avoid unnecessary
324     * argument evaluation and maximize application performance.
325     * </p>
326     * 
327     * @param msg
328     *            message to be recorded.
329     */
330    public void record(String msg) {
331        if (statsState > Stats.OFF) {
332            formatAndWrite(null, msg);
333        }
334    }
335
336    private void formatAndWrite(String prefix, String msg) {
337        if (statsState == Stats.CONSOLE) {
338            if (msg != null) {
339                if (prefix == null) {
340                    System.out.println(msg);
341                } else {
342                    System.out.println(prefix + msg);
343                }
344            }
345            return;
346        }
347
348        // The default capacity of StringBuffer in StringWriter is 16, but we
349        // know for sure that the minimum header size is about 35. Hence, to
350        // avoid reallocation allocate at least 160 chars.
351
352        String serverInstance = System.getProperty("server.name");
353        StringWriter swriter = new StringWriter(160);
354        PrintWriter buf = new PrintWriter(swriter, true);
355        synchronized (dateFormat) {
356            buf.write(dateFormat.format(new Date()));
357        }
358        if ((serverInstance != null) && (serverInstance != "")) {
359            buf.write(": ");
360            buf.write("Server Instance: " + serverInstance);
361        }
362        buf.write(": ");
363        buf.write(Thread.currentThread().toString());
364        buf.write("\n");
365        if (prefix != null) {
366            buf.write(prefix);
367        }
368        if (msg != null) {
369            buf.write(msg);
370        }
371        buf.flush();
372
373        write(swriter.toString());
374    }
375
376    /**
377     * Actually writes to the stats file. If it cannot write to the stats file,
378     * it turn off statistics. The first time this method is invoked on a Stats
379     * object, that object's stats file is created/opened in the directory
380     * specified by the
381     * <code>property com.iplanet.services.stats.directory</code> in the
382     * properties file, <code>AMConfig.properties</code>.
383     */
384    private synchronized void write(String msg) {
385        try {
386            // statistics is enabled.
387            // First, see if the statsFile is already open. If not, open it now.
388
389            if (statsFile == null) {
390                // open file in append mode
391                FileOutputStream fos = new FileOutputStream(outputDirectory
392                        + File.separator + statsName, true);
393                statsFile = new PrintWriter(new BufferedWriter(
394                        new OutputStreamWriter(fos, "UTF-8")), true); 
395
396                statsFile.println("*********************************" +
397                        "*********************");
398            }
399
400            statsFile.println(msg);
401        } catch (IOException e) {
402            System.err.println(msg);
403
404            // turn off statistics because statsFile is not accessible
405            statsState = Stats.OFF;
406        }
407    }
408
409    /**
410     * Sets the stats capabilities based on the values of the
411     * <code>statsType</code> argument.
412     * 
413     * @param statsType
414     *            is any one of five possible values:
415     *            <p>
416     *            <code>Stats.OFF</code>
417     *            <p>
418     *            <p>
419     *            <code>Stats.FILE</code>
420     *            <p>
421     *            <p>
422     *            <code>Stats.CONSOLE</code>
423     *            <p>
424     */
425    public void setStats(int statsType) {
426        switch (statsType) {
427        case Stats.OFF:
428        case Stats.FILE:
429        case Stats.CONSOLE:
430            statsState = statsType;
431            break;
432
433        default:
434            // ignore invalid statsType values
435            break;
436        }
437    }
438
439    /**
440     * Sets the <code>stats</code> capabilities based on the values of the
441     * <code>statsType</code> argument.
442     * 
443     * @param statsType
444     *            is any one of the following possible values:
445     *            <p>
446     *            off - statistics is disabled
447     *            </p>
448     *            <p>
449     *            file - statistics are written to the stats file
450     *            <code>System.out</code>
451     *            </p>
452     *            <p>
453     *            console - statistics are written to the stats to the console
454     */
455    public void setStats(String statsType) {
456        if (statsType == null) {
457            return;
458        } else if (statsType.equalsIgnoreCase("console")) {
459            statsState = Stats.CONSOLE;
460        } else if (statsType.equalsIgnoreCase("file")) {
461            statsState = Stats.FILE;
462        } else if (statsType.equalsIgnoreCase("off")) {
463            statsState = Stats.OFF;
464        } else if (statsType.equals("*")) {
465            statsState = Stats.CONSOLE;
466        } else {
467            if (statsType.endsWith("*")) {
468                statsType = statsType.substring(0, statsType.length() - 1);
469            }
470            if (statsName.startsWith(statsType)) {
471                statsState = Stats.CONSOLE;
472            }
473        }
474    }
475
476    /**
477     * Destroys the stats object, closes the stats file and releases any system
478     * resources. Note that the stats file will remain open until
479     * <code>destroy()</code> is invoked. To conserve file resources, you
480     * should invoke <code>destroy()</code> explicitly rather than wait for
481     * the garbage collector to clean up.
482     * 
483     * <p>
484     * If this object is accessed after <code>destroy()</code> has been
485     * invoked, the results are undefined.
486     * </p>
487     */
488    public void destroy() {
489        finalize();
490    }
491    
492    public void shutdown() {
493        finalize();
494    }
495
496    /** Flushes and then closes the stats file. */
497    protected void finalize() {
498        synchronized (statsMap) {
499            statsMap.remove(statsName);
500        }
501
502        synchronized (this) {
503            if (statsFile == null) {
504                return;
505            }
506
507            statsState = Stats.OFF;
508            statsFile.flush();
509            statsFile.close();
510            statsFile = null;
511        }
512    }
513
514    public void addStatsListener(StatsListener listener) {
515        statsListeners.addElement(listener);
516    }
517}




























































Copyright © 2010-2017, ForgeRock All Rights Reserved.