/* * TeleStax, Open Source Cloud Communications * Copyright 2011-2016, TeleStax Inc. and individual contributors * by the @authors tag. * * This program is free software: you can redistribute it and/or modify * under the terms of the GNU Affero General Public License as * published by the Free Software Foundation; either version 3 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/> * * This file incorporates work covered by the following copyright and * permission notice: * * JBoss, Home of Professional Open Source * Copyright 2007-2011, Red Hat, Inc. and individual contributors * by the @authors tag. See the copyright.txt in the distribution for a * full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jdiameter.api; import static java.security.AccessController.doPrivileged; import java.io.PrintWriter; import java.security.PrivilegedAction; import java.util.Collections; import java.util.Enumeration; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; /** * <P>The basic service for managing a set of Diameter stacks.<br> * * <P>As part of its initialization, the <code>DriverManager</code> class will * attempt to load the stacks classes referenced in the "diameter.drivers" * system property. This allows a user to customize the Diameter Drivers * used by their applications. For example in your * ~/.hotjava/properties file you might specify: * <pre> * <CODE>diameter.drivers=foo.bah.Stack:wombat.diameter.Stack</CODE> * </pre> * * A program can also explicitly load Diameter stacks at any time. For * example, the my.diameter.Stack is loaded with the following statement: * <pre> * <CODE>Class.forName("my.diameter.Stack");</CODE> * </pre> * * <P>When the method <code>getSession</code> is called, * the <code>StackManager</code> will attempt to * locate a suitable stack from amongst those loaded at * initialization and those loaded explicitly using the same classloader * as the current applet or application. * * @author erick.svenson@yahoo.com * @version 1.5.1 Final */ public final class StackManager { private static final Object logSync = new Object(); private static List<StackInfo> stacks = new CopyOnWriteArrayList<StackInfo>(); private static PrintWriter logWriter = null; private static boolean initialized = false; static void initialize() { if (initialized) { return; } initialized = true; loadInitialStacks(); println("Diameter StackManager initialized"); } private StackManager() { } /** * Retrieves the log writer. * * The <code>getLogWriter</code> and <code>setLogWriter</code> * methods should be used instead * of the <code>get/setlogStream</code> methods, which are deprecated. * @return a <code>java.io.PrintWriter</code> object * @see #setLogWriter */ public static PrintWriter getLogWriter() { synchronized (logSync) { return logWriter; } } /** * Sets the logging/tracing <code>PrintWriter</code> object * that is used by the <code>StackManager</code> and all drivers. * <P> * There is a minor versioning problem created by the introduction * of the method <code>setLogWriter</code>. The * method <code>setLogWriter</code> cannot create a <code>PrintStream</code> object * that will be returned by <code>getLogStream</code> * * @param out the new logging/tracing <code>PrintStream</code> object; * <code>null</code> to disable logging and tracing * @throws SecurityException * if a security manager exists and its * <code>checkPermission</code> method denies * setting the log writer */ public static void setLogWriter(java.io.PrintWriter out) { synchronized (logSync) { logWriter = out; } } /** * Attempts to locate a stack. * The <code>StackManager</code> attempts to select an appropriate stack from * the set of registered Diameter stacks. * @param className class name of stack * @return stack instance * @exception InternalException if a manager has internal error */ public static synchronized Stack getStack(String className) throws InternalException { println(new StringBuilder().append("StackManager.getStack(\"").append(className).append("\")").toString()); if (!initialized) { initialize(); } // Gets the classloader of the code that called this method, may be null. ClassLoader callerCL = ClassLoader.getSystemClassLoader(); // Walk through the loaded stacks attempting to locate someone who understands the given URL. for (StackInfo di : stacks) { // If the caller does not have permission to load the stack then skip it. if (getCallerClass(callerCL, di.stackClassName) != di.stackClass) { println(new StringBuilder().append(" skipping: ").append(di).toString()); continue; } println(new StringBuilder().append(" trying ").append(di).toString()); if (di.stackClassName.equals(className)) { // Success! println("geStack returning " + di); return (di.stack); } } println("getStack: no suitable stack"); throw new InternalException("No suitable stack"); } /** * Registers the given stack with the <code>ScoketManager</code>. * A newly-loaded stack class should call * the method <code>registerStack</code> to make itself * known to the <code>StackManager</code>. * * @param stack the new Diameter Stack that is to be registered with the * <code>StackManager</code> * @exception InternalException if a manager has internal error */ public static synchronized void registerStack(Stack stack) throws InternalException { if (!initialized) { initialize(); } StackInfo stackInfo = new StackInfo(); stackInfo.stack = stack; stackInfo.stackClass = stack.getClass(); stackInfo.stackClassName = stackInfo.stackClass.getName(); stacks.add(stackInfo); println(new StringBuilder().append("registerStack: ").append(stackInfo).toString()); } /** * Drops a driver from the <code>DiameterManager</code>'s list. Applets can only * deregister stacks from their own classloaders. * * @param stack the Diameter stack to drop * @exception InternalException if a manager has internal error */ public static synchronized void deregisterStack(Stack stack) throws InternalException { // Gets the classloader of the code that called this method, may be null. ClassLoader callerCL = ClassLoader.getSystemClassLoader(); println(new StringBuilder().append("StackManager.deregisterStack: ").append(stack).toString()); // Walk through the loaded stacks. int i; StackInfo stackInfo = null; for (i = 0; i < stacks.size(); i++) { stackInfo = stacks.get(i); if (stackInfo.stack == stack) { break; } } // If we can't find the stack just return. if (i >= stacks.size()) { println(" couldn't find stack to unload"); return; } // If the caller does not have permission to load the stack then throw a security exception. if (stackInfo == null || getCallerClass(callerCL, stackInfo.stackClassName) != stackInfo.stackClass) { throw new SecurityException(); } // Remove the stack. Other entries in stacks get shuffled down. stacks.remove(i); } /** * Retrieves an Enumeration with all of the currently loaded Diameter stacks * to which the current caller has access. * * <P><B>Note:</B> The classname of a stack can be found using * <CODE>d.getClass().getName()</CODE> * * @return the list of Diameter stacks loaded by the caller's class loader */ public static synchronized Enumeration<Stack> getStacks() { List<Stack> result = new CopyOnWriteArrayList<Stack>(); if (!initialized) { initialize(); } // Gets the classloader of the code that called this method, may be null. ClassLoader callerCL = ClassLoader.getSystemClassLoader(); // Walk through the loaded stacks. for (StackInfo di : stacks) { // If the caller does not have permission to load the stack then skip it. if (getCallerClass(callerCL, di.stackClassName) != di.stackClass) { println(new StringBuilder().append(" skipping: ").append(di).toString()); continue; } result.add(di.stack); } return Collections.enumeration(result); } public static void println(String message) { synchronized (logSync) { if (logWriter != null) { logWriter.println(message); // automatic flushing is never enabled, so we must do it ourselves logWriter.flush(); } } } private static Class getCallerClass(ClassLoader callerClassLoader, String stackClassName) { Class callerC; try { callerC = Class.forName(stackClassName, true, callerClassLoader); } catch (Exception ex) { callerC = null; } return callerC; } private static void loadInitialStacks() { String stacks; try { stacks = doPrivileged( new GetPropertyAction("diameter.stacks") ); } catch (Exception ex) { stacks = null; } println(new StringBuilder().append("StackManager.initialize: diameter.stacks = ").append(stacks).toString()); if (stacks == null) { return; } while (stacks.length() != 0) { int x = stacks.indexOf(':'); String stack; if (x < 0) { stack = stacks; stacks = ""; } else { stack = stacks.substring(0, x); stacks = stacks.substring(x + 1); } if (stack.length() == 0) { continue; } try { println(new StringBuilder().append("StackManager.Initialize: loading ").append(stack).toString()); Class.forName(stack, true, ClassLoader.getSystemClassLoader()); } catch (Exception ex) { println(new StringBuilder().append("StackManager.Initialize: load failed: ").append(ex).toString()); } } } } class GetPropertyAction implements PrivilegedAction<String> { private String theProp; private String defaultVal; GetPropertyAction(String s) { theProp = s; } GetPropertyAction(String s, String s1) { theProp = s; defaultVal = s1; } @Override public String run() { String s = System.getProperty(theProp); return s != null ? s : defaultVal; } } class StackInfo { Stack stack; Class stackClass; String stackClassName; @Override public String toString() { return (new StringBuilder().append("stack[className=").append(stackClassName).append(","). append(stack).append("]").toString()); } }