All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.cristalise.storage.jooqdb.lookup.JooqLookupManager Maven / Gradle / Ivy

There is a newer version: 6.0.0
Show newest version
/**
 * This file is part of the CRISTAL-iSE jOOQ Cluster Storage Module.
 * Copyright (c) 2001-2017 The CRISTAL Consortium. All rights reserved.
 *
 * This library 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 3 of the License, or (at
 * your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; with out 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 library; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *
 * http://www.fsf.org/licensing/licenses/lgpl.html
 */
package org.cristalise.storage.jooqdb.lookup;

import static org.cristalise.storage.jooqdb.clusterStore.JooqItemPropertyHandler.ITEM_PROPERTY_TABLE;
import static org.cristalise.storage.jooqdb.lookup.JooqDomainPathHandler.DOMAIN_PATH_TABLE;
import static org.cristalise.storage.jooqdb.lookup.JooqDomainPathHandler.TARGET;
import static org.cristalise.storage.jooqdb.lookup.JooqItemHandler.ITEM_TABLE;
import static org.cristalise.storage.jooqdb.lookup.JooqRolePathHandler.AGENT;
import static org.cristalise.storage.jooqdb.lookup.JooqRolePathHandler.ROLE_PATH_TABLE;
import static org.jooq.impl.DSL.field;
import static org.jooq.impl.DSL.name;
import static org.jooq.impl.DSL.upper;

import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;

import org.apache.commons.lang3.StringUtils;
import org.cristalise.kernel.common.ObjectAlreadyExistsException;
import org.cristalise.kernel.common.ObjectCannotBeUpdated;
import org.cristalise.kernel.common.ObjectNotFoundException;
import org.cristalise.kernel.common.PersistencyException;
import org.cristalise.kernel.lookup.AgentPath;
import org.cristalise.kernel.lookup.DomainPath;
import org.cristalise.kernel.lookup.InvalidItemPathException;
import org.cristalise.kernel.lookup.ItemPath;
import org.cristalise.kernel.lookup.LookupManager;
import org.cristalise.kernel.lookup.Path;
import org.cristalise.kernel.lookup.RolePath;
import org.cristalise.kernel.process.Gateway;
import org.cristalise.kernel.process.auth.Authenticator;
import org.cristalise.kernel.property.Property;
import org.cristalise.kernel.property.PropertyDescriptionList;
import org.cristalise.kernel.utils.Logger;
import org.cristalise.storage.jooqdb.JooqHandler;
import org.cristalise.storage.jooqdb.auth.Argon2Password;
import org.cristalise.storage.jooqdb.clusterStore.JooqItemPropertyHandler;
import org.jooq.DSLContext;
import org.jooq.Field;
import org.jooq.JoinType;
import org.jooq.Operator;
import org.jooq.Record;
import org.jooq.Result;
import org.jooq.SelectQuery;
import org.jooq.impl.DSL;

/**
 *
 */
public class JooqLookupManager implements LookupManager {

    protected DSLContext context;

    private JooqItemHandler         items;
    private JooqDomainPathHandler   domains;
    private JooqRolePathHandler     roles;
    private JooqPermissionHandler   permissions;
    private JooqItemPropertyHandler properties;

    private Argon2Password passwordHasher;

    @Override
    public void open(Authenticator auth) {
        try {
            context = JooqHandler.connect();

            initialiseHandlers();

            passwordHasher = new Argon2Password();
        }
        catch (PersistencyException ex) {
            Logger.error(ex);
            throw new IllegalArgumentException(ex.getMessage());
        }
    }

    public void initialiseHandlers() throws PersistencyException {
        items       = new JooqItemHandler();
        domains     = new JooqDomainPathHandler();
        roles       = new JooqRolePathHandler();
        permissions = new JooqPermissionHandler();
        properties  = new JooqItemPropertyHandler();

        items      .createTables(context);
        domains    .createTables(context);
        roles      .createTables(context);
        permissions.createTables(context);
        properties .createTables(context);
    }

