/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.eas.server.handlers;

import com.eas.server.RequestHandler;
import com.eas.client.DatabasesClient;
import com.eas.client.SqlCompiledQuery;
import com.eas.client.SqlQuery;
import com.eas.client.changes.Change;
import com.eas.client.changes.Command;
import com.eas.client.login.AnonymousPlatypusPrincipal;
import com.eas.client.login.PlatypusPrincipal;
import com.eas.client.queries.LocalQueriesProxy;
import com.eas.client.threetier.json.ChangesJSONReader;
import com.eas.client.threetier.requests.CommitRequest;
import com.eas.script.Scripts;
import com.eas.server.PlatypusServerCore;
import com.eas.server.Session;
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.security.auth.AuthPermission;

/**
 *
 * @author pk, mg refactoring
 */
public class CommitRequestHandler extends RequestHandler<CommitRequest, CommitRequest.Response> {

    protected static class ChangesSortProcess {

        private final List<Change> expectedChanges;
        private final String defaultDatasource;
        private int factCalls;
        private final Consumer<Map<String, List<Change>>> onSuccess;
        private final Consumer<Exception> onFailure;

        private final Map<String, SqlCompiledQuery> entities = new HashMap<>();
        private final List<AccessControlException> accessDeniedEntities = new ArrayList<>();
        private final List<Exception> notRetrievedEntities = new ArrayList<>();

        public ChangesSortProcess(List<Change> aChanges, String aDefaultDatasource, Consumer<Map<String, List<Change>>> aOnSuccess, Consumer<Exception> aOnFailure) {
            super();
            expectedChanges = aChanges;
            defaultDatasource = aDefaultDatasource;
            onSuccess = aOnSuccess;
            onFailure = aOnFailure;
        }

        protected String assembleErrors() {
            if (accessDeniedEntities != null && !accessDeniedEntities.isEmpty()) {
                StringBuilder sb = new StringBuilder();
                Consumer<Exception> appender = (ex) -> {
                    if (sb.length() > 0) {
                        sb.append("\n");
                    }
                    sb.append(ex.getMessage());
                };
                accessDeniedEntities.stream().forEach(appender);
                notRetrievedEntities.stream().forEach(appender);
                return sb.toString();
            }
            return null;
        }

        public void complete(Change aChange, SqlQuery aQuery, AccessControlException accessDenied, Exception failed) {
            if (aChange != null && aQuery != null) {
                try {
                    SqlCompiledQuery entity = entities.get(aChange.entityName);
                    if (entity == null) {
                        entity = aQuery.compile();
                        entities.put(aChange.entityName, entity);
                    }
                    if (aChange instanceof Command) {
                        ((Command) aChange).command = entity.getSqlClause();
                    }
                } catch (Exception ex) {
                    Logger.getLogger(CommitRequestHandler.class.getName()).log(Level.SEVERE, null, ex);
                    notRetrievedEntities.add(ex);
                }
            }
            if (accessDenied != null) {
                accessDeniedEntities.add(accessDenied);
            }
            if (failed != null) {
                notRetrievedEntities.add(failed);
            }

            if (++factCalls == expectedChanges.size()) {
                if (accessDeniedEntities.isEmpty() && notRetrievedEntities.isEmpty()) {
                    if (onSuccess != null) {
                        Map<String, List<Change>> changeLogs = new HashMap<>();
                        expectedChanges.stream().forEach((Change aSortedChange) -> {
                            SqlCompiledQuery entity = entities.get(aSortedChange.entityName);
                            String datasourceName = entity.getDatasourceName();
                            // defaultDatasource is needed here to avoid multi transaction
                            // actions against the same datasource, leading to unexpected
                            // row-level locking and deadlocks in two phase transaction commit process
                            if (datasourceName == null || datasourceName.isEmpty()) {
                                datasourceName = defaultDatasource;
                            }
                            List<Change> targetChangeLog = changeLogs.get(datasourceName);
                            if (targetChangeLog == null) {
                                targetChangeLog = new ArrayList<>();
                                changeLogs.put(entity.getDatasourceName(), targetChangeLog);
                            }
                            targetChangeLog.add(aSortedChange);
                        });
                        onSuccess.accept(changeLogs);
                    }
                } else {
                    if (onFailure != null) {
                        onFailure.accept(new IllegalStateException(assembleErrors()));
                    }
                }
            }
        }
    }

    public CommitRequestHandler(PlatypusServerCore aServerCore, CommitRequest aRequest) {
        super(aServerCore, aRequest);
    }

    @Override
    public void handle(Session aSession, Consumer<CommitRequest.Response> onSuccess, Consumer<Exception> onFailure) {
        try {
            DatabasesClient client = getServerCore().getDatabasesClient();
            List<Change> changes = ChangesJSONReader.read(getRequest().getChangesJson(), Scripts.getSpace());
            ChangesSortProcess process = new ChangesSortProcess(changes, client.getDefaultDatasourceName(), (Map<String, List<Change>> changeLogs) -> {
                try {
                    client.commit(changeLogs, (Integer aUpdated) -> {
                        if (onSuccess != null) {
                            onSuccess.accept(new CommitRequest.Response(aUpdated));
                        }
                    }, onFailure);
                } catch (Exception ex) {
                    Logger.getLogger(CommitRequestHandler.class.getName()).log(Level.SEVERE, null, ex);
                }
            }, onFailure);
            if (changes.isEmpty()) {
                if (onSuccess != null) {
                    onSuccess.accept(new CommitRequest.Response(0));
                }
            } else {
                changes.stream().forEach((change) -> {
                    try {
                        ((LocalQueriesProxy) serverCore.getQueries()).getQuery(change.entityName, Scripts.getSpace(), (SqlQuery aQuery) -> {
                            if (aQuery.isPublicAccess()) {
                                AccessControlException aex = checkWritePrincipalPermission((PlatypusPrincipal) Scripts.getContext().getPrincipal(), change.entityName, aQuery.getWriteRoles());
                                if (aex != null) {
                                    process.complete(null, null, aex, null);
                                } else {
                                    process.complete(change, aQuery, null, null);
                                }
                            } else {
                                process.complete(null, null, new AccessControlException(String.format("Public access to query %s is denied while commiting changes for it's entity.", change.entityName)), null);
                            }
                        }, (Exception ex) -> {
                            process.complete(null, null, null, ex);
                        });
                    } catch (Exception ex) {
                        Logger.getLogger(CommitRequestHandler.class.getName()).log(Level.SEVERE, null, ex);
                    }
                });
            }
        } catch (Exception ex) {
            onFailure.accept(ex);
        }
    }

    private AccessControlException checkWritePrincipalPermission(PlatypusPrincipal aPrincipal, String aEntityId, Set<String> writeRoles) {
        if (writeRoles != null && !writeRoles.isEmpty()) {
            if (aPrincipal == null || !aPrincipal.hasAnyRole(writeRoles)) {
                return new AccessControlException(String.format("Access denied for write (entity: %s) for '%s'.", aEntityId != null ? aEntityId : "", aPrincipal != null ? aPrincipal.getName() : null), aPrincipal instanceof AnonymousPlatypusPrincipal ? new AuthPermission("*") : null);
            }
        }
        return null;
    }
}

@@ -5029,27 +5029,17 @@
 ogs.put(
-entity.getD
+d
 atasourc
@@ -5043,18 +5043,16 @@
 urceName
-()
 , target

fix fake multi transaction again