org.pivot4j.impl.PivotModelImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.pivot4j.impl.PivotModelImpl.java

Source

/*
 * ====================================================================
 * This software is subject to the terms of the Common Public License
 * Agreement, available at the following URL:
 *   http://www.opensource.org/licenses/cpl.html .
 * You must accept the terms of that agreement to use this software.
 * ====================================================================
 */
package org.pivot4j.impl;

import java.io.Serializable;
import java.sql.Date;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;

import org.apache.commons.configuration.HierarchicalConfiguration;
import org.apache.commons.lang.NullArgumentException;
import org.apache.commons.logging.LogFactory;
import org.olap4j.Axis;
import org.olap4j.CellSet;
import org.olap4j.CellSetAxis;
import org.olap4j.OlapConnection;
import org.olap4j.OlapDataSource;
import org.olap4j.OlapDatabaseMetaData;
import org.olap4j.OlapException;
import org.olap4j.OlapStatement;
import org.olap4j.Position;
import org.olap4j.Scenario;
import org.olap4j.mdx.IdentifierNode;
import org.olap4j.metadata.Catalog;
import org.olap4j.metadata.Cube;
import org.olap4j.metadata.Dimension;
import org.olap4j.metadata.Dimension.Type;
import org.olap4j.metadata.Member;
import org.olap4j.metadata.Schema;
import org.pivot4j.ModelChangeEvent;
import org.pivot4j.ModelChangeListener;
import org.pivot4j.NotInitializedException;
import org.pivot4j.PivotException;
import org.pivot4j.PivotModel;
import org.pivot4j.QueryEvent;
import org.pivot4j.QueryListener;
import org.pivot4j.el.ExpressionContext;
import org.pivot4j.el.ExpressionEvaluatorFactory;
import org.pivot4j.el.freemarker.FreeMarkerExpressionEvaluatorFactory;
import org.pivot4j.sort.SortCriteria;
import org.pivot4j.transform.Transform;
import org.pivot4j.transform.TransformFactory;
import org.pivot4j.transform.impl.TransformFactoryImpl;
import org.pivot4j.util.MemberHierarchyCache;
import org.pivot4j.util.OlapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The pivot model represents all (meta-)data for an MDX query.
 */
public class PivotModelImpl implements PivotModel {

    private Logger logger = LoggerFactory.getLogger(getClass());

    private OlapDataSource dataSource;

    private OlapConnection connection;

    private String roleName;

    private Locale locale;

    private boolean initialized = false;

    private Boolean scenarioSupported;

    private Collection<ModelChangeListener> modelListeners = new LinkedList<ModelChangeListener>();

    private Collection<QueryListener> queryListeners = new LinkedList<QueryListener>();

    private QueryAdapter queryAdapter;

    private TransformFactory transformFactory = new TransformFactoryImpl();

    private ExpressionEvaluatorFactory expressionEvaluatorFactory = new FreeMarkerExpressionEvaluatorFactory();

    private int topBottomCount = 10;

    private SortCriteria sortCriteria = SortCriteria.ASC;

    private boolean sorting = false;

    private boolean defaultNonEmpty = false;

    private List<Member> sortPosMembers;

    private String mdxQuery;

    private CellSet cellSet;

    private ExpressionContext expressionContext;

    private MemberHierarchyCache memberHierarchyCache;

    private QueryChangeListener queryChangeListener = new QueryChangeListener() {

        public void queryChanged(QueryChangeEvent e) {
            fireStructureChanged();
        }
    };

    /**
     * @param dataSource
     */
    public PivotModelImpl(OlapDataSource dataSource) {
        if (dataSource == null) {
            throw new NullArgumentException("dataSource");
        }

        this.dataSource = dataSource;
        this.expressionContext = createExpressionContext();
    }

    /**
     * @return the logger
     */
    protected Logger getLogger() {
        return logger;
    }

    /**
     * Returns the current locale.
     * 
     * @return Locale
     * @see org.pivot4j.PivotModel#getLocale()
     */
    public Locale getLocale() {
        if (locale == null) {
            return Locale.getDefault();
        }

        return locale;
    }

