Package org.exist.config

Source Code of org.exist.config.ConfigurationDocumentTrigger$PreAllocatedIdReceiver

/*
*  eXist Open Source Native XML Database
*  Copyright (C) 2014 The eXist Project
*  http://exist-db.org
*  This program 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
*  of the License, or (at your option) any later version.
*  This program 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.
*  You should have received a copy of the GNU Lesser General Public License
*  along with this program; if not, write to the Free Software
*  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*  $Id$
*/
package org.exist.config;

import java.util.*;

import org.apache.log4j.Logger;
import org.exist.EXistException;
import org.exist.collections.Collection;
import org.exist.collections.triggers.DeferrableFilteringTrigger;
import org.exist.collections.triggers.TriggerException;
import org.exist.dom.DocumentImpl;
import org.exist.dom.ElementAtExist;
import org.exist.security.*;
import org.exist.security.SecurityManager;
import org.exist.security.internal.RealmImpl;
import org.exist.security.utils.ConverterFrom1_0;
import org.exist.storage.DBBroker;
import org.exist.storage.txn.Txn;
import org.exist.util.sax.event.SAXEvent;
import org.exist.util.sax.event.contenthandler.Characters;
import org.exist.util.sax.event.contenthandler.Element;
import org.exist.util.sax.event.contenthandler.StartElement;
import org.exist.xmldb.XmldbURI;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

/**
* Amongst other things, this trigger defers immediate updates to Principals
* (Accounts or Groups) until it has enough information to determine
* if such an update would cause a principal id or name collision.
*
* If a collision is detected, then it attempts to resolve the collision,
* before the deferred updates are applied.
*
* @author <a href="mailto:shabanovd@gmail.com">Dmitriy Shabanov</a>
* @author Adam Retter <adam.retter@googlemail.com>
*/
public class ConfigurationDocumentTrigger extends DeferrableFilteringTrigger {

    private final static String ID_ATTR = "id";

    protected Logger LOG = Logger.getLogger(getClass());

    private DBBroker broker = null;

    /*
    Used for holding a pre-allocated id for either an account or group
    */
    private final PreAllocatedIdReceiver preAllocatedId = new PreAllocatedIdReceiver();

    /*
    Are we creating or updating a document?
    */
    private boolean createOrUpdate = false;

    /*
    Guard used to prevent processing group elements
    within account elements as though they were standalone
    group elements
    */
    private boolean processingAccount = false;


    @Deprecated
    public void finish(final int event, final DBBroker broker, final Txn txn, final XmldbURI documentPath, final DocumentImpl document) {
       
        Configuration conf;
        switch (event) {
        case REMOVE_DOCUMENT_EVENT:
            conf = Configurator.getConfigurtion(broker.getBrokerPool(), documentPath);
            if (conf != null) {
                Configurator.unregister(conf);
                //XXX: inform object that configuration was deleted
            }
            break;
        default:
            conf = Configurator.getConfigurtion(broker.getBrokerPool(), documentPath);
            if (conf != null) {
                conf.checkForUpdates((ElementAtExist) document.getDocumentElement());
            }
            if (documentPath.toString().equals(ConverterFrom1_0.LEGACY_USERS_DOCUMENT_PATH)) {
                try {
                  final SecurityManager sm = broker.getBrokerPool().getSecurityManager();
                    ConverterFrom1_0.convert(sm, document);
                } catch (final PermissionDeniedException pde) {
                    LOG.error(pde.getMessage(), pde);
                    //TODO : raise exception ? -pb
                } catch (final EXistException ee) {
                    LOG.error(ee.getMessage(), ee);
                    //TODO : raise exception ? -pb
                }
            }
            break;
        }
    }

    private void checkForUpdates(final DBBroker broker, final XmldbURI uri, final DocumentImpl document) {
        final Configuration conf = Configurator.getConfigurtion(broker.getBrokerPool(), uri);
        if (conf != null) {
            conf.checkForUpdates((ElementAtExist) document.getDocumentElement());
        }

        //TODO : use XmldbURI methos ! not String.equals()
        if (uri.toString().equals(ConverterFrom1_0.LEGACY_USERS_DOCUMENT_PATH)) {
            try {
              final SecurityManager sm = broker.getBrokerPool().getSecurityManager();
                ConverterFrom1_0.convert(sm, document);
            } catch (final PermissionDeniedException pde) {
                LOG.error(pde.getMessage(), pde);
                //TODO : raise exception ? -pb
            } catch (final EXistException ee) {
                LOG.error(ee.getMessage(), ee);
                //TODO : raise exception ? -pb
            }
        }
    }

