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