<commit_before>package io.agrest.runtime.cayenne.processor.update;

import io.agrest.AgObjectId;
import io.agrest.CompoundObjectId;
import io.agrest.EntityParent;
import io.agrest.EntityUpdate;
import io.agrest.AgException;
import io.agrest.ResourceEntity;
import io.agrest.SimpleObjectId;
import io.agrest.meta.AgEntity;
import io.agrest.meta.AgRelationship;
import io.agrest.processor.Processor;
import io.agrest.processor.ProcessorOutcome;
import io.agrest.runtime.cayenne.processor.Util;
import io.agrest.runtime.meta.IMetadataService;
import io.agrest.runtime.processor.update.UpdateContext;
import org.apache.cayenne.Cayenne;
import org.apache.cayenne.DataObject;
import org.apache.cayenne.Fault;
import org.apache.cayenne.ObjectContext;
import org.apache.cayenne.map.DbAttribute;
import org.apache.cayenne.map.DbEntity;
import org.apache.cayenne.map.ObjAttribute;
import org.apache.cayenne.map.ObjEntity;
import org.apache.cayenne.map.ObjRelationship;
import org.apache.cayenne.reflect.ClassDescriptor;

import javax.ws.rs.core.Response;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @since 2.7
 */
public abstract class CayenneUpdateDataStoreStage implements Processor<UpdateContext<?>> {

    private IMetadataService metadataService;

    public CayenneUpdateDataStoreStage(IMetadataService metadataService) {
        this.metadataService = metadataService;
    }

    @Override
    public ProcessorOutcome execute(UpdateContext<?> context) {
        sync((UpdateContext<DataObject>) context);
        CayenneUpdateStartStage.cayenneContext(context).commitChanges();

        // Stores parent-child result list in ResourceEntity
        // TODO Replace this by dedicated select child stage during of update stages refactoring
        ResourceEntity entity = context.getEntity();
        Map<String, ResourceEntity<?>> children = entity.getChildren();
        List rootResult = new ArrayList();
        for (EntityUpdate<?> u : context.getUpdates()) {
            DataObject o = (DataObject) u.getMergedTo();
            //saves root elements
            rootResult.add(o);
            //assigns children
            assignChildrenToParent(o, entity, children);
        }
        entity.setResult(rootResult);

        return ProcessorOutcome.CONTINUE;
    }

    protected void assignChildrenToParent(DataObject root, ResourceEntity<?> parent, Map<String, ResourceEntity<?>> children) {
        if (!children.isEmpty()) {
            for (Map.Entry<String, ResourceEntity<?>> e : children.entrySet()) {
                ResourceEntity childEntity = e.getValue();

                Object result = root.readPropertyDirectly(e.getKey());
                if (result == null || result instanceof Fault) {
                    continue;
                }
                AgObjectId id = root.getObjectId().getIdSnapshot().size() > 1
                        ? new CompoundObjectId(root.getObjectId().getIdSnapshot())
                        : new SimpleObjectId(root.getObjectId().getIdSnapshot().values().iterator().next());

                AgRelationship rel = parent.getAgEntity().getRelationship(e.getKey());
                if (rel.isToMany() && result instanceof List) {
                    List r = (List) result;

                    childEntity.setToManyResult(id, r);
                    for (Object ro : r) {
                        assignChildrenToParent((DataObject) ro, childEntity, childEntity.getChildren());
                    }

                } else {
                    childEntity.setToOneResult(id, result);
                    assignChildrenToParent((DataObject) result, childEntity, childEntity.getChildren());
                }
            }
        }
    }

    protected abstract <T extends DataObject> void sync(UpdateContext<T> context);

    protected <T extends DataObject> void create(UpdateContext<T> context) {

        ObjectRelator relator = createRelator(context);

        for (EntityUpdate<T> u : context.getUpdates()) {
            createSingle(context, relator, u);
        }
    }

    protected <T extends DataObject> void updateSingle(UpdateContext<T> context, T o, Collection<EntityUpdate<T>> updates) {

        ObjectRelator relator = createRelator(context);

        for (EntityUpdate<T> u : updates) {
            mergeChanges(u, o, relator);
        }

        relator.relateToParent(o);
    }

    protected <T extends DataObject> void createSingle(UpdateContext<T> context, ObjectRelator relator, EntityUpdate<T> u) {

        ObjectContext objectContext = CayenneUpdateStartStage.cayenneContext(context);
        DataObject o = objectContext.newObject(context.getType());
        Map<String, Object> idMap = u.getId();

        // set explicit ID
        if (idMap != null) {

            if (context.isIdUpdatesDisallowed() && u.isExplicitId()) {
                throw new AgException(Response.Status.BAD_REQUEST, "Setting ID explicitly is not allowed: " + idMap);
            }

            ObjEntity entity = objectContext.getEntityResolver().getObjEntity(context.getType());
            if (isPrimaryKey(entity.getDbEntity(), idMap.keySet())) {
                createSingleFromPk(entity, idMap, o);
            } else {
                // need to make an additional check that the AgId is unique
                checkExisting(objectContext, context.getEntity().getAgEntity(), idMap);
                createSingleFromPartialPk(entity, idMap, o);
            }
        }

        mergeChanges(u, o, relator);
        relator.relateToParent(o);
    }

