com.evolveum.midpoint.repo.sql.helpers.LookupTableHelper.java Source code

Java tutorial

Introduction

Here is the source code for com.evolveum.midpoint.repo.sql.helpers.LookupTableHelper.java

Source

/*
 * Copyright (c) 2010-2015 Evolveum
 *
 * 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 com.evolveum.midpoint.repo.sql.helpers;

import com.evolveum.midpoint.prism.PrismContainerValue;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.delta.ContainerDelta;
import com.evolveum.midpoint.prism.delta.ItemDelta;
import com.evolveum.midpoint.prism.path.*;
import com.evolveum.midpoint.prism.polystring.PolyString;
import com.evolveum.midpoint.prism.polystring.PrismDefaultPolyStringNormalizer;
import com.evolveum.midpoint.prism.query.ObjectPaging;
import com.evolveum.midpoint.repo.sql.data.common.RLookupTable;
import com.evolveum.midpoint.repo.sql.data.common.RObject;
import com.evolveum.midpoint.repo.sql.data.common.id.RContainerId;
import com.evolveum.midpoint.repo.sql.data.common.other.RLookupTableRow;
import com.evolveum.midpoint.repo.sql.util.RUtil;
import com.evolveum.midpoint.schema.GetOperationOptions;
import com.evolveum.midpoint.schema.ObjectSelector;
import com.evolveum.midpoint.schema.RelationalValueSearchQuery;
import com.evolveum.midpoint.schema.SelectorOptions;
import com.evolveum.midpoint.util.QNameUtil;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.LookupTableRowType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.LookupTableType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType;
import org.apache.commons.lang.StringUtils;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.xml.namespace.QName;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * Contains methods specific to handle LookupTable rows.
 * (As these rows are stored outside main LookupTable object.)
 *
 * It is quite a temporary solution in order to ease SqlRepositoryServiceImpl
 * from tons of type-specific code. Serious solution would be to implement
 * subobject-level operations more generically.
 *
 * @author lazyman, mederly
 */
@Component
public class LookupTableHelper {

    private static final Trace LOGGER = TraceManager.getTrace(LookupTableHelper.class);

    @Autowired
    private GeneralHelper generalHelper;

    public void addLookupTableRows(Session session, RObject object, boolean deleteBeforeAdd) {
        if (!(object instanceof RLookupTable)) {
            return;
        }
        RLookupTable table = (RLookupTable) object;

        if (deleteBeforeAdd) {
            deleteLookupTableRows(session, table.getOid());
        }
        if (table.getRows() != null) {
            for (RLookupTableRow row : table.getRows()) {
                session.save(row);
            }
        }
    }

    private void addLookupTableRows(Session session, String tableOid, Collection<PrismContainerValue> values,
            int currentId, boolean deleteBeforeAdd) throws SchemaException {
        for (PrismContainerValue value : values) {
            LookupTableRowType rowType = new LookupTableRowType();
            rowType.setupContainerValue(value);
            if (deleteBeforeAdd) {
                deleteRowByKey(session, tableOid, rowType.getKey());
            }
            RLookupTableRow row = RLookupTableRow.toRepo(tableOid, rowType);
            row.setId(currentId);
            currentId++;
            session.save(row);
        }
    }

    /**
     * This method removes all lookup table rows for object defined by oid
     * @param session
     * @param oid
     */
    public void deleteLookupTableRows(Session session, String oid) {
        Query query = session.getNamedQuery("delete.lookupTableData");
        query.setParameter("oid", oid);

        query.executeUpdate();
    }