    @Override
    public void beforeCreateDocument(final DBBroker broker, final Txn txn, final XmldbURI uri) throws TriggerException {
        this.createOrUpdate = true;
        this.broker = broker;
    }

    @Override
    public void afterCreateDocument(final DBBroker broker, final Txn txn, final DocumentImpl document) throws TriggerException {
        //check saving list
        if (Configurator.saving.contains(Configurator.getFullURI(broker.getBrokerPool(), document.getURI()) ))
            {return;}

        checkForUpdates(broker, document.getURI(), document);

        final XmldbURI uri = document.getCollection().getURI();
        if (uri.startsWith(SecurityManager.SECURITY_COLLECTION_URI)) {
            try {
                broker.getBrokerPool().getSecurityManager().processPramatter(broker, document);
            } catch (final ConfigurationException e) {
                LOG.error("Configuration can't be processed [" + document.getURI() + "]", e);
                //TODO : raise exception ? -pb
            }
        }

        this.broker = null;
        this.createOrUpdate = false;
    }

    @Override
    public void beforeUpdateDocument(final DBBroker broker, final Txn txn, final DocumentImpl document) throws TriggerException {
        this.createOrUpdate = true;
        this.broker = broker;

        //check saving list
        if (Configurator.saving.contains(Configurator.getFullURI(broker.getBrokerPool(), document.getURI()))) {
            return;
        }

        final XmldbURI uri = document.getCollection().getURI();
        if (uri.startsWith(SecurityManager.SECURITY_COLLECTION_URI)) {
            try {
                broker.getBrokerPool().getSecurityManager()
                .processPramatterBeforeSave(broker, document);
            } catch (final ConfigurationException e) {
                LOG.error("Configuration can't be processed [" + document.getURI() + "]", e);
                //TODO : raise exception ? -pb
            }
        }
    }

    @Override
    public void afterUpdateDocument(final DBBroker broker, final Txn txn, final DocumentImpl document) throws TriggerException {
        //check saving list
        if (Configurator.saving.contains(Configurator.getFullURI(broker.getBrokerPool(), document.getURI()))) {
            return;
        }

      checkForUpdates(broker, document.getURI(), document);

        final XmldbURI uri = document.getCollection().getURI();
        if (uri.startsWith(SecurityManager.SECURITY_COLLECTION_URI)) {
            try {
                broker.getBrokerPool().getSecurityManager().processPramatter(broker, document);
            } catch (final ConfigurationException e) {
                LOG.error("Configuration can't be processed [" + document.getURI() + "]", e);
                //TODO : raise exception ? -pb
            }
        }

        this.broker = null;
        this.createOrUpdate = false;
    }

    @Override
    public void beforeCopyDocument(final DBBroker broker, final Txn txn, final DocumentImpl document, final XmldbURI newUri) throws TriggerException {
        //Nothing to do
    }

    @Override
    public void afterCopyDocument(final DBBroker broker, final Txn txn, final DocumentImpl document, final XmldbURI oldUri) throws TriggerException {
        checkForUpdates(broker, document.getURI(), document);
    }

    @Override
    public void beforeMoveDocument(final DBBroker broker, final Txn txn, final DocumentImpl document, final XmldbURI newUri) throws TriggerException {
        //Nothing to do
    }

    @Override
    public void afterMoveDocument(final DBBroker broker, final Txn txn, final DocumentImpl document, final XmldbURI oldUri) throws TriggerException {
        checkForUpdates(broker, document.getURI(), document);
    }

    @Override
    public void beforeDeleteDocument(final DBBroker broker, final Txn txn, final DocumentImpl document) throws TriggerException {
        //Nothing to do
    }

    @Override
    public void afterDeleteDocument(final DBBroker broker, final Txn txn, final XmldbURI uri) throws TriggerException {
        final Configuration conf = Configurator.getConfigurtion(broker.getBrokerPool(), uri);
        if (conf != null) {
            Configurator.unregister(conf);
            //XXX: inform object that configuration was deleted
        }
    }

  @Override
    public void beforeUpdateDocumentMetadata(final DBBroker broker, final Txn txn, final DocumentImpl document) {
  }

  @Override
  public void afterUpdateDocumentMetadata(DBBroker broker, Txn txn, DocumentImpl document) {
  }

  @Override
  public void configure(final DBBroker broker, final Collection parent,
      final Map<String, List<? extends Object>> parameters)
      throws TriggerException {
  }

    @Override
    public void startElement(final String namespaceURI, final String localName, final String qname, final Attributes attributes) throws SAXException {
        if(createOrUpdate && namespaceURI != null && namespaceURI.equals(Configuration.NS) && (localName.equals(PrincipalType.ACCOUNT.getElementName()) || (localName.equals(PrincipalType.GROUP.getElementName()) && !processingAccount))) {
            processingAccount = localName.equals(PrincipalType.ACCOUNT.getElementName()); //set group account/group guard
            defer(true);
        }
        super.startElement(namespaceURI, localName, qname, attributes);
    }