    public void dropHandlers() throws PersistencyException {
        properties .dropTables(context);
        permissions.dropTables(context);
        roles      .dropTables(context);
        domains    .dropTables(context);
        items      .dropTables(context);
    }

    @Override
    public void initializeDirectory() throws ObjectNotFoundException {
        Logger.msg(6, "JOOQLookupManager.initializeDirectory() - NOTHING done.");
    }

    @Override
    public void close() {
        if (context != null) context.close();
    }

    @Override
    public boolean exists(Path path) {
        if (path == null) return false;

        boolean exists = false;

        try {
            if      (path instanceof ItemPath)   exists = items  .exists(context, path.getUUID());
            else if (path instanceof AgentPath)  exists = items  .exists(context, path.getUUID());
            else if (path instanceof DomainPath) exists = domains.exists(context, (DomainPath)path);
            else if (path instanceof RolePath)   exists = roles  .exists(context, (RolePath)path, null);
        }
        catch (PersistencyException e) {
            Logger.error(e);
        }

        if (Logger.doLog(5)) JooqHandler.logConnectionCount("JooqLookupManager.exists()", context);

        return exists;
    }

    @Override
    public void add(Path newPath) throws ObjectCannotBeUpdated, ObjectAlreadyExistsException {
        if (exists(newPath)) throw new ObjectAlreadyExistsException("Path exist:"+newPath);

        Logger.msg(8, "JooqLookupManager.add() - path:"+newPath);

        try {
            int rows = 0;

            if      (newPath instanceof AgentPath)  rows = items  .insert(context, (AgentPath) newPath, properties);
            else if (newPath instanceof ItemPath)   rows = items  .insert(context, (ItemPath)  newPath);
            else if (newPath instanceof DomainPath) rows = domains.insert(context, (DomainPath)newPath);
            else if (newPath instanceof RolePath)   rows = (createRole((RolePath) newPath) != null) ? 1 : 0;

            if (rows == 0)
                throw new ObjectCannotBeUpdated("JOOQLookupManager must insert some records:"+rows);
            else
                Logger.msg(8, "JooqLookupManager.add() - path:"+newPath+" rows inserted:"+rows);

            if (Logger.doLog(5)) JooqHandler.logConnectionCount("JooqLookupManager.add()", context);
        }
        catch (PersistencyException e) {
            Logger.error(e);
            throw new ObjectCannotBeUpdated(e.getMessage());
        }
    }

    @Override
    public void delete(Path path) throws ObjectCannotBeUpdated {
        if (!exists(path)) throw new ObjectCannotBeUpdated("Path does not exist:"+path);

        Logger.msg(8, "JooqLookupManager.delete() - path:"+path);

        try {
            if (getChildren(path).hasNext()) throw new ObjectCannotBeUpdated("Path is not a leaf");

            int rows = 0;
            if      (path instanceof ItemPath)   rows = items  .delete(context, path.getUUID());
            else if (path instanceof AgentPath)  rows = items  .delete(context, path.getUUID());
            else if (path instanceof DomainPath) rows = domains.delete(context, path.getStringPath());
            else if (path instanceof RolePath)   {
                permissions.delete(context, path.getStringPath());
                rows = roles.delete(context, (RolePath)path, null);
            }

            if (rows == 0)
                throw new ObjectCannotBeUpdated("JOOQLookupManager must delete some records:"+rows);
            else
                Logger.msg(8, "JooqLookupManager.delete() - path:"+path+" rows deleted:"+rows);
        }
        catch (PersistencyException e) {
            Logger.error(e);
            throw new ObjectCannotBeUpdated(e.getMessage());
        }

    }

    @Override
    public ItemPath getItemPath(String sysKey) throws InvalidItemPathException, ObjectNotFoundException {
        ItemPath ip = new ItemPath(sysKey);

        if (!exists(ip)) throw new ObjectNotFoundException("Path does not exist:"+sysKey);

        try {
            return items.fetch(context, ip.getUUID(), properties);
        }
        catch (PersistencyException e) {
            Logger.error(e);
            throw new InvalidItemPathException(e.getMessage());
        }
    }