    private void createSingleFromPk(ObjEntity entity, Map<String, Object> idMap, DataObject o) {
        for (DbAttribute pk : entity.getDbEntity().getPrimaryKeys()) {
            Object id = idMap.get(pk.getName());
            if (id != null) {
                setPrimaryKey(o, entity, pk, id);
            }
        }
    }

    private <T extends DataObject> void checkExisting(ObjectContext objectContext, AgEntity<T> agEntity, Map<String, Object> idMap) {
        T existing = Util.findById(objectContext, agEntity.getType(), agEntity, idMap);
        if (existing != null) {
            // TODO: printing "idMap" exposes column names, not JSON attributes
            throw new AgException(Response.Status.BAD_REQUEST, "Can't create '"
                    + agEntity.getName()
                    + "' with id "
                    + CompoundObjectId.mapToString(idMap)
                    + " - already exists");
        }
    }

    private void createSingleFromPartialPk(
            ObjEntity entity,
            Map<String, Object> idMap,
            DataObject o) {

        for (Map.Entry<String, Object> idPart : idMap.entrySet()) {

            DbAttribute maybePk = entity.getDbEntity().getAttribute(idPart.getKey());
            if (maybePk == null) {
                throw new AgException(Response.Status.BAD_REQUEST, "Can't create '"
                        + entity.getName()
                        + "' with id "
                        + CompoundObjectId.mapToString(idMap)
                        + " - not an ID DB attribute: "
                        + idPart.getKey());
            }

            if (maybePk.isPrimaryKey()) {
                setPrimaryKey(o, entity, maybePk, idPart.getValue());
            } else {

                ObjAttribute objAttribute = entity.getAttributeForDbAttribute(maybePk);
                if (objAttribute == null) {
                    throw new AgException(Response.Status.BAD_REQUEST, "Can't create '"<commit_msg>cleanup<commit_after>package io.agrest.runtime.cayenne.processor.update;

import io.agrest.AgObjectId;
import io.agrest.CompoundObjectId;
import io.agrest.EntityParent;
import io.agrest.EntityUpdate;
import io.agrest.AgException;
import io.agrest.ResourceEntity;
import io.agrest.SimpleObjectId;
import io.agrest.meta.AgEntity;
import io.agrest.meta.AgRelationship;
import io.agrest.processor.Processor;
import io.agrest.processor.ProcessorOutcome;
import io.agrest.runtime.cayenne.processor.Util;
import io.agrest.runtime.meta.IMetadataService;
import io.agrest.runtime.processor.update.UpdateContext;
import org.apache.cayenne.Cayenne;
import org.apache.cayenne.DataObject;
import org.apache.cayenne.Fault;
import org.apache.cayenne.ObjectContext;
import org.apache.cayenne.map.DbAttribute;
import org.apache.cayenne.map.DbEntity;
import org.apache.cayenne.map.ObjAttribute;
import org.apache.cayenne.map.ObjEntity;
import org.apache.cayenne.map.ObjRelationship;
import org.apache.cayenne.reflect.ClassDescriptor;

import javax.ws.rs.core.Response;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @since 2.7
 */
public abstract class CayenneUpdateDataStoreStage implements Processor<UpdateContext<?>> {

    private IMetadataService metadataService;

    public CayenneUpdateDataStoreStage(IMetadataService metadataService) {
        this.metadataService = metadataService;
    }

    @Override
    public ProcessorOutcome execute(UpdateContext<?> context) {
        sync((UpdateContext<DataObject>) context);
        CayenneUpdateStartStage.cayenneContext(context).commitChanges();

        // Stores parent-child result list in ResourceEntity
        // TODO Replace this by dedicated select child stage during of update stages refactoring
        ResourceEntity entity = context.getEntity();
        Map<String, ResourceEntity<?>> children = entity.getChildren();
        List rootResult = new ArrayList();
        for (EntityUpdate<?> u : context.getUpdates()) {
            DataObject o = (DataObject) u.getMergedTo();
            //saves root elements
            rootResult.add(o);
            //assigns children
            assignChildrenToParent(o, entity, children);
        }
        entity.setResult(rootResult);

        return ProcessorOutcome.CONTINUE;
    }

    protected void assignChildrenToParent(DataObject root, ResourceEntity<?> parent, Map<String, ResourceEntity<?>> children) {
        if (!children.isEmpty()) {
            for (Map.Entry<String, ResourceEntity<?>> e : children.entrySet()) {
                ResourceEntity childEntity = e.getValue();

                Object result = root.readPropertyDirectly(e.getKey());
                if (result == null || result instanceof Fault) {
                    continue;
                }
                AgObjectId id = root.getObjectId().getIdSnapshot().size() > 1
                        ? new CompoundObjectId(root.getObjectId().getIdSnapshot())
                        : new SimpleObjectId(root.getObjectId().getIdSnapshot().values().iterator().next());

                AgRelationship rel = parent.getAgEntity().getRelationship(e.getKey());
                if (rel.isToMany() && result instanceof List) {
                    List r = (List) result;

                    childEntity.setToManyResult(id, r);
                    for (Object ro : r) {
                        assignChildrenToParent((DataObject) ro, childEntity, childEntity.getChildren());
                    }

                } else {
                    childEntity.setToOneResult(id, result);
                    assignChildrenToParent((DataObject) result, childEntity, childEntity.getChildren());
                }
            }
        }
    }

    protected abstract <T extends DataObject> void sync(UpdateContext<T> context);

    protected <T extends DataObject> void create(UpdateContext<T> context) {

        ObjectRelator relator = createRelator(context);

        for (EntityUpdate<T> u : context.getUpdates()) {
            createSingle(context, relator, u);
        }
    }

    protected <T extends DataObject> void updateSingle(UpdateContext<T> context, T o, Collection<EntityUpdate<T>> updates) {

        ObjectRelator relator = createRelator(context);

        for (EntityUpdate<T> u : updates) {
            mergeChanges(u, o, relator);
        }

        relator.relateToParent(o);
    }

    protected <T extends DataObject> void createSingle(UpdateContext<T> context, ObjectRelator relator, EntityUpdate<T> u) {

        ObjectContext objectContext = CayenneUpdateStartStage.cayenneContext(context);
        DataObject o = objectContext.newObject(context.getType());
        Map<String, Object> idMap = u.getId();

        // set explicit ID
        if (idMap != null) {

            if (context.isIdUpdatesDisallowed() && u.isExplicitId()) {
                throw new AgException(Response.Status.BAD_REQUEST, "Setting ID explicitly is not allowed: " + idMap);
            }

            ObjEntity entity = objectContext.getEntityResolver().getObjEntity(context.getType());
            if (isPrimaryKey(entity.getDbEntity(), idMap.keySet())) {
                createSingleFromPk(entity, idMap, o);
            } else {
                // need to make an additional check that the AgId is unique
                checkExisting(objectContext, context.getEntity().getAgEntity(), idMap);
                createSingleFromIdValues(entity, idMap, o);
            }
        }

        mergeChanges(u, o, relator);
        relator.relateToParent(o);
    }

    private void createSingleFromPk(ObjEntity entity, Map<String, Object> idMap, DataObject o) {
        for (DbAttribute pk : entity.getDbEntity().getPrimaryKeys()) {
            Object id = idMap.get(pk.getName());
            if (id != null) {
                setPrimaryKey(o, entity, pk, id);
            }
        }
    }

    private <T extends DataObject> void checkExisting(ObjectContext objectContext, AgEntity<T> agEntity, Map<String, Object> idMap) {
        T existing = Util.findById(objectContext, agEntity.getType(), agEntity, idMap);
        if (existing != null) {
            // TODO: printing "idMap" exposes column names, not JSON attributes
            throw new AgException(Response.Status.BAD_REQUEST, "Can't create '"
                    + agEntity.getName()
                    + "' with id "
                    + CompoundObjectId.mapToString(idMap)
                    + " - already exists");
        }
    }

    private void createSingleFromIdValues(
            ObjEntity entity,
            Map<String, Object> idMap,
            DataObject o) {

        for (Map.Entry<String, Object> idPart : idMap.entrySet()) {

            DbAttribute maybePk = entity.getDbEntity().getAttribute(idPart.getKey());
            if (maybePk == null) {
                throw new AgException(Response.Status.BAD_REQUEST, "Can't create '"
                        + entity.getName()
                        + "' with id "
                        + CompoundObjectId.mapToString(idMap)
                        + " - not an ID DB attribute: "
                        + idPart.getKey());
            }

            if (maybePk.isPrimaryKey()) {
                setPrimaryKey(o, entity, maybePk, idPart.getValue());
            } else {

                ObjAttribute objAttribute = entity.getAttributeForDbAttribute(maybePk);
                if (objAttribute == null) {
                    throw new AgException(Response.Status.BAD_REQUEST, "Can't create '"
{'fraction_non_alphanumeric': 0.10409511228533685, 'fraction_numerical': 0.0003963011889035667, 'mean_word_length': 2.4032366824005393, 'pattern_counts': {'":': 0, '<': 67, '<?xml version=': 0, '>': 69, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6821494', 'n_tokens_mistral': 4101, 'n_tokens_neox': 3960, 'n_words': 876}