    /**
     * Sets the current locale.
     * 
     * @param locale
     *            The locale to set
     * @see org.pivot4j.PivotModel#setLocale(java.util.Locale)
     */
    public void setLocale(Locale locale) {
        this.locale = locale;

        if (connection != null) {
            connection.setLocale(locale);
        }
    }

    /**
     * @return the roleName
     * @see org.pivot4j.PivotModel#getRoleName()
     */
    public String getRoleName() {
        return roleName;
    }

    /**
     * @param roleName
     *            the roleName to set
     * @see org.pivot4j.PivotModel#setRoleName(java.lang.String)
     */
    public void setRoleName(String roleName) {
        this.roleName = roleName;

        if (connection != null) {
            try {
                connection.setRoleName(roleName);
            } catch (OlapException e) {
                throw new PivotException(e);
            }
        }
    }

    /**
     * @see org.pivot4j.PivotModel#initialize()
     */
    public synchronized void initialize() {
        if (isInitialized()) {
            destroy();
        }

        if (mdxQuery == null) {
            throw new PivotException("Initial MDX query is null.");
        }

        try {
            this.connection = createConnection(dataSource);
        } catch (SQLException e) {
            throw new PivotException(e);
        }

        this.initialized = true;

        Logger log = LoggerFactory.getLogger(getClass());
        if (log.isDebugEnabled()) {
            log.debug("Initializing model with MDX : {}", mdxQuery);
        }

        this.queryAdapter = createQueryAdapter();

        queryAdapter.initialize();
        queryAdapter.updateQuery();
        queryAdapter.addChangeListener(queryChangeListener);

        initializeCache();

        fireModelInitialized();
    }

    /**
     * @see org.pivot4j.PivotModel#isInitialized()
     */
    public boolean isInitialized() {
        return initialized;
    }

    private void checkInitialization() {
        if (!isInitialized()) {
            throw new NotInitializedException("Model has not been initialized yet.");
        }
    }

    private void initializeCache() {
        if (!isInitialized()) {
            return;
        }

        Cube cube = getCube();

        if (cube == null) {
            this.memberHierarchyCache = null;
        } else if (memberHierarchyCache == null || !OlapUtils.equals(memberHierarchyCache.getCube(), cube)) {
            this.memberHierarchyCache = new MemberHierarchyCache(cube);
        }
    }

    /**
     * @param dataSource
     * @return
     * @throws SQLException
     */
    protected OlapConnection createConnection(OlapDataSource dataSource) throws SQLException {
        OlapConnection con = dataSource.getConnection();

        if (roleName != null) {
            con.setRoleName(roleName);
        }

        if (locale != null) {
            con.setLocale(locale);
        }

        return con;
    }

    /**
     * @see org.pivot4j.PivotModel#destroy()
     */
    public synchronized void destroy() {
        checkInitialization();

        if (queryAdapter != null) {
            queryAdapter.removeChangeListener(queryChangeListener);
            this.queryAdapter = null;
        }

        if (connection != null) {
            if (logger.isDebugEnabled()) {
                logger.debug("Closing OLAP connection {}", connection);
            }

            try {
                closeConnection(connection);
            } catch (SQLException e) {
                throw new PivotException(e);
            }

            this.connection = null;
        }

        this.sortPosMembers = null;
        this.sortCriteria = SortCriteria.ASC;
        this.sorting = false;
        this.cellSet = null;
        this.initialized = false;

        this.memberHierarchyCache = null;

        fireModelDestroyed();
    }

    /**
     * @param connection
     * @throws SQLException
     */
    protected void closeConnection(OlapConnection connection) throws SQLException {
        connection.close();
    }