    @Override
    public String getIOR(Path path) throws ObjectNotFoundException {
        try {
            return getItemPath(path.getStringPath()).getIORString();
        }
        catch (InvalidItemPathException e) {
            throw new ObjectNotFoundException(e.getMessage());
        }
    }

    private List find(Path start, String name, List uuids) {
        Logger.msg(8, "JooqLookupManager.find() - start:"+start+" name:"+name);

        if      (start instanceof DomainPath) return domains.find(context, (DomainPath)start, name, uuids);
        else if (start instanceof RolePath)   return roles  .find(context, (RolePath)start,   name, uuids);

        return new ArrayList();
    }

    @Override
    public Iterator search(Path start, String name) {
        List result = find(start, name, null);

        if (result == null) return new ArrayList().iterator(); //returns empty iterator
        else                return result.iterator();
    }

    @Override
    public AgentPath getAgentPath(String agentName) throws ObjectNotFoundException {
        List uuids = properties.findItemsByName(context, agentName);

        if (uuids.size() == 0) throw new ObjectNotFoundException("Could not find agent:"+agentName);

        try {
            return (AgentPath) items.fetch(context, uuids.get(0), properties);
        }
        catch (PersistencyException e) {
            throw new ObjectNotFoundException("Could not retrieve agentName:"+agentName + " error:"+e.getMessage());
        }
    }

    @Override
    public RolePath getRolePath(String roleName) throws ObjectNotFoundException {
        List uuids = new ArrayList<>();
        uuids.add(JooqRolePathHandler.NO_AGENT);

        List result = roles.find(context, "%/"+roleName, uuids);

        if      (result == null || result.size() == 0) throw new ObjectNotFoundException("Role '"+roleName+"' does not exist");
        else if (result.size() > 1)                    throw new ObjectNotFoundException("Unbiguos roleName:'"+roleName+"'");
        
        RolePath role = (RolePath)result.get(0);
        role.setPermissions(permissions.fetch(context, role.getStringPath()));

        return role;
    }

    @Override
    public ItemPath resolvePath(DomainPath domainPath) throws InvalidItemPathException, ObjectNotFoundException {
        if (!exists(domainPath)) throw new ObjectNotFoundException("Path does not exist:"+domainPath);

        try {
            DomainPath dp = domains.fetch(context, domainPath);

            if (dp.getTarget() == null) throw new InvalidItemPathException("DomainPath has no target:"+domainPath);

            //issue #165: using items.fetch() ensures that Path is either ItemPath or AgentPath
            return items.fetch(context, dp.getTarget().getUUID(), properties);
        }
        catch (PersistencyException e) {
            Logger.error(e);
            throw new ObjectNotFoundException(e.getMessage());
        }
    }

    @Override
    public String getAgentName(AgentPath agentPath) throws ObjectNotFoundException {
        if (!exists(agentPath)) throw new ObjectNotFoundException("Path does not exist:"+agentPath);

        try {
            ItemPath ip = items.fetch(context, agentPath.getUUID(), properties);
            
            if (ip instanceof AgentPath) return ((AgentPath)ip).getAgentName();
            else                         throw new ObjectNotFoundException("Path is not an agent:"+agentPath);
        }
        catch (PersistencyException e) {
            Logger.error(e);
            throw new ObjectNotFoundException(e.getMessage());
        }
    }

    private String getChildrenPattern(Path path) {
        //after the path match everything except '/'
        return "^" + path.getStringPath() + "/[^/]*$";
    }

    @Override
    public Iterator getChildren(Path path) {
        String pattern = getChildrenPattern(path);

        Logger.msg(8, "JooqLookupManager.getChildren() - pattern:" + pattern);

        Iterator iter = null;

        if      (path instanceof RolePath)   iter = roles  .findByRegex(context, pattern ).iterator();
        else if (path instanceof DomainPath) iter = domains.findByRegex(context, pattern ).iterator();

        if (Logger.doLog(5)) JooqHandler.logConnectionCount("JooqLookupManager.getChildren()", context);

        if (iter == null) return new ArrayList().iterator(); //empty iterator
        else              return iter;
    }

