org.mondo.collaboration.security.lens.arbiter.LockArbiter.java Source code

Java tutorial

Introduction

Here is the source code for org.mondo.collaboration.security.lens.arbiter.LockArbiter.java

Source

/*******************************************************************************
 * Copyright (c) 2004-2015 Gabor Bergmann and Daniel Varro
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Gabor Bergmann - initial API and implementation
 *******************************************************************************/

package org.mondo.collaboration.security.lens.arbiter;

import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.incquery.patternlanguage.patternLanguage.Pattern;
import org.eclipse.incquery.runtime.api.AdvancedIncQueryEngine;
import org.eclipse.incquery.runtime.api.GenericPatternGroup;
import org.eclipse.incquery.runtime.api.IMatchUpdateListener;
import org.eclipse.incquery.runtime.api.IPatternMatch;
import org.eclipse.incquery.runtime.api.IQuerySpecification;
import org.eclipse.incquery.runtime.api.IncQueryMatcher;
import org.eclipse.incquery.runtime.exception.IncQueryException;
import org.mondo.collaboration.security.mpbl.xtext.mondoPropertyBasedLocking.Bind;
import org.mondo.collaboration.security.mpbl.xtext.mondoPropertyBasedLocking.Binding;
import org.mondo.collaboration.security.mpbl.xtext.mondoPropertyBasedLocking.Group;
import org.mondo.collaboration.security.mpbl.xtext.mondoPropertyBasedLocking.Lock;
import org.mondo.collaboration.security.mpbl.xtext.mondoPropertyBasedLocking.MondoPropertyBasedLockingFactory;
import org.mondo.collaboration.security.mpbl.xtext.mondoPropertyBasedLocking.PropertyBasedLockingModel;
import org.mondo.collaboration.security.mpbl.xtext.mondoPropertyBasedLocking.Role;
import org.mondo.collaboration.security.mpbl.xtext.mondoPropertyBasedLocking.User;
import org.mondo.collaboration.security.mpbl.xtext.mondoPropertyBasedLocking.ValueBind;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multimap;

/**
 * Enforces locks.
 * 
 * For each active user operation on the gold model (such as a PUTBACK), a session must be opened.
 * 
 * @author Bergmann Gabor
 *
 */
public class LockArbiter {

    public static LockArbiter create(SecurityArbiter arbiter, Resource lockResource) throws IncQueryException {
        return new LockArbiter(arbiter, extractLockingResource(lockResource));
    }

    private static PropertyBasedLockingModel extractLockingResource(Resource lockResource) {
        return lockResource == null ? null : (PropertyBasedLockingModel) lockResource.getContents().get(0);
    }

    private SecurityArbiter secArbiter;
    private PropertyBasedLockingModel lockingModel;

    private Map<Lock, Set<String>> lockOwnerNames;
    // a lock is only relevant for an active user if they are not the (co)owner
    private Map<String, Set<Lock>> locksRelevantForUser;

    private Map<Lock, IncQueryMatcher<? extends IPatternMatch>> matcherForLock;
    private Map<Lock, IPatternMatch> bindingForLock;

    /**
     * @param secArbiter the security arbiter
     * @param lockingModel the initial locking model. May be null if there are no locks.
     * @throws IncQueryException
     */
    public LockArbiter(SecurityArbiter secArbiter, PropertyBasedLockingModel lockingModel)
            throws IncQueryException {
        this.secArbiter = secArbiter;
        reinitializeWith(lockingModel);
        //Set<RuleSpecification<IPatternMatch>> lockEnforcingRules = new HashSet<>();
    }

    /**
     * Call if there is a new locking model to parse. 
     * Do not call while there is an active {@link LockMonitoringSession}.
     * @param lockResource the resource of the new locking model. May be null if there are no locks.
     */
    public void reinitializeWith(Resource lockResource) throws IncQueryException {
        reinitializeWith(extractLockingResource(lockResource));
    }