    protected ExpressionContext createExpressionContext() {
        ExpressionContext context = new ExpressionContext();

        context.put("locale", new ExpressionContext.ValueBinding<Locale>() {

            @Override
            public Locale getValue() {
                return getLocale();
            }
        });

        context.put("roleName", new ExpressionContext.ValueBinding<String>() {

            @Override
            public String getValue() {
                return getRoleName();
            }
        });

        context.put("cube", new ExpressionContext.ValueBinding<Cube>() {

            @Override
            public Cube getValue() {
                if (!isInitialized()) {
                    return null;
                }

                return getCube();
            }
        });

        context.put("catalog", new ExpressionContext.ValueBinding<Catalog>() {

            @Override
            public Catalog getValue() {
                if (!isInitialized()) {
                    return null;
                }

                return getCatalog();
            }
        });

        context.put("cellSet", new ExpressionContext.ValueBinding<CellSet>() {

            @Override
            public CellSet getValue() {
                return cellSet;
            }
        });

        context.put("memberUtils", new ExpressionContext.ValueBinding<OlapUtils>() {

            @Override
            public OlapUtils getValue() {
                Cube cube = getCube();
                if (cube == null) {
                    return null;
                }

                OlapUtils utils = new OlapUtils(cube);
                utils.setMemberHierarchyCache(memberHierarchyCache);

                return utils;
            }
        });

        return context;
    }

    /**
     * Returns the connection.
     */
    protected OlapConnection getConnection() {
        return connection;
    }

    /**
     * @see org.pivot4j.PivotModel#isScenarioSupported()
     */
    @Override
    public boolean isScenarioSupported() {
        if (scenarioSupported == null) {
            Cube cube = getCube();

            Schema schema = cube.getSchema();
            Catalog catalog = schema.getCatalog();

            OlapDatabaseMetaData metadata = cube.getSchema().getCatalog().getMetaData();

            ResultSet rs = null;

            try {
                // TODO See Olap4J's issue #116
                if (metadata.getDriverName().startsWith("Mondrian")) {
                    Dimension dimension = cube.getDimensions().get("Scenario");
                    this.scenarioSupported = dimension != null && dimension.isVisible();
                } else {
                    rs = metadata.getCubes(catalog.getName(), schema.getName(), cube.getName());
                    if (rs.next()) {
                        scenarioSupported = rs.getBoolean("IS_WRITE_ENABLED");
                    }
                }
            } catch (SQLException e) {
                throw new PivotException(e);
            } finally {
                if (rs != null) {
                    try {
                        rs.close();
                    } catch (SQLException e) {
                    }
                }
            }
        }

        return Boolean.TRUE.equals(scenarioSupported);
    }

    /**
     * @see org.pivot4j.PivotModel#createScenario()
     */
    @Override
    public Scenario createScenario() {
        checkInitialization();

        try {
            return connection.createScenario();
        } catch (OlapException e) {
            throw new PivotException(e);
        }
    }

    /**
     * @see org.pivot4j.PivotModel#getScenario()
     */
    @Override
    public Scenario getScenario() {
        checkInitialization();

        try {
            return connection.getScenario();
        } catch (OlapException e) {
            throw new PivotException(e);
        }
    }

    /**
     * @see org.pivot4j.PivotModel#setScenario(org.olap4j.Scenario)
     */
    @Override
    public void setScenario(Scenario scenario) {
        checkInitialization();

        try {
            connection.setScenario(scenario);
        } catch (OlapException e) {
            throw new PivotException(e);
        }

        refresh();

        fireModelChanged();
    }

    public OlapDataSource getDataSource() {
        return dataSource;
    }

    /**
     * @return
     * @throws NotInitializedException
     */
    public Catalog getCatalog() {
        checkInitialization();

        try {
            return connection.getOlapCatalog();
        } catch (OlapException e) {
            throw new PivotException(e);
        }
    }

    /**
     * @see org.pivot4j.PivotModel#getMetadata()
     */
    @Override
    public OlapDatabaseMetaData getMetadata() {
        boolean initialized = (connection != null);

        try {
            if (!initialized) {
                connection = createConnection(dataSource);
            }

            return connection.getMetaData();
        } catch (SQLException e) {
            throw new PivotException(e);
        } finally {
            if (!initialized) {
                try {
                    closeConnection(connection);
                } catch (SQLException e) {
                    throw new PivotException(e);
                }
            }
        }
    }

    /**
     * @see org.pivot4j.PivotModel#getCube()
     */
    public Cube getCube() {
        checkInitialization();

        Cube cube = null;

        try {
            if (cellSet != null) {
                cube = cellSet.getMetaData().getCube();
            } else {
                String cubeName = queryAdapter.getCubeName();

                Schema schema = connection.getOlapSchema();
                cube = schema.getCubes().get(cubeName);

                if (cube == null && cubeName != null) {
                    if (logger.isWarnEnabled()) {
                        logger.warn("Cube with the specified name cannot be found : " + cubeName);
                    }

                    if (logger.isDebugEnabled()) {
                        logger.debug("List of cubes in schema : " + schema.getName());

                        for (Cube c : schema.getCubes()) {
                            logger.debug(c.getCaption() + " - " + c.getUniqueName());
                        }
                    }
                }
            }
        } catch (OlapException e) {
            throw new PivotException(e);
        }

        return cube;
    }

