001    /*
002    // $Id: //open/mondrian/src/main/mondrian/olap/fun/GlobalFunTable.java#9 $
003    // This software is subject to the terms of the Common Public License
004    // Agreement, available at the following URL:
005    // http://www.opensource.org/licenses/cpl.html.
006    // Copyright (C) 2006-2008 Julian Hyde and others
007    // All Rights Reserved.
008    // You must accept the terms of that agreement to use this software.
009    */
010    package mondrian.olap.fun;
011    
012    import java.io.BufferedReader;
013    import java.io.IOException;
014    import java.io.InputStreamReader;
015    import java.net.URL;
016    import java.util.ArrayList;
017    import java.util.Enumeration;
018    import java.util.List;
019    import java.util.Set;
020    import java.util.Collection;
021    import java.util.HashSet;
022    
023    import mondrian.olap.FunTable;
024    import mondrian.olap.Syntax;
025    import mondrian.olap.Util;
026    import mondrian.olap.type.Type;
027    import mondrian.resource.MondrianResource;
028    import mondrian.spi.UserDefinedFunction;
029    
030    import org.apache.log4j.Logger;
031    
032    /**
033     * Global function table contains builtin functions and global user-defined functions.
034     *
035     * @author Gang Chen
036     * @version $Id: //open/mondrian/src/main/mondrian/olap/fun/GlobalFunTable.java#9 $
037     */
038    public class GlobalFunTable extends FunTableImpl {
039    
040        private static Logger logger = Logger.getLogger(GlobalFunTable.class);
041    
042        private static GlobalFunTable instance = new GlobalFunTable();
043    
044        public static GlobalFunTable instance() {
045            return instance;
046        }
047    
048        private GlobalFunTable() {
049            super();
050            init();
051        }
052    
053        protected void defineFunctions() {
054            final FunTable builtinFunTable = BuiltinFunTable.instance();
055            final List<String> reservedWords = builtinFunTable.getReservedWords();
056            for (String reservedWord : reservedWords) {
057                defineReserved(reservedWord);
058            }
059            final List<Resolver> resolvers = builtinFunTable.getResolvers();
060            for (Resolver resolver : resolvers) {
061                define(resolver);
062            }
063    
064            for (String className : lookupUdfImplClasses()) {
065                defineUdf(className);
066            }
067        }
068    
069    
070        private Collection<String> lookupUdfImplClasses() {
071            ClassLoader cl = this.getClass().getClassLoader();
072            List<URL> serviceUrls = new ArrayList<URL>();
073            try {
074                Enumeration<URL> serviceEnum = cl.getResources("META-INF/services/mondrian.spi.UserDefinedFunction");
075                for (; serviceEnum.hasMoreElements();) {
076                    serviceUrls.add(serviceEnum.nextElement());
077                }
078            } catch (IOException e) {
079                logger.warn("Error while finding service files for user-defined functions", e);
080            }
081            Set<String> classNames = new HashSet<String>();
082            for (URL url : serviceUrls) {
083                BufferedReader reader = null;
084                try {
085                    reader =
086                        new BufferedReader(new InputStreamReader(url.openStream(),
087                            "UTF-8"));
088                    String line;
089                    while ((line = reader.readLine()) != null) {
090                        line = line.trim();
091                        if (line.length() > 0) {
092                            if (line.charAt(0) == '#') {
093                                continue;
094                            }
095                            int comment = line.indexOf('#');
096                            if (comment != -1) {
097                                line = line.substring(0, comment).trim();
098                            }
099                            classNames.add(line);
100                        }
101                    }
102                } catch (IOException e) {
103                    logger.warn("Error when loading service file '" + url + "'", e);
104                } finally {
105                    if (reader != null) {
106                        try {
107                            reader.close();
108                        } catch (IOException ignored) {
109                        }
110                    }
111                }
112            }
113            return classNames;
114        }
115    
116        /**
117         * Defines a user-defined function in this table.
118         *
119         * <p>If the function is not valid, throws an error.
120         *
121         * @param className Name of the class which implements the function.
122         *   The class must implement {@link mondrian.spi.UserDefinedFunction}
123         *   (otherwise it is a user-error).
124         */
125        private void defineUdf(String className) {
126            // Load class.
127            final Class<?> udfClass;
128            try {
129                udfClass = Class.forName(className);
130            } catch (ClassNotFoundException e) {
131                throw MondrianResource.instance().UdfClassNotFound.ex("",className);
132            }
133    
134            // Instantiate class with default constructor.
135            final UserDefinedFunction udf = Util.createUdf(udfClass);
136    
137            // Validate function.
138            validateFunction(udf);
139    
140            // Define function.
141            define(new UdfResolver(udf));
142        }
143    
144        /**
145         * Throws an error if a user-defined function does not adhere to the
146         * API.
147         */
148        private void validateFunction(final UserDefinedFunction udf) {
149            // Check that the name is not null or empty.
150            final String udfName = udf.getName();
151            if (udfName == null || udfName.equals("")) {
152                throw Util.newInternal("User-defined function defined by class '" +
153                        udf.getClass() + "' has empty name");
154            }
155            // It's OK for the description to be null.
156            //final String description = udf.getDescription();
157    
158            final Type[] parameterTypes = udf.getParameterTypes();
159            for (int i = 0; i < parameterTypes.length; i++) {
160                Type parameterType = parameterTypes[i];
161                if (parameterType == null) {
162                    throw Util.newInternal("Invalid user-defined function '" +
163                            udfName + "': parameter type #" + i +
164                            " is null");
165                }
166            }
167    
168            // It's OK for the reserved words to be null or empty.
169            //final String[] reservedWords = udf.getReservedWords();
170    
171            // Test that the function returns a sensible type when given the FORMAL
172            // types. It may still fail when we give it the ACTUAL types, but it's
173            // impossible to check that now.
174            final Type returnType = udf.getReturnType(parameterTypes);
175            if (returnType == null) {
176                throw Util.newInternal("Invalid user-defined function '" +
177                        udfName + "': return type is null");
178            }
179            final Syntax syntax = udf.getSyntax();
180            if (syntax == null) {
181                throw Util.newInternal("Invalid user-defined function '" +
182                        udfName + "': syntax is null");
183            }
184        }
185    
186    
187    }
188    
189    // End GlobalFunTable.java