org.polymap.core.qi4j.QiModule.java Source code

Java tutorial

Introduction

Here is the source code for org.polymap.core.qi4j.QiModule.java

Source

/*
 * polymap.org
 * Copyright 2009-2011, Falko Brutigam, and individual contributors as
 * indicated by the @authors tag. All rights reserved.
 *
 * This is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 *
 * This software is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 */
package org.polymap.core.qi4j;

import java.util.EventObject;

import java.beans.PropertyChangeListener;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.qi4j.api.entity.EntityBuilder;
import org.qi4j.api.query.Query;
import org.qi4j.api.query.QueryBuilder;
import org.qi4j.api.query.grammar.BooleanExpression;
import org.qi4j.api.unitofwork.ConcurrentEntityModificationException;
import org.qi4j.api.unitofwork.EntityTypeNotFoundException;
import org.qi4j.api.unitofwork.NoSuchEntityException;
import org.qi4j.api.unitofwork.UnitOfWork;
import org.qi4j.api.unitofwork.UnitOfWorkCompletionException;
import org.qi4j.api.unitofwork.UnitOfWorkException;
import org.qi4j.api.value.ValueBuilder;

import com.google.common.collect.ObjectArrays;

import org.eclipse.core.commands.operations.IUndoableOperation;
import org.eclipse.core.runtime.IProgressMonitor;

import org.polymap.core.model.CompletionException;
import org.polymap.core.model.Composite;
import org.polymap.core.model.Entity;
import org.polymap.core.model.EntityType;
import org.polymap.core.model.Module;
import org.polymap.core.model.event.ModelChangeEvent;
import org.polymap.core.model.security.ACL;
import org.polymap.core.model.security.ACLUtils;
import org.polymap.core.model.security.AclPermission;
import org.polymap.core.operation.IOperationSaveListener;
import org.polymap.core.operation.OperationSupport;
import org.polymap.core.qi4j.Qi4jPlugin.Session;
import org.polymap.core.qi4j.event.PropertyChangeSupport;
import org.polymap.core.runtime.ISessionListener;
import org.polymap.core.runtime.SessionContext;
import org.polymap.core.runtime.entity.ConcurrentModificationException;
import org.polymap.core.runtime.entity.EntityStateEvent;
import org.polymap.core.runtime.event.EventFilter;
import org.polymap.core.runtime.event.EventManager;
import org.polymap.core.workbench.PolymapWorkbench;

/**
 * Provides the implementation of a module of the Qi4J model. There is one
 * module instance per user session.
 * <p/>
 * QiModule implements {@link PropertyChangeListener} so that changes to
 * entities can be signaled to the module. This can be done inside an
 * {@link IUndoableOperation} or via the {@link PropertyChangeSupport} provided
 * by an entity.
 *
 * @author <a href="http://www.polymap.de">Falko Braeutigam</a>
 * @since 3.0
 */
public abstract class QiModule implements Module {

    private static Log log = LogFactory.getLog(QiModule.class);

    public static final int DEFAULT_MAX_RESULTS = 10000000;

    // instance *******************************************

    protected QiModuleAssembler assembler;

    protected UnitOfWork uow;

    protected QiModule(QiModuleAssembler assembler) {
        this.assembler = assembler;
        this.uow = assembler.getModule().unitOfWorkFactory().newUnitOfWork();

        final SessionContext sessionContext = SessionContext.current();
        if (sessionContext != null) {
            sessionContext.addSessionListener(new ISessionListener() {
                public void beforeDestroy() {
                    sessionContext.removeSessionListener(this);
                    log.info("Session closed: removing module...");
                    dispose();
                }
            });
        }
    }

    /**
     * During init a module can access other modules of the {@link Session} via
     * {@link Session#module(Class)}.
     *
     * @param session 
     */
    public void init(Session session) {
    }

    @Override
    protected void finalize() throws Throwable {
        dispose();
    }

    protected void dispose() {
        if (uow != null) {
            uow.discard();
            uow = null;
        }
        assembler = null;
    }

    public void commitChanges() throws ConcurrentModificationException, CompletionException {
        try {
            // save changes
            uow.apply();
        } catch (ConcurrentEntityModificationException e) {
            throw new ConcurrentModificationException(e);
        } catch (UnitOfWorkCompletionException e) {
            throw new CompletionException(e);
        }
    }

    public void revertChanges() {
        try {
            uow.revert();
        } catch (UnitOfWorkCompletionException e) {
            throw new RuntimeException(e);
        }

        // where do we get the list of changed entities?
        // then we could create a new UoW and get the old state from;
        // just creating new UoW does not send events and nothing gets updated
    }

    /**
     * 
     */
    public class OperationSaveListener implements IOperationSaveListener {

        public void prepareSave(OperationSupport os, IProgressMonitor monitor) throws Exception {
            //
        }

        public void save(OperationSupport os, IProgressMonitor monitor) {
            try {
                monitor.beginTask(getClass().getSimpleName(), 1);
                commitChanges();
            } catch (Exception e) {
                PolymapWorkbench.handleError(Qi4jPlugin.PLUGIN_ID, this,
                        "Die nderungen konnten nicht gespeichert werden.\nDie Daten sind mglicherweise in einem inkonsistenten Zustand.\nBitte verstndigen Sie den Administrator.",
                        e);
            }
        }