    /**
     * @see org.pivot4j.PivotModel#getCellSet()
     */
    public synchronized CellSet getCellSet() {
        checkInitialization();

        if (cellSet != null) {
            return cellSet;
        }

        if (queryAdapter == null) {
            throw new IllegalStateException("Initial MDX is not specified.");
        }

        if (expressionEvaluatorFactory != null) {
            queryAdapter.evaluate(expressionEvaluatorFactory.createEvaluator());
        }

        String mdx = normalizeMdx(getCurrentMdx(true));

        if (!queryAdapter.isValid()) {
            return null;
        }

        try {
            this.cellSet = executeMdx(connection, mdx);
        } catch (OlapException e) {
            throw new PivotException(e);
        }

        expressionContext.put("cellSet", cellSet);

        queryAdapter.afterExecute(cellSet);

        return cellSet;
    }

    /**
     * @see org.pivot4j.PivotModel#refresh()
     */
    @Override
    public void refresh() {
        this.cellSet = null;
    }

    /**
     * @see org.pivot4j.PivotModel#getExpressionContext()
     */
    @Override
    public ExpressionContext getExpressionContext() {
        return expressionContext;
    }

    /**
     * @return the memberHierarchyCache
     */
    public MemberHierarchyCache getMemberHierarchyCache() {
        return memberHierarchyCache;
    }

    /**
     * @param connection
     * @param mdx
     * @return
     * @throws OlapException
     */
    protected CellSet executeMdx(OlapConnection connection, String mdx) throws OlapException {
        if (logger.isDebugEnabled()) {
            logger.debug(mdx);
        }

        Date start = new Date(System.currentTimeMillis());

        OlapStatement stmt = connection.createStatement();
        CellSet result = stmt.executeOlapQuery(mdx);

        long duration = System.currentTimeMillis() - start.getTime();
        if (logger.isInfoEnabled()) {
            logger.info(String.format("Query execution time : %d ms", duration));
        }

        fireQueryExecuted(start, duration, mdx);

        return result;
    }

    protected String normalizeMdx(String mdx) {
        if (mdx == null) {
            return null;
        } else {
            return mdx.replaceAll("\r", "");
        }
    }

    /**
     * @see org.pivot4j.PivotModel#getCurrentMdx()
     */
    public String getCurrentMdx() {
        return getCurrentMdx(false);
    }

    /**
     * @see org.pivot4j.PivotModel#getEvaluatedMdx()
     */
    public String getEvaluatedMdx() {
        return getCurrentMdx(true);
    }

    /**
     * @param evaluated
     * @return
     */
    protected String getCurrentMdx(boolean evaluated) {
        if (queryAdapter == null) {
            return null;
        } else {
            return queryAdapter.getCurrentMdx(evaluated);
        }
    }

    /**
     * Returns the mdxQuery.
     * 
     * @return String
     */
    public String getMdx() {
        return mdxQuery;
    }

    /**
     * Sets the mdxQuery.
     * 
     * @param mdxQuery
     *            The mdxQuery to set
     */
    public void setMdx(String mdxQuery) {
        if (mdxQuery == null) {
            throw new NullArgumentException("mdxQuery");
        }

        this.mdxQuery = mdxQuery;

        String mdx = normalizeMdx(mdxQuery);
        if (!mdx.equals(normalizeMdx(getCurrentMdx()))) {
            onMdxChanged(mdx);
        }
    }

    /**
     * @param mdx
     */
    protected void onMdxChanged(String mdx) {
        if (logger.isInfoEnabled()) {
            logger.info("MDX has been changed : {}", mdx);
        }

        this.cellSet = null;
        this.scenarioSupported = null;
        this.topBottomCount = 10;
        this.sortCriteria = SortCriteria.ASC;
        this.sorting = false;
        this.sortPosMembers = null;

        if (queryAdapter != null) {
            queryAdapter.initialize();
            queryAdapter.updateQuery();
        }

        initializeCache();

        fireStructureChanged();
    }