    /**
     * Call if there is a new locking model to parse. 
     * Do not call while there is an active {@link LockMonitoringSession}.
     * @param lockingModel the new locking model. May be null if there are no locks.
     */
    public void reinitializeWith(PropertyBasedLockingModel lockingModel) throws IncQueryException {
        if (lockingModel == null)
            lockingModel = dummyLockingModel();
        this.lockingModel = lockingModel;

        lockOwnerNames = new HashMap<Lock, Set<String>>();
        locksRelevantForUser = new HashMap<String, Set<Lock>>();
        matcherForLock = new HashMap<Lock, IncQueryMatcher<? extends IPatternMatch>>();
        bindingForLock = new HashMap<Lock, IPatternMatch>();

        Multimap<IQuerySpecification<?>, Lock> lockQueries = HashMultimap.create();
        for (Lock lock : lockingModel.getLocks()) {
            preprocessLock(lock, lockQueries);
        }

        preInitializeMatchers(lockQueries);
    }

    private AdvancedIncQueryEngine getQueryEngine() {
        return this.secArbiter.getPolicyQueryEngine();
    }

    // if no locking model is provided, an empty one is used instead, with no locks
    private static PropertyBasedLockingModel dummyLockingModel() {
        return MondoPropertyBasedLockingFactory.eINSTANCE.createPropertyBasedLockingModel();
    }

    private void preprocessLock(Lock lock, Multimap<IQuerySpecification<?>, Lock> lockQueryAccumulator)
            throws IncQueryException {
        final Pattern pattern = lock.getPattern();
        if (pattern == null || pattern.eIsProxy())
            throw new IllegalArgumentException("Cannot resolve query of lock: " + lock);

        lockOwnerNames.put(lock, new HashSet<String>());
        registerLockOwnership(lock, lock.getOwner());

        IQuerySpecification<? extends IncQueryMatcher<? extends IPatternMatch>> query = secArbiter.getSpecBuilder()
                .getOrCreateSpecification(pattern);
        lockQueryAccumulator.put(query, lock);

        IPatternMatch bindingMatch = query.newEmptyMatch();
        for (Binding binding : lock.getBindings()) {
            Bind bindingValue = binding.getValue();
            if (bindingValue instanceof ValueBind) {
                String value = ((ValueBind) bindingValue).getValue();
                bindingMatch.set(binding.getParam().getName(), value);
            } else
                throw new UnsupportedOperationException("Unsupported binding " + bindingValue + " in lock " + lock);
        }
        bindingForLock.put(lock, bindingMatch.toImmutable());
    }

    private void registerLockOwnership(Lock lock, Role owner) {
        if (lockOwnerNames.get(lock).add(owner.getName())) {
            if (owner instanceof Group) {
                for (User user : ((Group) owner).getUsers()) {
                    registerLockOwnership(lock, user);
                }
            }
        }
    }

    private void preInitializeMatchers(Multimap<IQuerySpecification<?>, Lock> lockQueries)
            throws IncQueryException {
        if (!lockQueries.isEmpty())
            new GenericPatternGroup(lockQueries.keySet()).prepare(getQueryEngine());
        for (IQuerySpecification<?> lockQuery : lockQueries.keySet()) {
            IncQueryMatcher<? extends IPatternMatch> matcher = getQueryEngine().getMatcher(lockQuery);
            for (Lock lock : lockQueries.get(lockQuery)) {
                matcherForLock.put(lock, matcher);
            }
        }
    }

    private Set<Lock> getLocksRelevantForUser(String activeUserName) {
        Set<Lock> relevantLocks = locksRelevantForUser.get(activeUserName);
        if (relevantLocks == null) {
            relevantLocks = new HashSet<Lock>();
            for (Lock lock : lockingModel.getLocks()) {
                if (!lockOwnerNames.get(lock).contains(activeUserName))
                    relevantLocks.add(lock);
            }
            locksRelevantForUser.put(activeUserName, relevantLocks);
        }
        return relevantLocks;
    }

    public LockMonitoringSession openSession(String activeUserName) {
        return new LockMonitoringSession(activeUserName);
    }