        public void rollback(OperationSupport os, IProgressMonitor monitor) {
            // no prepare -> no rollback
        }

        public void revert(OperationSupport os, IProgressMonitor monitor) {
            monitor.beginTask(getClass().getSimpleName(), 1);
            revertChanges();
        }
    }

    // events ***

    /**
     * 
     */
    public void addEntityListener(Object handler, EventFilter... filters) {
        EventManager.instance().subscribe(handler, ObjectArrays.concat(new EventFilter<EventObject>() {
            public boolean apply(EventObject ev) {
                // ModelChangeEvent (dies not have entity as source)
                if (ev instanceof ModelChangeEvent) {
                    return true;
                }
                //
                else if (ev instanceof EntityStateEvent) {
                    // XXX is there a way to check if entity of this module are changed
                    return true;
                }
                // PropertyChangeEvent
                else if (ev.getSource() instanceof QiEntity) {
                    QiEntity entity = (QiEntity) ev.getSource();
                    try {
                        // check if entity is part of this module
                        findEntity(entity.getCompositeType(), entity.id());
                        return true;
                    } catch (UnitOfWorkException e) {
                        return false;
                    }
                }
                return false;
            }
        }, filters));
    }

    public void removeEntityListener(Object handler) {
        EventManager.instance().unsubscribe(handler);
    }

    protected boolean appliesTo(org.qi4j.api.structure.Module rhs) {
        return assembler.getModule().equals(rhs);
    }

    // factory methods ***

    /**
     *
     * @param <T>
     * @param type
     * @param id The id of the newly created entity; null specifies that the
     *        system creates a unique id.
     * @return The newly created entity.
     */
    public <T> T newEntity(Class<T> type, String id) {
        T result = uow.newEntity(type, id);
        return result;
    }

    /**
     *
     * @param <T>
     * @param type
     * @param id
     * @param creator
     * @return The newly created entity
     * @throws Exception If an exception occured while executing the creator.
     */
    public <T> T newEntity(Class<T> type, String id, EntityCreator<T> creator) throws Exception {
        EntityBuilder<T> builder = uow.newEntityBuilder(type);
        creator.create(builder.instance());
        return builder.newInstance();
    }

    /**
     * Functor for the {@link QiModule#newEntity(Class, String, EntityCreator)} method.
     */
    public interface EntityCreator<T> {

        public void create(T prototype) throws Exception;

    }

    public <T> ValueBuilder<T> newValueBuilder(Class<T> type) {
        return assembler.getModule().valueBuilderFactory().newValueBuilder(type);
    }

    /**
     *
     * @param entity
     */
    public void removeEntity(Entity entity) {
        if (entity instanceof ACL) {
            ACLUtils.checkPermission((ACL) entity, AclPermission.DELETE, true);
        }

        // XXX add removed entities to current operation/ModelChangeEvent?
        //        ModelChangeEvent ev = operationEvents.get( Thread.currentThread() );
        //        if (ev != null) {
        //            ev.addRemoved( entity.id() );
        //        }
        uow.remove(entity);
    }

    /**
     * Update the state of the given entity. If the global state has changed
     * (entity was commited by another session), then the entity is loaded from
     * store. If the entity has locally changed, then these changes are merged
     * with the global changes. If one property was concurrently changed, then a
     * {@link ConcurrentModificationException} is thrown.
     */
    public void updateEntity(Entity entity) throws ConcurrentModificationException {
        throw new RuntimeException("not yet implemented.");
    }

    /**
     * Find an Entity of the given mixin type with the give identity. This
     * method verifies that it exists by asking the underlying EntityStore.
     *
     * @return the entity
     * @throws EntityTypeNotFoundException If no entity type could be found
     * @throws NoSuchEntityException
     */
    public <T> T findEntity(Class<T> type, String id) {
        return uow.get(type, id);
    }

    /**
     *
     * @param <T>
     * @param compositeType
     * @param expression The query, or null if all entities are to be fetched.
     * @param firstResult The first result index, 0 by default.
     * @param maxResults The maximum number of entities in the result; -1
     *        signals that there si no limit.
     * @return The newly created query.
     */
    public <T> Query<T> findEntities(Class<T> compositeType, BooleanExpression expression, int firstResult,
            int maxResults) {
        if (maxResults < 0) {
            maxResults = DEFAULT_MAX_RESULTS;
        }
        if (maxResults > DEFAULT_MAX_RESULTS) {
            maxResults = DEFAULT_MAX_RESULTS;
        }

        QueryBuilder<T> builder = assembler.getModule().queryBuilderFactory().newQueryBuilder(compositeType);

        builder = expression != null ? builder.where(expression) : builder;

        Query<T> query = builder.newQuery(uow).maxResults(maxResults).firstResult(firstResult);
        return query;
    }

    //    /**
    //     * Creates a new operation of the given type.
    //     * <p>
    //     * The caller must ensure to initialize the operation properly.
    //     *
    //     * @param type Class that extends {@link IUndoableOperation}.
    //     */
    //    public <T> T newOperation( Class<T> type ) {
    //        T result = assembler.getModule().transientBuilderFactory().newTransient( type );
    //        return result;
    //    }

    /**
     * Creates a new {@link EntityType} instance for the given {@link Entity}
     * class. The return value should be cached and reused if possible.
     */
    public <T extends Composite> EntityType<T> entityType(Class<T> type) {
        return EntityTypeImpl.forClass(type);
    }

}