    protected QueryAdapter createQueryAdapter() {
        return new QueryAdapter(this);
    }

    /**
     * Returns the queryAdapter.
     * 
     * @return QueryAdapter
     */
    protected QueryAdapter getQueryAdapter() {
        return queryAdapter;
    }

    /**
     * @return the transformFactory
     */
    public TransformFactory getTransformFactory() {
        return transformFactory;
    }

    /**
     * @param factory
     *            the transformFactory to set
     */
    public void setTransformFactory(TransformFactory factory) {
        this.transformFactory = factory;
    }

    /**
     * @return the expressionEvaluatorFactory
     * @see org.pivot4j.PivotModel#getExpressionEvaluatorFactory()
     */
    public ExpressionEvaluatorFactory getExpressionEvaluatorFactory() {
        return expressionEvaluatorFactory;
    }

    /**
     * @param factory
     *            the expressionEvaluatorFactory to set
     */
    public void setExpressionEvaluatorFactory(ExpressionEvaluatorFactory factory) {
        this.expressionEvaluatorFactory = factory;
    }

    /**
     * @see org.pivot4j.PivotModel#addModelChangeListener(org.pivot4j.ModelChangeListener)
     */
    public void addModelChangeListener(ModelChangeListener listener) {
        modelListeners.add(listener);
    }

    /**
     * @see org.pivot4j.PivotModel#removeModelChangeListener(org.pivot4j.ModelChangeListener)
     */
    public void removeModelChangeListener(ModelChangeListener listener) {
        modelListeners.remove(listener);
    }

    protected void fireModelInitialized() {
        ModelChangeEvent e = new ModelChangeEvent(this);

        List<ModelChangeListener> copiedListeners = new ArrayList<ModelChangeListener>(modelListeners);
        for (ModelChangeListener listener : copiedListeners) {
            listener.modelInitialized(e);
        }
    }

    protected void fireModelChanged() {
        this.cellSet = null;

        ModelChangeEvent e = new ModelChangeEvent(this);

        List<ModelChangeListener> copiedListeners = new ArrayList<ModelChangeListener>(modelListeners);
        for (ModelChangeListener listener : copiedListeners) {
            listener.modelChanged(e);
        }
    }

    protected void fireStructureChanged() {
        this.cellSet = null;

        ModelChangeEvent e = new ModelChangeEvent(this);

        List<ModelChangeListener> copiedListeners = new ArrayList<ModelChangeListener>(modelListeners);
        for (ModelChangeListener listener : copiedListeners) {
            listener.structureChanged(e);
        }
    }

    protected void fireModelDestroyed() {
        ModelChangeEvent e = new ModelChangeEvent(this);

        List<ModelChangeListener> copiedListeners = new ArrayList<ModelChangeListener>(modelListeners);
        for (ModelChangeListener listener : copiedListeners) {
            listener.modelDestroyed(e);
        }
    }

    /**
     * @see org.pivot4j.PivotModel#addQueryListener(org.pivot4j.QueryListener)
     */
    public void addQueryListener(QueryListener listener) {
        queryListeners.add(listener);
    }

    /**
     * @see org.pivot4j.PivotModel#removeQueryListener(org.pivot4j.QueryListener)
     */
    public void removeQueryListener(QueryListener listener) {
        queryListeners.remove(listener);
    }

    protected void fireQueryExecuted(Date start, long duration, String mdx) {
        QueryEvent e = new QueryEvent(this, start, duration, mdx, cellSet);

        List<QueryListener> copiedListeners = new ArrayList<QueryListener>(queryListeners);
        for (QueryListener listener : copiedListeners) {
            listener.queryExecuted(e);
        }
    }

    /**
     * @see org.pivot4j.PivotModel#getTransform(java.lang.Class)
     */
    public <T extends Transform> T getTransform(Class<T> type) {
        if (transformFactory == null) {
            throw new IllegalStateException("No transform factory instance is available.");
        }

        return transformFactory.createTransform(type, queryAdapter, connection);
    }