    @Override
    public PagedResult getChildren(Path path, int offset, int limit) {
        String pattern = getChildrenPattern(path);

        Logger.msg(8, "JooqLookupManager.getChildren() - pattern:%s offset:%d limit:%d", pattern, offset, limit);

        if (path instanceof ItemPath) return new PagedResult();

        int maxRows = 0;

        if      (path instanceof RolePath)   maxRows = roles  .countByRegex(context, pattern);
        else if (path instanceof DomainPath) maxRows = domains.countByRegex(context, pattern);

        if (maxRows == 0) return new PagedResult();

        List pathes = null;

        if      (path instanceof RolePath)   pathes = roles  .findByRegex(context, pattern, offset, limit);
        else if (path instanceof DomainPath) pathes = domains.findByRegex(context, pattern, offset, limit);

        if (Logger.doLog(5)) JooqHandler.logConnectionCount("JooqLookupManager.getChildren()", context);

        if (pathes == null) return new PagedResult(); 
        else                return new PagedResult(maxRows, pathes);
    }

    private SelectQuery getSearchSelect(Path start, List props) {
        SelectQuery select = context.selectQuery();

        select.addFrom(DOMAIN_PATH_TABLE);

        for (Property p : props) {
            Field joinField = field(name(p.getName(), "UUID"), UUID.class);

            select.addJoin(ITEM_PROPERTY_TABLE.as(p.getName()), JoinType.LEFT_OUTER_JOIN, TARGET.equal(joinField));

            select.addConditions(Operator.AND, field(name(p.getName(), "NAME"),  String.class).equal(p.getName()));
            select.addConditions(Operator.AND, upper(field(name(p.getName(), "VALUE"), String.class)).like(upper(p.getValue())));
        }

        select.addConditions(Operator.AND, JooqDomainPathHandler.PATH.like(domains.getFindPattern(start, "")));

        return select;
    }

    @Override
    public Iterator search(Path start, Property... props) {
        return search(start, Arrays.asList(props), 0, 0).rows.iterator();
    }

    @Override
    public PagedResult search(Path start, List props, int offset, int limit) {
        if (!exists(start)) return new PagedResult(0, new ArrayList());

        int maxRows = -1;

        // without limit no need to count the number of rows
        if (limit > 0) {
            SelectQuery selectCount = getSearchSelect(start, props);
            selectCount.addSelect(DSL.count());

            Logger.msg(8, "JooqLookupManager.search(props) - SQL(count):\n%s", selectCount);

            maxRows = selectCount.fetchOne(0, int.class);

            if(maxRows == 0) return new PagedResult(0, new ArrayList());
        }

        SelectQuery select = getSearchSelect(start, props);

        select.addSelect(JooqDomainPathHandler.PATH, TARGET);
        select.addOrderBy(JooqDomainPathHandler.PATH);

        if (limit  > 0) select.addLimit(limit);
        if (offset > 0) select.addOffset(offset);

        Logger.msg(8, "JooqLookupManager.search(props) - SQL:\n%s", select);

        return new PagedResult(maxRows, domains.getListOfPath(select.fetch()));
    }

    @Override
    public Iterator search(Path start, PropertyDescriptionList props) {
        return search(start, props, 0, 0).rows.iterator();
    }

    @Override
    public PagedResult search(Path start, PropertyDescriptionList props, int offset, int limit) {
        //FIXME: UNIMPLEMENTED search(PropertyDescriptionList)
        throw new RuntimeException("InMemoryLookup.search(PropertyDescriptionList) - UNIMPLEMENTED start:"+start);
    }

    @Override
    public RolePath createRole(RolePath role) throws ObjectAlreadyExistsException, ObjectCannotBeUpdated {
        Logger.msg(5, "JooqLookupManager.createRole() - role:"+role);

        if(exists(role)) throw new ObjectAlreadyExistsException("Role:"+role);

        try {
            role.getParent();
            roles.insert(context, role, null);
            permissions.insert(context, role.getStringPath(), role.getPermissionsList());
            return role;
        }
        catch (Throwable t) {
            Logger.error(t);
            throw new ObjectCannotBeUpdated("Parent role for '"+role+"' does not exists");
        }
    }