    public void updateLookupTableData(Session session, String tableOid,
            Collection<? extends ItemDelta> modifications) throws SchemaException {
        if (modifications.isEmpty()) {
            return;
        }

        for (ItemDelta delta : modifications) {
            if (delta.getPath().size() == 1) { // whole row add/delete/replace
                if (!(delta instanceof ContainerDelta)) {
                    throw new IllegalStateException("Wrong table delta sneaked into updateLookupTableData: class="
                            + delta.getClass() + ", path=" + delta.getPath());
                }
                // one "table" container modification
                ContainerDelta containerDelta = (ContainerDelta) delta;

                if (containerDelta.getValuesToDelete() != null) {
                    // todo do 'bulk' delete like delete from ... where oid=? and id in (...)
                    for (PrismContainerValue<LookupTableRowType> value : (Collection<PrismContainerValue>) containerDelta
                            .getValuesToDelete()) {
                        if (value.getId() != null) {
                            deleteRowById(session, tableOid, value.getId());
                        } else if (value.asContainerable().getKey() != null) {
                            deleteRowByKey(session, tableOid, value.asContainerable().getKey());
                        } else {
                            // ignore (or throw an exception?)
                        }
                    }
                }
                if (containerDelta.getValuesToAdd() != null) {
                    int currentId = generalHelper.findLastIdInRepo(session, tableOid, "get.lookupTableLastId") + 1;
                    addLookupTableRows(session, tableOid, containerDelta.getValuesToAdd(), currentId, true);
                }
                if (containerDelta.getValuesToReplace() != null) {
                    deleteLookupTableRows(session, tableOid);
                    addLookupTableRows(session, tableOid, containerDelta.getValuesToReplace(), 1, false);
                }
            } else if (delta.getPath().size() == 3) { // row segment modification (structure is already checked)
                List<ItemPathSegment> segments = delta.getPath().getSegments();
                Long rowId = ((IdItemPathSegment) segments.get(1)).getId();
                QName name = ((NameItemPathSegment) segments.get(2)).getName();

                RLookupTableRow row = (RLookupTableRow) session.get(RLookupTableRow.class,
                        new RContainerId(RUtil.toInteger(rowId), tableOid));
                LookupTableRowType rowType = row.toJAXB();
                delta.setParentPath(ItemPath.EMPTY_PATH);
                delta.applyTo(rowType.asPrismContainerValue());
                if (!QNameUtil.match(name, LookupTableRowType.F_LAST_CHANGE_TIMESTAMP)) {
                    rowType.setLastChangeTimestamp(null); // in order to get filled in via toRepo call below
                }
                RLookupTableRow rowUpdated = RLookupTableRow.toRepo(tableOid, rowType);
                session.merge(rowUpdated);
            }
        }
    }

    private void deleteRowById(Session session, String tableOid, Long id) {
        Query query = session.getNamedQuery("delete.lookupTableDataRow");
        query.setString("oid", tableOid);
        query.setInteger("id", RUtil.toInteger(id));
        query.executeUpdate();
    }

    private void deleteRowByKey(Session session, String tableOid, String key) {
        Query query = session.getNamedQuery("delete.lookupTableDataRowByKey");
        query.setString("oid", tableOid);
        query.setString("key", key);
        query.executeUpdate();
    }

    public GetOperationOptions findLookupTableGetOption(Collection<SelectorOptions<GetOperationOptions>> options) {
        final ItemPath tablePath = new ItemPath(LookupTableType.F_ROW);

        Collection<SelectorOptions<GetOperationOptions>> filtered = SelectorOptions.filterRetrieveOptions(options);
        for (SelectorOptions<GetOperationOptions> option : filtered) {
            ObjectSelector selector = option.getSelector();
            if (selector == null) {
                // Ignore this. These are top-level options. There will not
                // apply to lookup table
                continue;
            }
            ItemPath selected = selector.getPath();

            if (tablePath.equivalent(selected)) {
                return option.getOptions();
            }
        }

        return null;
    }