    /**
     * @see org.pivot4j.PivotModel#isSorting()
     */
    public boolean isSorting() {
        return sorting;
    }

    /**
     * @param position
     *            to be checked
     * @return true, if position is the current sorting position
     * @see org.pivot4j.PivotModel#isSorting(org.olap4j.Position)
     */
    public boolean isSorting(Position position) {
        if (!isSortOnQuery()) {
            return false;
        } else {
            if (sortPosMembers.size() != position.getMembers().size()) {
                return false;
            }

            for (int i = 0; i < sortPosMembers.size(); i++) {
                Member member1 = sortPosMembers.get(i);
                Member member2 = position.getMembers().get(i);
                // any null does not compare
                if (member1 == null) {
                    return false;
                } else if (!OlapUtils.equals(member1, member2)) {
                    return false;
                }
            }
            return true;
        }
    }

    /**
     * TODO Support for natural sorting
     * 
     * @see org.pivot4j.PivotModel#setSorting(boolean)
     */
    public void setSorting(boolean sorting) {
        if (sorting == this.sorting) {
            return;
        }

        if (logger.isInfoEnabled()) {
            logger.info("Change sorting to {}", sorting);
        }

        this.sorting = sorting;

        fireModelChanged();
    }

    /**
     * @return sort criteria (ASC,DESC,BASC,BDESC,TOPCOUNT,BOTTOMCOUNT)
     */
    public SortCriteria getSortCriteria() {
        return sortCriteria;
    }

    /**
     * @see org.pivot4j.PivotModel#setSortCriteria(org.pivot4j.sort.SortCriteria)
     */
    public void setSortCriteria(SortCriteria sortCriteria) {
        if (this.sortCriteria == sortCriteria) {
            return;
        }

        if (logger.isInfoEnabled()) {
            logger.info("Change sort mode from {} to {}", this.sortCriteria, sortCriteria);
        }

        this.sortCriteria = sortCriteria;

        if (isSortOnQuery()) {
            fireModelChanged();
        }
    }

    /**
     * returns true, if ONE of the members is a measure
     * 
     * @param position
     *            the position to check for sortability
     * @return true, if the position is sortable
     */
    public boolean isSortable(Position position) {
        try {
            List<Member> members = position.getMembers();
            for (Member member : members) {
                if (member.getLevel().getHierarchy().getDimension().getDimensionType() == Type.MEASURE) {
                    return true;
                }
            }
        } catch (OlapException e) {
            throw new PivotException(e);
        }

        return false;
    }

    /**
     * @return true, if there is a sort for the query
     */
    protected boolean isSortOnQuery() {
        return sorting && sortPosMembers != null && !sortPosMembers.isEmpty();
    }

    /**
     * @see org.pivot4j.PivotModel#getSortPosMembers()
     */
    public List<Member> getSortPosMembers() {
        return Collections.unmodifiableList(sortPosMembers);
    }

    /**
     * @return top/bottom count
     * @see org.pivot4j.PivotModel#getTopBottomCount()
     */
    public int getTopBottomCount() {
        return topBottomCount;
    }

    /**
     * @see org.pivot4j.PivotModel#setTopBottomCount(int)
     */
    public void setTopBottomCount(int topBottomCount) {
        if (this.topBottomCount == topBottomCount) {
            return;
        }

        if (logger.isInfoEnabled()) {
            logger.info("Change topBottomCount from {} to {}", this.topBottomCount, topBottomCount);
        }

        this.topBottomCount = topBottomCount;

        if (sorting && sortPosMembers != null
                && (sortCriteria == SortCriteria.TOPCOUNT || sortCriteria == SortCriteria.BOTTOMCOUNT)) {
            fireModelChanged();
        }
    }