    public final class LockMonitoringSession implements AutoCloseable {
        private String activeUserName;
        private EnumMap<UpdateDirection, Multimap<Lock, IPatternMatch>> delta = new EnumMap<LockArbiter.UpdateDirection, Multimap<Lock, IPatternMatch>>(
                ImmutableMap.of(UpdateDirection.ADD, HashMultimap.create(), UpdateDirection.REMOVE,
                        HashMultimap.create()));
        Multimap<IncQueryMatcher<? extends IPatternMatch>, MatchUpdateListener> listeners = HashMultimap.create();

        public LockMonitoringSession(String activeUserName) {
            this.activeUserName = activeUserName;

            for (Lock lock : getLocksRelevantForUser(activeUserName)) {
                initLockMonitoringRule(lock);
            }

        }

        public Entry<Lock, IPatternMatch> getAnyLockViolation() {
            if (delta.get(UpdateDirection.ADD).isEmpty()) {
                if (delta.get(UpdateDirection.REMOVE).isEmpty()) {
                    return null;
                } else {
                    return delta.get(UpdateDirection.REMOVE).entries().iterator().next();
                }
            } else {
                return delta.get(UpdateDirection.ADD).entries().iterator().next();
            }
        }

        @Override
        public void close() throws Exception {
            for (Entry<IncQueryMatcher<? extends IPatternMatch>, MatchUpdateListener> entry : listeners.entries()) {
                getQueryEngine().removeMatchUpdateListener(entry.getKey(), entry.getValue());
            }
        }

        private void initLockMonitoringRule(final Lock lock) { //, Set<RuleSpecification<IPatternMatch>> lockEnforcingRuleAccumulator) {
            MatchUpdateListener listener = new MatchUpdateListener(lock);
            IncQueryMatcher<? extends IPatternMatch> matcher = matcherForLock.get(lock);

            getQueryEngine().addMatchUpdateListener(matcher, listener, false);
            listeners.put(matcher, listener);
        }

        private final class MatchUpdateListener implements IMatchUpdateListener<IPatternMatch> {
            private Lock lock;
            private IPatternMatch bindingMatch;

            @Override
            public void notifyAppearance(IPatternMatch match) {
                processChange(match, UpdateDirection.ADD);
            }

            @Override
            public void notifyDisappearance(IPatternMatch match) {
                processChange(match, UpdateDirection.REMOVE);
            }

            private void processChange(IPatternMatch match, UpdateDirection direction) {
                if (match.isCompatibleWith(bindingMatch)) {
                    // update the delta data structure
                    if (!delta.get(direction.opposite()).remove(lock, match))
                        delta.get(direction).put(lock, match);
                }
            }

            public MatchUpdateListener(Lock lock) {
                this.lock = lock;
                bindingMatch = bindingForLock.get(lock);
            }

            @Override
            public int hashCode() {
                final int prime = 31;
                int result = 1;
                result = prime * result + getOuterType().hashCode();
                result = prime * result + ((lock == null) ? 0 : lock.hashCode());
                return result;
            }

            @Override
            public boolean equals(Object obj) {
                if (this == obj)
                    return true;
                if (obj == null)
                    return false;
                if (getClass() != obj.getClass())
                    return false;
                MatchUpdateListener other = (MatchUpdateListener) obj;
                if (!getOuterType().equals(other.getOuterType()))
                    return false;
                if (lock == null) {
                    if (other.lock != null)
                        return false;
                } else if (!lock.equals(other.lock))
                    return false;
                return true;
            }

            private LockMonitoringSession getOuterType() {
                return LockMonitoringSession.this;
            }

        }

    }

    static enum UpdateDirection {
        ADD, REMOVE;

        public UpdateDirection opposite() {
            switch (this) {
            case ADD:
                return REMOVE;
            case REMOVE:
                return ADD;
            }
            return null;
        }
    }

    public Map<Lock, Set<String>> getLockOwnerNames() {
        return lockOwnerNames;
    }

}