    @Override
    public void addRole(AgentPath agent, RolePath role) throws ObjectCannotBeUpdated, ObjectNotFoundException {
        if (!exists(role))  throw new ObjectNotFoundException("Role:"+role);
        if (!exists(agent)) throw new ObjectNotFoundException("Agent:"+agent);

        try {
            int rows = roles.insert(context, role, agent);
            if (rows != 1) throw new ObjectCannotBeUpdated("Updated rows must be 1 but it was '"+rows+"'");
        }
        catch (Exception e) {
            Logger.error(e);
            throw new ObjectCannotBeUpdated(e.getMessage());
        }
    }

    private SelectQuery getGetAgentsSelect(RolePath role) {
        SelectQuery select = context.selectQuery();

        select.addFrom(ROLE_PATH_TABLE.as("role"));

        select.addJoin(ITEM_TABLE.as("item"),          JoinType.JOIN, AGENT.equal(field(name("item", "UUID"), UUID.class)));
        select.addJoin(ITEM_PROPERTY_TABLE.as("prop"), JoinType.JOIN, AGENT.equal(field(name("prop", "UUID"), UUID.class)));

        select.addConditions(Operator.AND, JooqRolePathHandler.PATH.equal(role.getStringPath()));
        select.addConditions(Operator.AND, JooqItemPropertyHandler.NAME.equal("Name"));

        return select;
    }

    @Override
    public AgentPath[] getAgents(RolePath role) throws ObjectNotFoundException {
        return getAgents(role, -1, -1).rows.toArray(new AgentPath[0]);
    }

    @Override
    public PagedResult getAgents(RolePath role, int offset, int limit) throws ObjectNotFoundException {
        int maxRows = -1;

        if (limit > 0) {
            SelectQuery selectCount = getGetAgentsSelect(role);
            selectCount.addSelect(DSL.count());

            Logger.msg(8, "JooqLookupManager.getAgents(props) - role:%s  SQL(count):\n%s", role, selectCount);

            maxRows = selectCount.fetchOne(0, int.class);
        }

        SelectQuery select = getGetAgentsSelect(role);

        select.addSelect(
                field(name("item", "UUID"), UUID.class),
                JooqItemHandler.IOR,
                JooqItemHandler.IS_AGENT,
                JooqItemPropertyHandler.VALUE.as("Name"));

        if (Gateway.getProperties().getBoolean("JOOQ.TemporaryPwdFieldImplemented", true)) 
            select.addSelect(JooqItemHandler.IS_PASSWORD_TEMPORARY);

        select.addOrderBy(field(name("Name")));

        if (limit  > 0) select.addLimit(limit);
        if (offset > 0) select.addOffset(offset);

        Logger.msg(8, "JooqLookupManager.getAgents() - role:%s  SQL:\n%s", role, select);

        Result result = select.fetch();

        PagedResult pResult = new PagedResult();

        if(result != null) {
            pResult.maxRows = maxRows;

            for (Record record: result) {
                try {
                    pResult.rows.add(JooqItemHandler.getItemPath(context, null, record));
                }
                catch (PersistencyException e) {
                    // This shall never happen
                    Logger.error(e);
                    throw new ObjectNotFoundException(e.getMessage());
                }
            }
        }

        return pResult;
    }

    @Override
    public RolePath[] getRoles(AgentPath agent) {
        try {
            return roles.findRolesOfAgent(context, agent, permissions).toArray(new RolePath[0]);
        }
        catch (PersistencyException e) {
            Logger.error(e);
        }
        return new RolePath[0];
    }

    @Override
    public PagedResult getRoles(AgentPath agent, int offset, int limit) {
        try {
            return new PagedResult(
                    roles.countRolesOfAgent(context, agent),
                    roles.findRolesOfAgent(context, agent, offset, limit, permissions));
        }
        catch (PersistencyException e) {
            Logger.error(e);
        }
        return new PagedResult();
    }

    @Override
    public boolean hasRole(AgentPath agent, RolePath role) {
        try {
            return roles.exists(context, role, agent);
        }
        catch (PersistencyException e) {
            Logger.error(e);
        }
        return false;
    }

