/*
// This software is subject to the terms of the Eclipse Public License v1.0
// Agreement, available at the following URL:
// http://www.eclipse.org/legal/epl-v10.html.
// You must accept the terms of that agreement to use this software.
//
// Copyright (C) 2006-2014 Pentaho
// All Rights Reserved.
*/
package mondrian.server;
import mondrian.olap.MondrianException;
import mondrian.olap.MondrianServer;
import mondrian.olap.Util;
import mondrian.olap4j.*;
import mondrian.resource.MondrianResource;
import mondrian.rolap.RolapConnection;
import mondrian.rolap.RolapResultShepherd;
import mondrian.rolap.RolapSchema;
import mondrian.rolap.agg.AggregationManager;
import mondrian.server.monitor.*;
import mondrian.spi.CatalogLocator;
import mondrian.util.LockBox;
import mondrian.xmla.*;
import org.apache.commons.collections.map.ReferenceMap;
import org.apache.log4j.Logger;
import org.olap4j.OlapConnection;
import java.lang.management.ManagementFactory;
import java.lang.ref.*;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import javax.management.*;
/**
* Implementation of {@link mondrian.olap.MondrianServer}.
*
* @author jhyde
* @since Jun 25, 2006
*/
class MondrianServerImpl
extends MondrianServer
implements CatalogFinder, XmlaHandler.ConnectionFactory
{
/**
* Id of server. Unique within JVM's lifetime. Not the same as the ID of
* the server within a lockbox.
*/
private final int id = ID_GENERATOR.incrementAndGet();
/**
* Within a server, registry of objects such as data sources and roles.
* For convenience, all servers currently share the same lockbox.
*/
private final LockBox lockBox;
private final Repository repository;
private final CatalogLocator catalogLocator;
private final RolapResultShepherd shepherd;
/**
* Map of open connections, by id. Connections are added just after
* construction, and are removed when they call close. Garbage collection
* may cause a connection to be removed earlier.
*/
@SuppressWarnings("unchecked")
private final Map<Integer, RolapConnection> connectionMap =
// We use a reference map here because the value
// is what needs to be week, not the key, as it
// would be the case with a WeakHashMap.
Collections.synchronizedMap(
new ReferenceMap(ReferenceMap.WEAK, ReferenceMap.WEAK));
/**
* Map of open statements, by id. Statements are added just after
* construction, and are removed when they call close. Garbage collection
* may cause a connection to be removed earlier.
*/
@SuppressWarnings("unchecked")
private final Map<Long, Statement> statementMap =
// We use a reference map here because the value
// is what needs to be week, not the key, as it
// would be the case with a WeakHashMap.
Collections.synchronizedMap(
new ReferenceMap(ReferenceMap.WEAK, ReferenceMap.WEAK));
private final MonitorImpl monitor = new MonitorImpl();
private final AggregationManager aggMgr;
private boolean shutdown = false;
private static final Logger LOGGER =
Logger.getLogger(MondrianServerImpl.class);
private static final AtomicInteger ID_GENERATOR = new AtomicInteger();
private static final List<String> KEYWORD_LIST =
Collections.unmodifiableList(Arrays.asList(
"$AdjustedProbability", "$Distance", "$Probability",
"$ProbabilityStDev", "$ProbabilityStdDeV", "$ProbabilityVariance",
"$StDev", "$StdDeV", "$Support", "$Variance",
"AddCalculatedMembers", "Action", "After", "Aggregate", "All",
"Alter", "Ancestor", "And", "Append", "As", "ASC", "Axis",
"Automatic", "Back_Color", "BASC", "BDESC", "Before",
"Before_And_After", "Before_And_Self", "Before_Self_After",
"BottomCount", "BottomPercent", "BottomSum", "Break", "Boolean",
"Cache", "Calculated", "Call", "Case", "Catalog_Name", "Cell",
"Cell_Ordinal", "Cells", "Chapters", "Children",
"Children_Cardinality", "ClosingPeriod", "Cluster",
"ClusterDistance", "ClusterProbability", "Clusters",
"CoalesceEmpty", "Column_Values", "Columns", "Content",
"Contingent", "Continuous", "Correlation", "Cousin", "Covariance",
"CovarianceN", "Create", "CreatePropertySet", "CrossJoin", "Cube",
"Cube_Name", "CurrentMember", "CurrentCube", "Custom", "Cyclical",
"DefaultMember", "Default_Member", "DESC", "Descendents",
"Description", "Dimension", "Dimension_Unique_Name", "Dimensions",
"Discrete", "Discretized", "DrillDownLevel",
"DrillDownLevelBottom", "DrillDownLevelTop", "DrillDownMember",
"DrillDownMemberBottom", "DrillDownMemberTop", "DrillTrough",
"DrillUpLevel", "DrillUpMember", "Drop", "Else", "Empty", "End",
"Equal_Areas", "Exclude_Null", "ExcludeEmpty", "Exclusive",
"Expression", "Filter", "FirstChild", "FirstRowset",
"FirstSibling", "Flattened", "Font_Flags", "Font_Name",
"Font_size", "Fore_Color", "Format_String", "Formatted_Value",
"Formula", "From", "Generate", "Global", "Head", "Hierarchize",
"Hierarchy", "Hierary_Unique_name", "IIF", "IsEmpty",
"Include_Null", "Include_Statistics", "Inclusive", "Input_Only",
"IsDescendant", "Item", "Lag", "LastChild", "LastPeriods",
"LastSibling", "Lead", "Level", "Level_Unique_Name", "Levels",
"LinRegIntercept", "LinRegR2", "LinRegPoint", "LinRegSlope",
"LinRegVariance", "Long", "MaxRows", "Median", "Member",
"Member_Caption", "Member_Guid", "Member_Name", "Member_Ordinal",
"Member_Type", "Member_Unique_Name", "Members",
"Microsoft_Clustering", "Microsoft_Decision_Trees", "Mining",
"Model", "Model_Existence_Only", "Models", "Move", "MTD", "Name",
"Nest", "NextMember", "Non", "Normal", "Not", "Ntext", "Nvarchar",
"OLAP", "On", "OpeningPeriod", "OpenQuery", "Or", "Ordered",
"Ordinal", "Pages", "Pages", "ParallelPeriod", "Parent",
"Parent_Level", "Parent_Unique_Name", "PeriodsToDate", "PMML",
"Predict", "Predict_Only", "PredictAdjustedProbability",
"PredictHistogram", "Prediction", "PredictionScore",
"PredictProbability", "PredictProbabilityStDev",
"PredictProbabilityVariance", "PredictStDev", "PredictSupport",
"PredictVariance", "PrevMember", "Probability",
"Probability_StDev", "Probability_StdDev", "Probability_Variance",
"Properties", "Property", "QTD", "RangeMax", "RangeMid",
"RangeMin", "Rank", "Recursive", "Refresh", "Related", "Rename",
"Rollup", "Rows", "Schema_Name", "Sections", "Select", "Self",
"Self_And_After", "Sequence_Time", "Server", "Session", "Set",
"SetToArray", "SetToStr", "Shape", "Skip", "Solve_Order", "Sort",
"StdDev", "Stdev", "StripCalculatedMembers", "StrToSet",
"StrToTuple", "SubSet", "Support", "Tail", "Text", "Thresholds",
"ToggleDrillState", "TopCount", "TopPercent", "TopSum",
"TupleToStr", "Under", "Uniform", "UniqueName", "Use", "Value",
"Value", "Var", "Variance", "VarP", "VarianceP", "VisualTotals",
"When", "Where", "With", "WTD", "Xor"));
/**
* Creates a MondrianServerImpl.
*
* @param registry Registry of all servers in this JVM
* @param repository Repository of catalogs and schemas
* @param catalogLocator Catalog locator
*/
MondrianServerImpl(
MondrianServerRegistry registry,
Repository repository,
CatalogLocator catalogLocator)
{
assert repository != null;
assert catalogLocator != null;
this.repository = repository;
this.catalogLocator = catalogLocator;
// All servers in a JVM share the same lockbox. This is a bit more
// forgiving to applications which have slightly mismatched
// specifications of the servers where they create and retrieve the
// entry.
this.lockBox = registry.lockBox;
this.aggMgr = new AggregationManager(this);
this.shepherd = new RolapResultShepherd();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("new MondrianServer: id=" + id);
}
registerMBean();
}
@Override
protected void finalize() throws Throwable {
try {
super.finalize();
shutdown(true);
} catch (Throwable t) {
LOGGER.info(
MondrianResource.instance()
.FinalizerErrorMondrianServerImpl.baseMessage,
t);
}
}
public int getId() {
return id;
}
@Override
public RolapResultShepherd getResultShepherd() {
if (shutdown) {
throw new MondrianException("Server already shutdown.");
}
return this.shepherd;
}
public List<String> getKeywords() {
return KEYWORD_LIST;
}
public LockBox getLockBox() {
return lockBox;
}
public AggregationManager getAggregationManager() {
if (shutdown) {
throw new MondrianException("Server already shutdown.");
}
return aggMgr;
}
@Override
public OlapConnection getConnection(
String databaseName,
String catalogName,
String roleName)
throws SQLException
{
if (shutdown) {
throw new MondrianException("Server already shutdown.");
}
return this.getConnection(
databaseName, catalogName, roleName,
new Properties());
}
@Override
public OlapConnection getConnection(
String databaseName,
String catalogName,
String roleName,
Properties props)
throws SQLException
{
if (shutdown) {
throw new MondrianException("Server already shutdown.");
}
return repository.getConnection(
this, databaseName, catalogName, roleName, props);
}
public List<String> getCatalogNames(
RolapConnection connection)
{
if (shutdown) {
throw new MondrianException("Server already shutdown.");
}
return
repository.getCatalogNames(
connection,
// We assume that Mondrian supports a single database
// per server.
repository.getDatabaseNames(connection).get(0));
}
public List<Map<String, Object>> getDatabases(
RolapConnection connection)
{
if (shutdown) {
throw new MondrianException("Server already shutdown.");
}
return repository.getDatabases(connection);
}
@Override
public CatalogLocator getCatalogLocator() {
if (shutdown) {
throw new MondrianException("Server already shutdown.");
}
return catalogLocator;
}
@Override
public void shutdown() {
this.shutdown(false);
}
private void shutdown(boolean silent) {
if (this == MondrianServerRegistry.INSTANCE.staticServer) {
LOGGER.warn("Can't shutdown the static server.");
return;
}
if (shutdown) {
if (silent) {
return;
}
throw new MondrianException("Server already shutdown.");
}
this.shutdown = true;
aggMgr.shutdown();
monitor.shutdown();
repository.shutdown();
shepherd.shutdown();
}
@Override
synchronized public void addConnection(RolapConnection connection) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"addConnection "
+ ", id=" + id
+ ", statements=" + statementMap.size()
+ ", connections=" + connectionMap.size());
}
if (shutdown) {
throw new MondrianException("Server already shutdown.");
}
connectionMap.put(
connection.getId(),
connection);
monitor.sendEvent(
new ConnectionStartEvent(
System.currentTimeMillis(),
connection.getServer().getId(),
connection.getId()));
}
@Override
synchronized public void removeConnection(RolapConnection connection) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"removeConnection "
+ ", id=" + id
+ ", statements=" + statementMap.size()
+ ", connections=" + connectionMap.size());
}
if (shutdown) {
throw new MondrianException("Server already shutdown.");
}
connectionMap.remove(connection.getId());
monitor.sendEvent(
new ConnectionEndEvent(
System.currentTimeMillis(),
getId(),
connection.getId()));
}
@Override
public RolapConnection getConnection(int connectionId) {
if (shutdown) {
throw new MondrianException("Server already shutdown.");
}
return connectionMap.get(connectionId);
}
@Override
synchronized public void addStatement(Statement statement) {
if (shutdown) {
throw new MondrianException("Server already shutdown.");
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"addStatement "
+ ", id=" + id
+ ", statements=" + statementMap.size()
+ ", connections=" + connectionMap.size());
}
statementMap.put(
statement.getId(),
statement);
final RolapConnection connection =
statement.getMondrianConnection();
monitor.sendEvent(
new StatementStartEvent(
System.currentTimeMillis(),
connection.getServer().getId(),
connection.getId(),
statement.getId()));
}
@Override
synchronized public void removeStatement(Statement statement) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"removeStatement "
+ ", id=" + id
+ ", statements=" + statementMap.size()
+ ", connections=" + connectionMap.size());
}
if (shutdown) {
throw new MondrianException("Server already shutdown.");
}
statementMap.remove(statement.getId());
final RolapConnection connection =
statement.getMondrianConnection();
monitor.sendEvent(
new StatementEndEvent(
System.currentTimeMillis(),
connection.getServer().getId(),
connection.getId(),
statement.getId()));
}
public Monitor getMonitor() {
if (shutdown) {
throw new MondrianException("Server already shutdown.");
}
return monitor;
}
public Map<String, RolapSchema> getRolapSchemas(
RolapConnection connection,
String catalogName)
{
if (shutdown) {
throw new MondrianException("Server already shutdown.");
}
return
repository.getRolapSchemas(
connection,
// We assume that Mondrian supports a single database
// per server.
repository.getDatabaseNames(connection).get(0),
catalogName);
}
public Map<String, Object> getPreConfiguredDiscoverDatasourcesResponse() {
// No pre-configured response; XMLA servlet will connect to get
// data source info.
return null;
}
/**
* Registers the MonitorImpl associated with this server
* as an MBean accessible via JMX.
*/
private void registerMBean() {
if (Util.PreJdk16) {
LOGGER.info(
"JMX is supported in Mondrian only on Java 6+.");
return;
}
MBeanServer mbs =
ManagementFactory.getPlatformMBeanServer();
try {
ObjectName mxbeanName = new ObjectName(
"mondrian.server:type=Server-" + id);
mbs.registerMBean(getMonitor(), mxbeanName);
} catch (MalformedObjectNameException e) {
LOGGER.warn("Failed to register JMX MBean", e);
} catch (NotCompliantMBeanException e) {
LOGGER.warn("Failed to register JMX MBean", e);
} catch (InstanceAlreadyExistsException e) {
LOGGER.warn("Failed to register JMX MBean", e);
} catch (MBeanRegistrationException e) {
LOGGER.warn("Failed to register JMX MBean", e);
}
}
}
// End MondrianServerImpl.java