    public <T extends ObjectType> void updateLoadedLookupTable(PrismObject<T> object,
            Collection<SelectorOptions<GetOperationOptions>> options, Session session) throws SchemaException {
        if (!SelectorOptions.hasToLoadPath(LookupTableType.F_ROW, options)) {
            return;
        }

        LOGGER.debug("Loading lookup table data.");

        GetOperationOptions getOption = findLookupTableGetOption(options);
        RelationalValueSearchQuery queryDef = getOption == null ? null : getOption.getRelationalValueSearchQuery();
        Criteria criteria = setupLookupTableRowsQuery(session, queryDef, object.getOid());
        if (queryDef != null && queryDef.getPaging() != null) {
            ObjectPaging paging = queryDef.getPaging();

            if (paging.getOffset() != null) {
                criteria.setFirstResult(paging.getOffset());
            }
            if (paging.getMaxSize() != null) {
                criteria.setMaxResults(paging.getMaxSize());
            }

            ItemPath orderByPath = paging.getOrderBy();
            if (paging.getDirection() != null && orderByPath != null && !orderByPath.isEmpty()) {
                if (orderByPath.size() > 1 || !(orderByPath.first() instanceof NameItemPathSegment)) {
                    throw new SchemaException("OrderBy has to consist of just one naming segment");
                }
                String orderBy = ((NameItemPathSegment) (orderByPath.first())).getName().getLocalPart();
                switch (paging.getDirection()) {
                case ASCENDING:
                    criteria.addOrder(Order.asc(orderBy));
                    break;
                case DESCENDING:
                    criteria.addOrder(Order.desc(orderBy));
                    break;
                }
            }
        }

        List<RLookupTableRow> rows = criteria.list();
        if (rows == null || rows.isEmpty()) {
            return;
        }

        LookupTableType lookup = (LookupTableType) object.asObjectable();
        List<LookupTableRowType> jaxbRows = lookup.getRow();
        for (RLookupTableRow row : rows) {
            LookupTableRowType jaxbRow = row.toJAXB();
            jaxbRows.add(jaxbRow);
        }
    }

    private Criteria setupLookupTableRowsQuery(Session session, RelationalValueSearchQuery queryDef, String oid) {
        Criteria criteria = session.createCriteria(RLookupTableRow.class);
        criteria.add(Restrictions.eq("ownerOid", oid));

        if (queryDef != null && queryDef.getColumn() != null && queryDef.getSearchType() != null
                && StringUtils.isNotEmpty(queryDef.getSearchValue())) {

            String param = queryDef.getColumn().getLocalPart();
            String value = queryDef.getSearchValue();
            if (LookupTableRowType.F_LABEL.equals(queryDef.getColumn())) {
                param = "label.norm";

                PolyString poly = new PolyString(value);
                poly.recompute(new PrismDefaultPolyStringNormalizer());
                value = poly.getNorm();
            }
            switch (queryDef.getSearchType()) {
            case EXACT:
                criteria.add(Restrictions.eq(param, value));
                break;
            case STARTS_WITH:
                criteria.add(Restrictions.like(param, value + "%"));
                break;
            case SUBSTRING:
                criteria.add(Restrictions.like(param, "%" + value + "%"));
            }
        }

        return criteria;
    }

    public <T extends ObjectType> Collection<? extends ItemDelta> filterLookupTableModifications(Class<T> type,
            Collection<? extends ItemDelta> modifications) {
        Collection<ItemDelta> tableDelta = new ArrayList<>();
        if (!LookupTableType.class.equals(type)) {
            return tableDelta;
        }

        ItemPath rowPath = new ItemPath(LookupTableType.F_ROW);
        for (ItemDelta delta : modifications) {
            ItemPath path = delta.getPath();
            if (path.isEmpty()) {
                throw new UnsupportedOperationException(
                        "Lookup table cannot be modified via empty-path modification");
            } else if (path.equivalent(rowPath)) {
                tableDelta.add(delta);
            } else if (path.isSuperPath(rowPath)) {
                // should be row[id]/xxx where xxx=key|value|label?
                List<ItemPathSegment> segments = path.getSegments();
                if (segments.size() != 3 || !(segments.get(1) instanceof IdItemPathSegment)
                        || !(segments.get(2) instanceof NameItemPathSegment)) {
                    throw new UnsupportedOperationException(
                            "Unsupported modification path for lookup tables: " + path);
                }
                tableDelta.add(delta);
            }
        }

        modifications.removeAll(tableDelta);

        return tableDelta;
    }
}