    @Override
    public void removeRole(AgentPath agent, RolePath role) throws ObjectCannotBeUpdated, ObjectNotFoundException {
        if (!exists(role))  throw new ObjectNotFoundException("Role:"+role);
        if (!exists(agent)) throw new ObjectNotFoundException("Agent:"+agent);

        try {
            int rows = roles.delete(context, role, agent);

            if (rows == 0)
                throw new ObjectCannotBeUpdated("Role:"+role+" Agent:"+agent + " are not related.");
        }
        catch (Exception e) {
            throw new ObjectCannotBeUpdated("Role:"+role+" Agent:"+agent + " error:" + e.getMessage());
        }
    }

    @Override
    public void setAgentPassword(AgentPath agent, String newPassword) throws ObjectNotFoundException, ObjectCannotBeUpdated, NoSuchAlgorithmException {
        setAgentPassword(agent, newPassword, false);
    }

    @Override
    public void setAgentPassword(AgentPath agent, String newPassword, boolean temporary) throws ObjectNotFoundException, ObjectCannotBeUpdated, NoSuchAlgorithmException {
        if (!exists(agent)) throw new ObjectNotFoundException("Agent:"+agent);

        try {
            int rows = items.updatePassword(context, agent, passwordHasher.hashPassword(newPassword.toCharArray()), temporary);
            if (rows != 1) throw new ObjectCannotBeUpdated("Agent:"+agent);
        }
        catch (Exception e) {
            Logger.error(e);
            throw new ObjectCannotBeUpdated("Agent:"+agent + " error:" + e.getMessage());
        }
    }

    @Override
    public void setHasJobList(RolePath role, boolean hasJobList) throws ObjectNotFoundException, ObjectCannotBeUpdated {
        if (!exists(role)) throw new ObjectNotFoundException("Role:"+role);

        role.setHasJobList(hasJobList);

        try {
            roles.update(context, role);
        }
        catch (Exception e) {
            Logger.error(e);
            throw new ObjectCannotBeUpdated("Role:"+role + " error:" + e.getMessage());
        }
    }

    @Override
    public Iterator searchAliases(ItemPath itemPath) {
        return domains.find(context, itemPath).iterator();
    }

    @Override
    public PagedResult searchAliases(ItemPath itemPath, int offset, int limit) {
        return new PagedResult(
                domains.countFind(context, itemPath),
                domains.find(context, itemPath, offset, limit) );
    }

    @Override
    public void setIOR(ItemPath item, String ior) throws ObjectNotFoundException, ObjectCannotBeUpdated {
        if (!exists(item)) throw new ObjectNotFoundException("Item:"+item);

        item.setIORString(ior);

        try {
            items.updateIOR(context, item, ior);
        }
        catch (Exception e) {
            Logger.error(e);
            throw new ObjectCannotBeUpdated("Item:" + item + " error:" + e.getMessage());
        }
    }

    @Override
    public void setPermission(RolePath role, String permission) throws ObjectNotFoundException, ObjectCannotBeUpdated {
        ArrayList permissions = new ArrayList<>();

        if (StringUtils.isNotBlank(permission)) permissions.add(permission);

        //empty permission list shall clear the permissions of Role
        setPermissions(role, permissions);
    }

    @Override
    public void setPermissions(RolePath role, List permissions) throws ObjectNotFoundException, ObjectCannotBeUpdated {
        if (!exists(role)) throw new ObjectNotFoundException("Role:"+role);

        role.setPermissions(permissions);

        try {
            //empty permission list shall clear the permissions of Role
            if (this.permissions.exists(context, role.getStringPath())) this.permissions.delete(context, role.getStringPath());

            this.permissions.insert(context, role.getStringPath(), role.getPermissionsList());
        }
        catch (Exception e) {
            Logger.error(e);
            throw new ObjectCannotBeUpdated("Role:"+role + " error:" + e.getMessage());
        }
    }

    @Override
    public void postStartServer() {
    }

    @Override
    public void postBoostrap() {
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy