/*
* 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(datasourceName, 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;
}
}