tools.xor.ObjectPersister.java Source code

Java tutorial

Introduction

Here is the source code for tools.xor.ObjectPersister.java

Source

/**
 * XOR, empowering Model Driven Architecture in J2EE applications
 *
 * Copyright (c) 2012, Dilip Dalton
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *
 * See the License for the specific language governing permissions and limitations 
 * under the License.
 */

package tools.xor;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import tools.xor.action.CollectionUpdateAction;
import tools.xor.action.ElementAction;
import tools.xor.action.Executable;
import tools.xor.action.MigratorActionFactory;
import tools.xor.action.PropertyKey;
import tools.xor.action.SetterAction;
import tools.xor.util.ClassUtil;

@Component
@Scope(value = "prototype")
public class ObjectPersister {
    private static final Logger logger = LogManager.getLogger(new Exception().getStackTrace()[0].getClassName());

    // Includes add, remove and reposition collection elements
    private Map<PropertyKey, List<Executable>> propertyActions = new Object2ObjectOpenHashMap<PropertyKey, List<Executable>>();
    private Map<PropertyKey, List<Executable>> deferredActions = new Object2ObjectOpenHashMap<PropertyKey, List<Executable>>();

    public void processActions(Settings settings) {
        Map<PropertyKey, List<Executable>> immediateActions = new Object2ObjectOpenHashMap<PropertyKey, List<Executable>>();
        for (Map.Entry<PropertyKey, List<Executable>> entry : propertyActions.entrySet()) {
            if (entry.getKey().getProperty().isOpenContent()) {
                deferredActions.put(entry.getKey(), entry.getValue());
            } else {
                immediateActions.put(entry.getKey(), entry.getValue());
            }
        }

        process(settings, immediateActions);
    }

    public void processOpenPropertyActions(Settings settings) {
        process(settings, deferredActions);
    }

    public void process(Settings settings, Map<PropertyKey, List<Executable>> currentActions) {
        // Process the uni-directional actions
        Set<PropertyKey> uniDirKeys = new HashSet<PropertyKey>();
        for (PropertyKey key : currentActions.keySet()) {
            if (!((ExtendedProperty) key.getProperty()).isBiDirectional())
                uniDirKeys.add(key);
        }
        for (PropertyKey uniDirKey : uniDirKeys) {
            List<Executable> actions = currentActions.remove(uniDirKey);
            processActions(uniDirKey, actions);
        }

        // Currently used by tests for immediate actions
        if (currentActions != deferredActions) {
            settings.getInterceptor().preBiDirActionStage(Collections.unmodifiableMap(currentActions));
        }

        for (Map.Entry<PropertyKey, List<Executable>> entry : currentActions.entrySet())
            processActions(entry.getKey(), currentActions.get(entry.getKey()));
    }

    private void processActions(PropertyKey key, List<Executable> actions) {
        if (key.getProperty().isMany()) {
            for (Executable action : actions) {
                action.execute();
            }
        } else
            processToOne(actions);
    }

    public void processToOne(List<Executable> actions) {
        // We follow the system where we execute the action that has a non-null value if one is present or execute the null setting action
        // This policy is present to automatically fix broken links
        // The other policy we can have (maybe through configuration) is to throw an exception if a conflict is detected.
        // A conflict can occur if two actions set different values. It is still desirable to throw an exception if two different non-null values are being set.

        SetterAction actionToExecute = null;
        for (Executable action : actions) {
            SetterAction setterAction = (SetterAction) action;
            if (actionToExecute == null)
                actionToExecute = setterAction;
            if (ClassUtil.getInstance(setterAction.getValue()) != null) {
                if (ClassUtil.getInstance(actionToExecute.getValue()) != null && ClassUtil
                        .getInstance(actionToExecute.getValue()) != ClassUtil.getInstance(setterAction.getValue()))
                    throw new IllegalArgumentException(
                            "Two different objects cannot share a reference to the same object in a ToOne bi-directional relationship.");
                else
                    actionToExecute = setterAction;

            }
        }
        actionToExecute.execute();
    }

    /**
     * If this is a element action, the migrator action is found and the element action is recorded on it
     * @param action update action object
     */
    public void addAction(Executable action) {

        List<Executable> actions = getActions(action.getKey());

        if (ElementAction.class.isAssignableFrom(action.getClass())) {
            CollectionUpdateAction collectionAction = getOrCreateMigratorAction(action.getKey());
            collectionAction.addTriggeredByOppositeAction((ElementAction) action);
        } else {
            actions.add(action);
        }
    }

    private List<Executable> getActions(PropertyKey key) {
        List<Executable> actions = propertyActions.get(key);
        if (actions == null) {
            actions = new ArrayList<Executable>();
            propertyActions.put((PropertyKey) key, actions);
        }

        return actions;
    }

    public CollectionUpdateAction getOrCreateMigratorAction(PropertyKey key) {
        // actions should not be null
        List<Executable> actions = getActions(key);

        CollectionUpdateAction migrator = null;
        if (actions.size() == 0) {
            migrator = MigratorActionFactory.getInstance(key);
            addAction(migrator);
            return migrator;
        }

        if (actions.size() == 1) {
            migrator = (CollectionUpdateAction) actions.get(0);
        } else
            throw new IllegalStateException("Collection property should have only one migrator action");

        return migrator;
    }

}