    @Override
    public void endElement(final String namespaceURI, final String localName, final String qname) throws SAXException {

        super.endElement(namespaceURI, localName, qname);

        if(createOrUpdate && namespaceURI != null && namespaceURI.equals(Configuration.NS) && (localName.equals(PrincipalType.ACCOUNT.getElementName()) || (localName.equals(PrincipalType.GROUP.getElementName()) && !processingAccount))) {

            //we have now captured the entire account or group in our deferred queue,
            //so we can now process it in it's entirety
            processPrincipal(PrincipalType.fromElementName(localName));

            //stop deferring events and apply
            defer(false);

            if(localName.equals(PrincipalType.ACCOUNT.getElementName())) {
                //we are no longer processing an account
                processingAccount = false; //reset account/group guard
            }
        }
    }

    /**
     * When configuring a Principal (Account or Group) we need to
     * make sure of two things:
     *
     * 1) If the principal uses an old style id, i.e. before ACL Permissions
     * were introduced then we have to modernise this id
     *
     * 2) If the principal uses a name or id which already exists in
     * the database then we must avoid conflicts
     */
    private final void processPrincipal(final PrincipalType principalType) throws SAXException {
        final SAXEvent firstEvent = deferred.peek();
        if(!(firstEvent instanceof StartElement)) {
            throw new SAXException("Unbalanced SAX Events");
        }

        final StartElement start = ((StartElement)firstEvent);
        if(start.namespaceURI == null || !start.namespaceURI.equals(Configuration.NS) || !start.localName.equals(principalType.getElementName())) {
            throw new SAXException("First element does not match ending '" + principalType.getElementName() + "' element");
        }

        //if needed, update old style id to new style id
        final AttributesImpl attrs = new AttributesImpl(migrateIdAttribute(start.attributes, principalType));

        //check if there is a name collision, i.e. another principal with the same name
        final SecurityManager sm = broker.getBrokerPool().getSecurityManager();
        final String principalName = findName();
        final Principal existingPrincipleByName = principalName != null ? principalType.getPrincipal(sm, principalName) : null;

        final int newId;
        if(existingPrincipleByName != null) {
            //use id of existing principal which has the same name
            newId = existingPrincipleByName.getId();
        } else {

            //check if there is an id collision, i.e. another principal with the same id
            final Integer id = Integer.valueOf(attrs.getValue(ID_ATTR));
            final Principal existingPrincipalById = principalType.getPrincipal(sm, id);

            if(existingPrincipalById != null) {

                //pre-allocate a new id, so as not to collide with the existing principal
                if(isValidating()) {
                    try {
                        principalType.preAllocateId(sm, preAllocatedId);
                    } catch(final PermissionDeniedException | EXistException e) {
                        throw new SAXException("Unable to pre-allocate principle id for " + principalType.getElementName() + ": " + principalName, e);
                    }
                }

                newId = preAllocatedId.getId();

                if(!isValidating()) {
                    preAllocatedId.clear();
                }
            } else {
                newId = id; //use the provided id as it is currently unallocated
            }
        }

        //update attributes of the principal in deferred
        attrs.setValue(attrs.getIndex(ID_ATTR), String.valueOf(newId));
        final StartElement prevPrincipalStart = (StartElement)deferred.poll();
        deferred.addFirst(new StartElement(prevPrincipalStart.namespaceURI, prevPrincipalStart.localName, prevPrincipalStart.qname, attrs));
    }

    /**
     * Migrates the id of a principal
     *
     * @param attrs The existing attributes of the principal
     * @param principalType The type of the principal
     *
     * @return The updated attributes containing the new id
     */
    private Attributes migrateIdAttribute(final Attributes attrs, final PrincipalType principalType) {
        final boolean aclPermissionInUse =
            PermissionFactory.getDefaultResourcePermission() instanceof ACLPermission;

        final Attributes newAttrs;
        final String strId = attrs.getValue(ID_ATTR);
        if (aclPermissionInUse && strId != null) {
            final Integer id = Integer.parseInt(strId);
            final Integer newId = principalType.migrateId(id);
            if(newId != null) {
                newAttrs = new AttributesImpl(attrs);
                ((AttributesImpl)newAttrs).setValue(newAttrs.getIndex(ID_ATTR), newId.toString());
            } else {
                newAttrs = attrs;
            }
        } else {
            newAttrs = attrs;
        }

        return newAttrs;
    }