    /**
     * @param axisToSort
     *            Axis containing the members to be sorted
     * @param position
     *            Position on "other axis" defining the members by which the
     *            membersToSort are sorted
     */
    public void sort(CellSetAxis axisToSort, Position position) {
        List<Position> positions = axisToSort.getPositions();

        // if the axis to sort does not contain any positions - sorting is not
        // posssible
        if (positions.isEmpty()) {
            if (logger.isWarnEnabled()) {
                logger.warn("Reject sort, the axis to be sorted is empty.");
            }

            this.sorting = false;
            return;
        }

        this.sortPosMembers = position.getMembers();

        // find the axis to sort
        Dimension dim = positions.get(0).getMembers().get(0).getDimension();

        Quax quaxToSort = getQueryAdapter().findQuax(dim);

        if (quaxToSort == null) {
            if (logger.isWarnEnabled()) {
                logger.warn("Reject sort, the Quax is null");
            }
            this.sorting = false;
            return;
        }

        getQueryAdapter().setQuaxToSort(quaxToSort);

        if (logger.isInfoEnabled()) {
            StringBuilder builder = new StringBuilder();
            builder.append("Change Sort Position ");

            boolean first = true;

            List<Member> members = position.getMembers();
            for (Member member : members) {
                if (first) {
                    first = false;
                } else {
                    builder.append(" ");
                }
                builder.append(member.getUniqueName());
            }
            builder.append(" iAxisToSort=");
            builder.append(Integer.toString(quaxToSort.getOrdinal()));

            logger.info(builder.toString());
        }

        fireModelChanged();
    }

    /**
     * @see org.pivot4j.PivotModel#getDefaultNonEmpty()
     */
    @Override
    public boolean getDefaultNonEmpty() {
        return defaultNonEmpty;
    }

    /**
     * @see org.pivot4j.PivotModel#setDefaultNonEmpty(boolean)
     */
    @Override
    public void setDefaultNonEmpty(boolean defaultNonEmpty) {
        this.defaultNonEmpty = defaultNonEmpty;
    }

    /**
     * @see org.pivot4j.state.Bookmarkable#saveState()
     */
    @Override
    public synchronized Serializable saveState() {
        Serializable[] state = new Serializable[4];

        if (isInitialized()) {
            state[0] = getCurrentMdx(false);
        } else {
            state[0] = mdxQuery;
        }

        if (sortPosMembers == null) {
            state[1] = null;
        } else {
            Serializable[] sortState = new Serializable[4];

            String[] uniqueNames = new String[sortPosMembers.size()];
            for (int i = 0; i < uniqueNames.length; i++) {
                uniqueNames[i] = sortPosMembers.get(i).getUniqueName();
            }

            sortState[0] = uniqueNames;
            sortState[1] = getTopBottomCount();
            sortState[2] = getSortCriteria();
            sortState[3] = isSorting();

            state[1] = sortState;
        }

        state[2] = getQueryAdapter().saveState();
        state[3] = defaultNonEmpty;

        return state;
    }

    /**
     * @see org.pivot4j.state.Bookmarkable#restoreState(java.io.Serializable)
     */
    public synchronized void restoreState(Serializable state) {
        if (state == null) {
            throw new NullArgumentException("state");
        }

        Serializable[] states = (Serializable[]) state;

        setMdx((String) states[0]);

        if (!isInitialized()) {
            initialize();
        }

        // sorting
        if (states[1] == null) {
            this.sortPosMembers = null;
        } else {
            try {
                Cube cube = getCube();

                Serializable[] sortStates = (Serializable[]) states[1];

                String[] sortPosUniqueNames = (String[]) sortStates[0];
                if (sortPosUniqueNames == null) {
                    this.sortPosMembers = null;
                } else {
                    this.sortPosMembers = new ArrayList<Member>(sortPosUniqueNames.length);

                    for (int i = 0; i < sortPosUniqueNames.length; i++) {
                        Member member = cube.lookupMember(
                                IdentifierNode.parseIdentifier(sortPosUniqueNames[i]).getSegmentList());
                        if (member == null) {
                            if (logger.isWarnEnabled()) {
                                logger.warn("Sort position member not found {}", sortPosUniqueNames[i]);
                            }

                            break;
                        }

                        sortPosMembers.add(member);
                    }

                    this.topBottomCount = (Integer) sortStates[1];
                    this.sortCriteria = (SortCriteria) sortStates[2];
                    this.sorting = (Boolean) sortStates[3];
                }
            } catch (OlapException e) {
                throw new PivotException(e);
            }
        }

        this.cellSet = null;

        queryAdapter.restoreState(states[2]);

        this.defaultNonEmpty = (Boolean) states[3];
    }

