Erweiterungen: Funktionen und Formatter
Mondrian bietet Schema-Autoren mehrere Erweiterungspunkte, um anzupassen, wie MDX-Funktionen arbeiten, wie Zellwerte angezeigt werden und wie Member-Beschriftungen sprachübergreifend erscheinen. Diese Seite behandelt die Erweiterungspunkte, die Sie am wahrscheinlichsten im Alltag verwenden werden.
Benutzerdefinierte Funktionen
Eine benutzerdefinierte Funktion (User-Defined Function, UDF) erlaubt es Ihnen, eine eigene Java- (oder Skript-)Funktion aus jedem MDX-Ausdruck aufzurufen. Mondrian ruft sie während der Abfrageauswertung genau wie eine integrierte Funktion auf.
Eine Java-UDF schreiben
Ihre Klasse muss einen öffentlichen parameterlosen Konstruktor haben und das Interface mondrian.spi.UserDefinedFunction implementieren. Das folgende Beispiel addiert eins zu seinem numerischen Argument:
package com.example;
import mondrian.olap.*;import mondrian.olap.type.*;import mondrian.spi.UserDefinedFunction;
/** * A simple user-defined function which adds one to its argument. */public class PlusOneUdf implements UserDefinedFunction {
// public constructor public PlusOneUdf() { }
public String getName() { return "PlusOne"; }
public String getDescription() { return "Returns its argument plus one"; }
public Syntax getSyntax() { return Syntax.Function; }
public Type getReturnType(Type[] parameterTypes) { return new NumericType(); }
public Type[] getParameterTypes() { return new Type[] { new NumericType() }; }
public Object execute(Evaluator evaluator, Exp[] arguments) { final Object argValue = arguments[0].evaluateScalar(evaluator); if (argValue instanceof Number) { return new Double(((Number) argValue).doubleValue() + 1); } else { // Argument might be a RuntimeException indicating that // the cache does not yet have the required cell value. // The function will be called again when the cache is loaded. return null; } }
public String[] getReservedWords() { return null; }}Eine UDF in Ihrem Schema registrieren
Deklarieren Sie die UDF innerhalb Ihres <Schema>-Elements nach Ihren Cubes:
<Schema ...> ... <UserDefinedFunction name="PlusOne" className="com.example.PlusOneUdf"/></Schema>Die UDF in MDX verwenden
Einmal registriert, können Sie die Funktion aus jeder MDX-Anweisung aufrufen:
WITH MEMBER [Measures].[Unit Sales Plus One] AS 'PlusOne([Measures].[Unit Sales])'SELECT {[Measures].[Unit Sales]} ON COLUMNS, {[Gender].MEMBERS} ON ROWSFROM [Sales]Eine Klasse über mehrere Funktionsnamen teilen
Wenn Sie Ihrer Klasse einen öffentlichen Konstruktor geben, der ein einzelnes String-Argument akzeptiert, übergibt Mondrian den Funktionsnamen an diesen Konstruktor. So kann eine Klasse mehrere registrierte UDFs bedienen:
public class PlusOrMinusOneUdf implements UserDefinedFunction { private final String name; private final boolean isPlus;
public PlusOrMinusOneUdf(String name) { this.name = name; if (name.equals("PlusOne")) { isPlus = true; } else if (name.equals("MinusOne")) { isPlus = false; } else { throw new IllegalArgumentException("Unexpected name " + name); } }
public String getName() { return name; } // ... getDescription, getSyntax, getReturnType, getParameterTypes as before ...
public Object execute(Evaluator evaluator, Exp[] arguments) { final Object argValue = arguments[0].evaluateScalar(evaluator); if (argValue instanceof Number) { if (isPlus) { return new Double(((Number) argValue).doubleValue() + 1); } else { return new Double(((Number) argValue).doubleValue() - 1); } } else { return null; } }
public String[] getReservedWords() { return null; }}Registrieren Sie beide Namen, die auf dieselbe Klasse zeigen:
<Schema ...> ... <UserDefinedFunction name="PlusOne" className="com.example.PlusOrMinusOneUdf"/> <UserDefinedFunction name="MinusOne" className="com.example.PlusOrMinusOneUdf"/></Schema>Automatische Erkennung über den Service-Provider-Mechanismus
Sie können Ihre UDF-Implementierungen in einer JAR-Datei verpacken und eine Datei META-INF/services/mondrian.spi.UserDefinedFunction mit einem voll qualifizierten Klassennamen pro Zeile einfügen. Mondrian entdeckt diese automatisch und macht sie für jedes in dieser JVM geladene Schema verfügbar – ohne schema-spezifische Deklaration.
Skript-UDFs (JavaScript)
Für einfache Logik, die Sie nicht kompilieren möchten, betten Sie die Implementierung in ein <Script>-Kind von <UserDefinedFunction> ein. Die folgenden Funktionen müssen im Skript vorhanden sein:
getParameterTypes()getReturnType(parameterTypes)execute(evaluator, arguments)
getName(), getDescription(), getReservedWords() und getSyntax() sind optional und haben sinnvolle Standardwerte basierend auf dem name-Attribut.
Hier ist eine Fakultätsfunktion in JavaScript:
<UserDefinedFunction name="Factorial"> <Script language="JavaScript"> function getParameterTypes() { return new Array(new mondrian.olap.type.NumericType()); } function getReturnType(parameterTypes) { return new mondrian.olap.type.NumericType(); } function execute(evaluator, arguments) { var n = arguments[0].evaluateScalar(evaluator); return factorial(n); } function factorial(n) { return n <= 1 ? 1 : n * factorial(n - 1); } </Script></UserDefinedFunction>Cell Formatter
Ein Cell Formatter überschreibt, wie Mondrian den rohen Wert einer Measure-Zelle zur Anzeige formatiert. Er steuert, was Cell.getFormattedValue() zurückgibt. Ihre Klasse muss mondrian.spi.CellFormatter implementieren.
Einen Formatter an eine Measure anhängen
<Measure name="Revenue"> <CellFormatter className="com.example.MyCellFormatter"/></Measure>Skript-basierter Cell Formatter
<Measure name="Revenue"> <CellFormatter> <Script language="JavaScript"> var s = value.toString(); while (s.length() < 20) { s = "0" + s; } return s; </Script> </CellFormatter></Measure>Das Skript erhält eine Variable value, die dem rohen Zellwert entspricht. Das Fragment kann mehrere Anweisungen enthalten, muss aber mit einer return-Anweisung enden.
Formatter auf einem berechneten Member
<CellFormatter> funktioniert auch auf <CalculatedMember>-Elementen:
calculated_members:- name: "Double Sales" dimension: "Measures" formula: "[Measures].[Unit Sales] * 2" cell_formatter: script: body: "var s = value.toString();\nwhile (s.length() < 20) { s = \"0\" +\ \ s; }\nreturn s;"<CalculatedMember name="Double Sales" dimension="Measures"> <Formula>[Measures].[Unit Sales] * 2</Formula> <CellFormatter> <Script language="JavaScript"> var s = value.toString(); while (s.length() < 20) { s = "0" + s; } return s; </Script> </CellFormatter></CalculatedMember>Formatter über CELL_FORMATTER-Property (MDX)
Für berechnete Measures, die in der WITH MEMBER-Klausel einer MDX-Abfrage definiert werden, setzen Sie die Property CELL_FORMATTER:
WITH MEMBER [Measures].[Foo] AS '[Measures].[Unit Sales] * 2', CELL_FORMATTER='com.example.MyCellFormatter'SELECT {[Measures].[Unit Sales], [Measures].[Foo]} ON COLUMNS, {[Store].Children} ON ROWSFROM [Sales]Für einen Skript-basierten Formatter inline in MDX verwenden Sie CELL_FORMATTER_SCRIPT und CELL_FORMATTER_SCRIPT_LANGUAGE:
WITH MEMBER [Measures].[Foo] AS '[Measures].[Unit Sales] * 2', CELL_FORMATTER_SCRIPT_LANGUAGE='JavaScript', CELL_FORMATTER_SCRIPT='var s = value.toString(); while (s.length() < 20) s = "0" + s; return s;'SELECT {[Measures].[Unit Sales], [Measures].[Foo]} ON COLUMNS, {[Store].Children} ON ROWSFROM [Sales]Member Formatter
Ein Member Formatter überschreibt, wie Mondrian die Beschriftung eines Members formatiert. Er steuert, was Member.getCaption() zurückgibt. Ihre Klasse muss mondrian.spi.MemberFormatter implementieren.
Einen Formatter an eine Ebene anhängen
<Level name="Store Name" column="store_name"> <MemberFormatter className="com.example.MyMemberFormatter"/></Level>Skript-basierter Member Formatter
<Level name="Store Name" column="store_name"> <MemberFormatter> <Script language="JavaScript"> return member.getName().toUpperCase(); </Script> </MemberFormatter></Level>Das Skript erhält eine Variable member – das Member-Objekt, das formatiert wird. Das Fragment muss mit einer return-Anweisung enden.
Property Formatter
Ein Property Formatter überschreibt, wie Mondrian den Wert einer Member-Property formatiert. Er steuert, was Property.getPropertyFormattedValue() zurückgibt. Ihre Klasse muss mondrian.spi.PropertyFormatter implementieren.
Einen Formatter an eine Property anhängen
In Mondrian 4 lebt das <Property>-Element innerhalb einer <Attribute>-Definition:
<Attribute name="My Attribute" keyColumn="attributeColumn" uniqueMembers="true"> <Property name="My Property" column="propColumn"> <PropertyFormatter className="com.example.MyPropertyFormatter"/> </Property></Attribute>Skript-basierter Property Formatter
<Attribute name="Store Name" keyColumn="store_name"> <Property name="Store Type" column="store_type"> <PropertyFormatter> <Script language="JavaScript"> return member.getName().toUpperCase(); </Script> </PropertyFormatter> </Property></Attribute>Das Skript erhält die Variablen member, propertyName und propertyValue. Das Fragment muss mit einer return-Anweisung enden.
Ihr Schema lokalisieren
Mondrian erlaubt es Ihnen, Ihr Schema in mehreren Sprachen anzubieten, ohne separate Schema-Dateien zu pflegen. Der Ansatz kombiniert zwei Dinge: sprachspezifische Beschriftungs- und Beschreibungswerte, die als <Annotation>-Elemente gespeichert werden, und die Klasse LocalizingDynamicSchemaProcessor, die das Schema-XML für jede Sprache zur Laufzeit umschreibt.
<Annotation> für sprachspezifische Metadaten
Jedes Schema-Objekt (<Schema>, <Cube>, <Dimension>, <Hierarchy>, <Level>, <Measure>, benannte Sets usw.) unterstützt einen <Annotations>-Kindblock. Die Mondrian-Konvention ist, punktqualifizierte Annotationsnamen für Sprachmetadaten zu verwenden:
cubes: Sales: caption: "Sales" annotations: caption.de_DE: "Verkaufen" caption.fr_FR: "Ventes" description.fr_FR: "Cube des ventes" description.de_AT: "Cube den Verkaufen"<Cube name="Sales" caption="Sales"> <Annotations> <Annotation name="caption.de_DE">Verkaufen</Annotation> <Annotation name="caption.fr_FR">Ventes</Annotation> <Annotation name="description.fr_FR">Cube des ventes</Annotation> <Annotation name="description.de_AT">Cube den Verkaufen</Annotation> </Annotations> ...</Cube>Client-Tools, die diese Konvention verstehen, können dann Beschriftungen in der Sprache des Benutzers anzeigen. Das YAML-Schema-Format unterstützt diese Annotationen direkt unter dem annotations-Schlüssel jedes Elements – siehe YAML-Schema-Referenz für die vollständige Syntax.
Substitution per Properties-Datei mit LocalizingDynamicSchemaProcessor
Für eine vollständige serverseitige Sprach-Substitution schreiben Sie Ihr Schema mit %{key}-Platzhaltern in den Attributen caption, description, allMemberCaption und measuresCaption:
<Schema name="FoodMart" measuresCaption="%{foodmart.measures.caption}"> <Dimension name="Store" caption="%{foodmart.dimension.store.caption}" description="%{foodmart.dimension.store.description}"> <Hierarchy hasAll="true" allMemberName="All Stores" allMemberCaption="%{foodmart.dimension.store.allmember.caption}" primaryKey="store_id"> <Table name="store"/> <Level name="Store Country" column="store_country" uniqueMembers="true" caption="%{foodmart.dimension.store.country.caption}" description="%{foodmart.dimension.store.country.description}"/> <Level name="Store State" column="store_state" uniqueMembers="true" caption="%{foodmart.dimension.store.state.caption}"/> <Level name="Store City" column="store_city" uniqueMembers="false" caption="%{foodmart.dimension.store.city.caption}"/> </Hierarchy> </Dimension>
<Cube name="Sales" caption="%{foodmart.cube.sales.caption}" description="%{foodmart.cube.sales.description}"> ... <MeasureGroup table="sales_fact_1997"> <Measures> <Measure name="Unit Sales" column="unit_sales" caption="%{foodmart.cube.sales.measure.unitsales.caption}" description="%{foodmart.cube.sales.measure.unitsales.description}"/> </Measures> </MeasureGroup> </Cube></Schema>Stellen Sie eine Standard-Properties-Datei (locale.properties) und eine pro unterstützter Sprache (locale_fr.properties, locale_de.properties usw.) bereit. Jede Datei ordnet die Platzhalterschlüssel übersetzten Strings zu:
# locale.properties — default (English)foodmart.measures.caption=Measuresfoodmart.dimension.store.caption=Storefoodmart.dimension.store.country.caption=Store Countryfoodmart.dimension.store.state.caption=Store Statefoodmart.dimension.store.city.caption=Store Cityfoodmart.dimension.store.allmember.caption=All Storesfoodmart.cube.sales.caption=Salesfoodmart.cube.sales.measure.unitsales.caption=Unit Sales# locale_hu.properties — Hungarianfoodmart.measures.caption=Hungarian Measuresfoodmart.dimension.store.caption=Áruházfoodmart.dimension.store.country.caption=Országfoodmart.dimension.store.state.caption=Állam/Megyefoodmart.dimension.store.city.caption=Városfoodmart.dimension.store.allmember.caption=Minden Áruházfoodmart.cube.sales.caption=Forgalomfoodmart.cube.sales.measure.unitsales.caption=Eladott dbDer LocalizingDynamicSchemaProcessor wird über den Mondrian-Connection-String aktiviert. In Saiku Cloud wird dies auf der Datenquellen-Ebene durch Ihren Administrator konfiguriert. Wenn Sie eine Sprach-Substitution auf Schema-Ebene benötigen, wenden Sie sich an den Support.
Fortgeschritten: Selbstgehostete Erweiterungspunkte
Die drei folgenden Erweiterungspunkte gelten nur für selbstgehostete oder eingebettete Mondrian-Deployments. Sie erfordern Konfiguration auf Deployment-Ebene (Connection-Strings, web.xml-Servlet-Deklarationen) und werden typischerweise nicht benötigt, wenn Sie Saiku Cloud verwenden.
- Dynamic Schema Processor (
mondrian.spi.DynamicSchemaProcessor): Fängt das Schema-Laden ab, um das Schema-XML bei jeder ROLAP-Verbindung zu filtern oder zu generieren. Wird verwendet, um sprach- oder mandantenspezifische Inhalte zur Ladezeit einzuspielen. - Data Source Change Listener (
mondrian.spi.DataSourceChangeListener): Klinkt sich in Mondrians Cache-Invalidierungspfad ein. Beim Aufruf signalisiert er, ob sich die zugrundeliegenden Dimensions- oder Faktendaten geändert haben, und löst eine Cache-Leerung aus. - Dynamic Datasource XMLA Servlet (
mondrian.xmla.impl.DynamicDatasourceXmlaServlet): Ein alternatives XMLA-Servlet, dasdatasources.xmlbei jeder Client-Anfrage neu lädt und Katalog-Caches selektiv für geänderte Einträge leert.
Wenn Sie Mondrian eingebettet betreiben und eines dieser SPIs implementieren müssen, konsultieren Sie das Mondrian-Javadoc für mondrian.spi.
Adaptiert aus dem Mondrian-Projekt-Schema-Guide (EPL v1.0).