    /**
     * Attempts to find and extract the text value
     * of the name element from the deferred elements
     *
     * @return The text value of the name element, or null otherwise
     */
    private String findName() {
        boolean inName = false;
        final StringBuilder name = new StringBuilder();
        for(final Iterator<SAXEvent> iterator = deferred.iterator(); iterator.hasNext(); ) {
            final SAXEvent event = iterator.next();
            if(event instanceof Element) {
                final Element element = (Element)event;
                if(element.namespaceURI != null && element.namespaceURI.equals(Configuration.NS) && element.localName.equals("name")) {
                    inName = !inName;
                }
            }

            if(inName && event instanceof Characters) {
                name.append(((Characters)event).ch);
            }
        }

        if(name.length() > 0) {
            return name.toString().trim();
        } else {
            return null;
        }
    }

    /**
     * Abstracts the difference between working
     * with Accounts or Groups
     */
    private enum PrincipalType {
        ACCOUNT("account", new HashMap<Integer, Integer>() {
            {
                put(-1, RealmImpl.UNKNOWN_ACCOUNT_ID);
                put(0, RealmImpl.SYSTEM_ACCOUNT_ID);
                put(1, RealmImpl.ADMIN_ACCOUNT_ID);
                put(2, RealmImpl.GUEST_ACCOUNT_ID);
            }
        }),
        GROUP("group", new HashMap<Integer, Integer>() {
            {
                put(-1, RealmImpl.UNKNOWN_GROUP_ID);
                put(1, RealmImpl.DBA_GROUP_ID);
                put(2, RealmImpl.GUEST_GROUP_ID);
            }
        });

        private final String elementName;
        private final Map<Integer, Integer> idMigration;

        PrincipalType(final String elementName, final Map<Integer, Integer> idMigration) {
            this.elementName = elementName;
            this.idMigration = idMigration;
        }

        /**
         * Get the local-name of the element
         * for the principal
         *
         * @return The local-name of the element used
         * in the persisted XML document for the principal
         */
        public String getElementName() {
            return elementName;
        }

        /**
         * Looks up a new Id given an old Id.
         *
         * Old Id's were used prior to the introduction of
         * ACL Permissions into eXist. Looking up
         * a non-old id will return null;
         *
         * @param oldId The older id
         *
         * @return The new Id or null if there is no mapping from old to new
         */
        public Integer migrateId(final Integer oldId) {
            return idMigration.get(oldId);
        }

        /**
         * Gets a principal of this type from the SecurityManager by name
         *
         * @param sm An instance of the SecurityManager
         * @param name The name of the principal
         *
         * @return A principal of this type, or null if there is no principal
         * matching the provided name
         */
        public Principal getPrincipal(final SecurityManager sm, final String name) {
            switch(this) {
                case ACCOUNT:
                    return sm.getAccount(name);
                case GROUP:
                    return sm.getGroup(name);
            }
            return null;
        }

        /**
         * Gets a principal of this type from the SecurityManager by id
         *
         * @param sm An instance of the SecurityManager
         * @param id The id of the principal
         *
         * @return A principal of this type, or null if there is no principal
         * matching the provided id
         */
        public Principal getPrincipal(final SecurityManager sm, final int id) {
            switch(this) {
                case ACCOUNT:
                    return sm.getAccount(id);
                case GROUP:
                    return sm.getGroup(id);
            }
            return null;
        }

        public void preAllocateId(final SecurityManager sm, final PreAllocatedIdReceiver receiver) throws PermissionDeniedException, EXistException {
            switch(this) {
                case ACCOUNT:
                    sm.preAllocateAccountId(receiver);
                case GROUP:
                    sm.preAllocateGroupId(receiver);
            }
        }

        /**
         * Get the PrincipalType by its element name
         *
         * @return The PrincipalType for the element name
         *
         * @throws java.util.NoSuchElementException If there is no PrincipalType
         * for the provided element name
         */
        public static PrincipalType fromElementName(final String elementName) {
            for(final PrincipalType pt : PrincipalType.values()) {
                if(pt.getElementName().equals(elementName)) {
                    return pt;
                }
            }
            throw new NoSuchElementException("No PrincipalType with element name: " + elementName);
        }
    }

    private class PreAllocatedIdReceiver implements SecurityManager.PrincipalIdReceiver {
        Integer id = null;

        @Override
        public void allocate(final int id) {
            this.id = id;
        }

        public int getId() throws IllegalStateException {
            if(id == null) {
                throw new IllegalStateException("Id has not been allocated");
            } else {
                return id;
            }
        }

        public void clear() {
            this.id = null;
        }
    }
}
TOP

Related Classes of org.exist.config.ConfigurationDocumentTrigger$PreAllocatedIdReceiver

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.