    /**
     * @see org.pivot4j.state.Configurable#saveSettings(org.apache.commons.configuration.HierarchicalConfiguration)
     */
    @Override
    public synchronized void saveSettings(HierarchicalConfiguration configuration) {
        if (configuration == null) {
            throw new NullArgumentException("configuration");
        }

        if (configuration.getLogger() == null) {
            configuration.setLogger(LogFactory.getLog(getClass()));
        }

        if (isInitialized()) {
            configuration.addProperty("mdx", getCurrentMdx());
        } else {
            configuration.addProperty("mdx", mdxQuery);
        }

        if (sorting) {
            configuration.addProperty("sort[@enabled]", sorting);

            if (queryAdapter.getQuaxToSort() != null) {
                configuration.addProperty("sort[@ordinal]", queryAdapter.getQuaxToSort().getOrdinal());
            }

            if (sortCriteria != null) {
                configuration.addProperty("sort[@criteria]", sortCriteria.name());
                configuration.addProperty("sort[@topBottomCount]", topBottomCount);
                if (isSorting() && sortPosMembers != null) {
                    int index = 0;
                    for (Member member : sortPosMembers) {
                        configuration.addProperty(String.format("sort.member(%s)", index++),
                                member.getUniqueName());
                    }
                }
            }
        }

        if (queryAdapter != null && queryAdapter.isAxesSwapped()) {
            configuration.addProperty("axesSwapped", true);
        }

        // TODO This setting can potentially be present in the
        // pivot4j-config.xml, in which case, it would be saved with the report
        // thus making subsequent changing of the default value ineffective.
        // We should wait till we find a way to handle such a scenario better
        // later.
        //
        // if (defaultNonEmpty) {
        // configuration.addProperty("nonEmpty[@default]", true);
        // }
    }

    /**
     * @see org.pivot4j.state.Configurable#restoreSettings(org.apache.commons.configuration.HierarchicalConfiguration)
     */
    @Override
    public synchronized void restoreSettings(HierarchicalConfiguration configuration) {
        if (configuration == null) {
            throw new NullArgumentException("configuration");
        }

        this.defaultNonEmpty = configuration.getBoolean("nonEmpty[@default]", false);

        String mdx = configuration.getString("mdx");

        setMdx(mdx);

        if (mdx == null) {
            return;
        }

        if (!isInitialized()) {
            initialize();
        }

        this.sorting = configuration.getBoolean("sort[@enabled]", false);

        this.sortPosMembers = null;
        this.sortCriteria = SortCriteria.ASC;
        this.topBottomCount = 10;

        Quax quaxToSort = null;

        if (sorting) {
            List<Object> sortPosUniqueNames = configuration.getList("sort.member");
            if (sortPosUniqueNames != null && !sortPosUniqueNames.isEmpty()) {
                try {
                    Cube cube = getCube();

                    this.sortPosMembers = new ArrayList<Member>(sortPosUniqueNames.size());

                    for (Object uniqueName : sortPosUniqueNames) {
                        Member member = cube.lookupMember(
                                IdentifierNode.parseIdentifier(uniqueName.toString()).getSegmentList());
                        if (member == null) {
                            if (logger.isWarnEnabled()) {
                                logger.warn("Sort position member not found " + uniqueName);
                            }

                            break;
                        }

                        sortPosMembers.add(member);
                    }
                } catch (OlapException e) {
                    throw new PivotException(e);
                }
            }

            this.topBottomCount = configuration.getInt("sort[@topBottomCount]", 10);

            String sortName = configuration.getString("sort[@criteria]");
            if (sortName != null) {
                this.sortCriteria = SortCriteria.valueOf(sortName);
            }

            int ordinal = configuration.getInt("sort[@ordinal]", -1);

            if (ordinal > 0) {
                for (Axis axis : queryAdapter.getAxes()) {
                    Quax quax = queryAdapter.getQuax(axis);
                    if (quax.getOrdinal() == ordinal) {
                        quaxToSort = quax;
                        break;
                    }
                }
            }
        }

        queryAdapter.setQuaxToSort(quaxToSort);

        boolean axesSwapped = configuration.getBoolean("axesSwapped", false);

        queryAdapter.setAxesSwapped(axesSwapped);

        this.cellSet = null;
    }
}