/*
* Copyright 2015 Samppa Saarela
*
* 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 org.javersion.store.jdbc;
import static com.querydsl.core.group.GroupBy.groupBy;
import static com.querydsl.core.types.dsl.Expressions.constant;
import static com.querydsl.core.types.dsl.Expressions.predicate;
import static com.querydsl.core.types.Ops.EQ;
import static com.querydsl.core.types.Ops.IN;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import org.javersion.core.VersionNode;
import org.javersion.path.PropertyPath;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.querydsl.sql.SQLQuery;
import com.querydsl.sql.dml.SQLInsertClause;
import com.querydsl.core.types.Order;
import com.querydsl.core.types.OrderSpecifier;
public class EntityUpdateBatch<Id extends Comparable, M, V extends JEntityVersion<Id>>
extends AbstractUpdateBatch<Id, M, V, EntityStoreOptions<Id, M, V>, EntityUpdateBatch<Id, M, V>> {
protected final SQLInsertClause entityCreateBatch;
protected final Set<Id> lockedDocIds;
private final Map<Id, Long> entityOrdinals;
public EntityUpdateBatch(EntityVersionStoreJdbc<Id, M, V> store, Collection<Id> docIds) {
super(store);
entityCreateBatch = options.queryFactory.insert(options.entity);
lockedDocIds = ImmutableSet.copyOf(docIds);
entityOrdinals = docIds.isEmpty() ? ImmutableMap.of() : lockEntitiesForUpdate(options, docIds);
}
public boolean contains(Id docId) {
return lockedDocIds.contains(docId);
}
public boolean isCreate(Id docId) {
return contains(docId) && !entityOrdinals.containsKey(docId);
}
public boolean isUpdate(Id docId) {
return contains(docId) && entityOrdinals.containsKey(docId);
}
public EntityUpdateBatch<Id, M, V> addVersion(Id docId, VersionNode<PropertyPath, Object, M> version) {
verifyDocId(docId);
if (isCreate(docId)) {
insertEntity(docId, version);
} else {
updateEntity(docId, version);
}
return super.addVersion(docId, version);
}
@Override
public void execute() {
if (isNotEmpty(entityCreateBatch)) {
entityCreateBatch.execute();
}
super.execute();
}
protected void insertEntity(Id docId, VersionNode<PropertyPath, Object, M> version) {
entityCreateBatch
.set(options.entity.id, docId)
.addBatch();
}
protected void updateEntity(Id docId, VersionNode<PropertyPath, Object, M> version) {}
@Override
protected void insertVersion(Id docId, VersionNode<PropertyPath, Object, M> version) {
versionBatch.set(options.version.localOrdinal, nextLocalOrdinal(docId));
super.insertVersion(docId, version);
}
protected Map<Id, Long> lockEntitiesForUpdate(EntityStoreOptions<Id, M, V> options, Collection<Id> docIds) {
return options.queryFactory
.from(options.entity)
.where(predicate(IN, options.entity.id, constant(docIds)))
.orderBy(new OrderSpecifier<>(Order.ASC, options.entity.id))
.forUpdate()
.transform(groupBy(options.entity.id).as(maxLocalOrdinalByEntity(options)));
}
protected SQLQuery<Long> maxLocalOrdinalByEntity(EntityStoreOptions<Id, M, V> options) {
return options.queryFactory
.select(options.version.localOrdinal.max())
.from(options.version)
.where(predicate(EQ, options.version.docId, options.entity.id))
.groupBy(options.entity.id);
}
protected void verifyDocId(Id docId) {
if (!contains(docId)) {
throw new IllegalStateException("docId not marked for inclusion in this batch: " + docId);
}
}
protected long nextLocalOrdinal(Id docId) {
long currentTime = System.currentTimeMillis();
long prevOrdinal = getPrevOrdinal(docId);
long nextOrdinal = prevOrdinal < currentTime ? currentTime : prevOrdinal + 1;
setPrevOrdinal(docId, nextOrdinal);
return nextOrdinal;
}
private long getPrevOrdinal(Id docId) {
Long prevOrdinal = entityOrdinals.get(docId);
return prevOrdinal != null ? prevOrdinal : Long.MIN_VALUE;
}
private void setPrevOrdinal(Id docId, long ordinal) {
entityOrdinals.put(docId, ordinal);
}
}