/*
* Copyright 2014 - 2017 Blazebit.
*
* 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.blazebit.persistence.impl;
import com.blazebit.persistence.KeysetPage;
import com.blazebit.persistence.PagedList;
import com.blazebit.persistence.impl.builder.object.KeysetExtractionObjectBuilder;
import com.blazebit.persistence.impl.keyset.KeysetMode;
import com.blazebit.persistence.impl.keyset.KeysetPageImpl;
import com.blazebit.persistence.impl.keyset.KeysetPaginationHelper;
import javax.persistence.FlushModeType;
import javax.persistence.LockModeType;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;
import javax.persistence.Parameter;
import javax.persistence.PersistenceException;
import javax.persistence.TemporalType;
import javax.persistence.TypedQuery;
import java.io.Serializable;
import java.util.*;
public class PaginatedTypedQuery<X> implements TypedQuery<X> {
private final TypedQuery<?> countQuery;
private final TypedQuery<?> idQuery;
private final TypedQuery<X> objectQuery;
private final KeysetExtractionObjectBuilder<X> objectBuilder;
private final Object entityId;
private int firstResult;
private int pageSize;
private final boolean needsNewIdList;
private final boolean keysetExtraction;
private final KeysetMode keysetMode;
private final KeysetPage keysetPage;
public PaginatedTypedQuery(TypedQuery<?> countQuery, TypedQuery<?> idQuery, TypedQuery<X> objectQuery, KeysetExtractionObjectBuilder<X> objectBuilder, Object entityId, int firstResult, int pageSize, boolean needsNewIdList, boolean keysetExtraction, KeysetMode keysetMode, KeysetPage keysetPage) {
this.countQuery = countQuery;
this.idQuery = idQuery;
this.objectQuery = objectQuery;
this.objectBuilder = objectBuilder;
this.entityId = entityId;
this.firstResult = firstResult;
this.pageSize = pageSize;
this.needsNewIdList = needsNewIdList;
this.keysetExtraction = keysetExtraction;
this.keysetMode = keysetMode;
this.keysetPage = keysetPage;
}
@Override
@SuppressWarnings("unchecked")
public PagedList<X> getResultList() {
int queryFirstResult = firstResult;
int firstRow = firstResult;
long totalSize;
if (entityId == null) {
totalSize = (Long) countQuery.getSingleResult();
} else {
Object[] result = (Object[]) countQuery.getSingleResult();
totalSize = (Long) result[0];
if (result[1] == null) {
// If the reference entity id is not contained (i.e. has no position), we return this special value
queryFirstResult = -1;
firstRow = 0;
} else {
// The page position is numbered from 1 so we need to correct this here
int position = ((Long) result[1]).intValue() - 1;
queryFirstResult = firstRow = position == 0 ? 0 : position - (position % pageSize);
}
}
if (totalSize == 0L) {
return new PagedListImpl<X>(null, totalSize, queryFirstResult, pageSize);
}
if (idQuery != null) {
idQuery.setMaxResults(pageSize);
if (keysetMode == KeysetMode.NONE) {
idQuery.setFirstResult(firstRow);
} else {
idQuery.setFirstResult(0);
}
List<?> ids = idQuery.getResultList();
if (ids.isEmpty()) {
KeysetPage newKeysetPage = null;
if (keysetMode == KeysetMode.NEXT) {
// When we scroll over the last page to a non existing one, we reuse the current keyset
newKeysetPage = keysetPage;
}
return new PagedListImpl<X>(newKeysetPage, totalSize, queryFirstResult, pageSize);
}
Serializable[] lowest = null;
Serializable[] highest = null;
if (needsNewIdList) {
if (keysetExtraction) {
lowest = KeysetPaginationHelper.extractKey((Object[]) ids.get(0), 1);
highest = KeysetPaginationHelper.extractKey((Object[]) ids.get(ids.size() - 1), 1);
}
List<Object> newIds = new ArrayList<Object>(ids.size());
for (int i = 0; i < ids.size(); i++) {
newIds.add(((Object[]) ids.get(i))[0]);
}
ids = newIds;
}
objectQuery.setParameter(AbstractCommonQueryBuilder.ID_PARAM_NAME, ids);
KeysetPage newKeyset = null;
if (keysetExtraction) {
newKeyset = new KeysetPageImpl(firstRow, pageSize, lowest, highest);
}
List<X> queryResultList = objectQuery.getResultList();
PagedList<X> pagedResultList = new PagedListImpl<X>(queryResultList, newKeyset, totalSize, queryFirstResult, pageSize);
return pagedResultList;
} else {
objectQuery.setMaxResults(pageSize);
if (keysetMode == KeysetMode.NONE) {
objectQuery.setFirstResult(firstRow);
} else {
objectQuery.setFirstResult(0);
}
List<X> result = objectQuery.getResultList();
if (result.isEmpty()) {
KeysetPage newKeysetPage = null;
if (keysetMode == KeysetMode.NEXT) {
// When we scroll over the last page to a non existing one, we reuse the current keyset
newKeysetPage = keysetPage;
}
return new PagedListImpl<X>(newKeysetPage, totalSize, queryFirstResult, pageSize);
}
if (keysetMode == KeysetMode.PREVIOUS) {
Collections.reverse(result);
}
KeysetPage newKeyset = null;
if (keysetExtraction) {
Serializable[] lowest = objectBuilder.getLowest();
Serializable[] highest = objectBuilder.getHighest();
newKeyset = new KeysetPageImpl(firstRow, pageSize, lowest, highest);
}
PagedList<X> pagedResultList = new PagedListImpl<X>(result, newKeyset, totalSize, queryFirstResult, pageSize);
return pagedResultList;
}
}
@Override
@SuppressWarnings("unchecked")
public X getSingleResult() {
List<X> result = getResultList();
if (result.size() == 0) {
throw new NoResultException("No entity found for query");
} else if (result.size() > 1) {
final Set<X> uniqueResult = new HashSet<X>(result);
if (uniqueResult.size() > 1) {
throw new NonUniqueResultException("result returns more than one element");
} else {
return uniqueResult.iterator().next();
}
} else {
return result.get(0);
}
}
@Override
public int executeUpdate() {
throw new IllegalArgumentException("Can not call executeUpdate on a select query!");
}
@Override
public TypedQuery<X> setMaxResults(int maxResult) {
throw new IllegalArgumentException("Updating max results is not supported on paginated query!");
}
@Override
public int getMaxResults() {
return pageSize;
}
@Override
public TypedQuery<X> setFirstResult(int startPosition) {
throw new IllegalArgumentException("Updating first result is not supported on paginated query!");
}
@Override
public int getFirstResult() {
return firstResult;
}
@Override
public TypedQuery<X> setHint(String hintName, Object value) {
// TODO: implement
throw new UnsupportedOperationException("Not yet implemented!");
}
@Override
public Map<String, Object> getHints() {
// TODO: implement
throw new UnsupportedOperationException("Not yet implemented!");
}
@Override
public <T> TypedQuery<X> setParameter(Parameter<T> param, T value) {
if (objectQuery.getParameter(param.getName()) != null) {
objectQuery.setParameter(param, value);
}
if (idQuery != null && idQuery.getParameter(param.getName()) != null) {
idQuery.setParameter(param, value);
}
if (countQuery.getParameter(param.getName()) != null) {
countQuery.setParameter(param, value);
}
return this;
}
@Override
public TypedQuery<X> setParameter(Parameter<Calendar> param, Calendar value, TemporalType temporalType) {
if (objectQuery.getParameter(param.getName()) != null) {
objectQuery.setParameter(param, value, temporalType);
}
if (idQuery != null && idQuery.getParameter(param.getName()) != null) {
idQuery.setParameter(param, value, temporalType);
}
if (countQuery.getParameter(param.getName()) != null) {
countQuery.setParameter(param, value, temporalType);
}
return this;
}
@Override
public TypedQuery<X> setParameter(Parameter<Date> param, Date value, TemporalType temporalType) {
if (objectQuery.getParameter(param.getName()) != null) {
objectQuery.setParameter(param, value, temporalType);
}
if (idQuery != null && idQuery.getParameter(param.getName()) != null) {
idQuery.setParameter(param, value, temporalType);
}
if (countQuery.getParameter(param.getName()) != null) {
countQuery.setParameter(param, value, temporalType);
}
return this;
}
@Override
public TypedQuery<X> setParameter(String name, Object value) {
return setParameter((Parameter<Object>) getParameter(name), value);
}
@Override
public TypedQuery<X> setParameter(String name, Calendar value, TemporalType temporalType) {
return setParameter(getParameter(name, Calendar.class), value, temporalType);
}
@Override
public TypedQuery<X> setParameter(String name, Date value, TemporalType temporalType) {
return setParameter(getParameter(name, Date.class), value, temporalType);
}
@Override
public TypedQuery<X> setParameter(int position, Object value) {
throw new IllegalArgumentException("Positional parameters unsupported!");
}
@Override
public TypedQuery<X> setParameter(int position, Calendar value, TemporalType temporalType) {
throw new IllegalArgumentException("Positional parameters unsupported!");
}
@Override
public TypedQuery<X> setParameter(int position, Date value, TemporalType temporalType) {
throw new IllegalArgumentException("Positional parameters unsupported!");
}
@Override
public Set<Parameter<?>> getParameters() {
Set<Parameter<?>> parameters = new HashSet<Parameter<?>>();
parameters.addAll(objectQuery.getParameters());
if (idQuery != null) {
parameters.addAll(idQuery.getParameters());
}
parameters.addAll(countQuery.getParameters());
return parameters;
}
@Override
public Parameter<?> getParameter(String name) {
Parameter<?> parameter = objectQuery.getParameter(name);
if (parameter != null) {
return parameter;
}
if (idQuery != null) {
parameter = idQuery.getParameter(name);
if (parameter != null) {
return parameter;
}
}
return countQuery.getParameter(name);
}
@Override
public <T> Parameter<T> getParameter(String name, Class<T> type) {
Parameter<T> parameter = objectQuery.getParameter(name, type);
if (parameter != null) {
return parameter;
}
if (idQuery != null) {
parameter = idQuery.getParameter(name, type);
if (parameter != null) {
return parameter;
}
}
return countQuery.getParameter(name, type);
}
@Override
public boolean isBound(Parameter<?> param) {
if (objectQuery.isBound(param)) {
return true;
}
if (idQuery != null && idQuery.isBound(param)) {
return true;
}
return countQuery.isBound(param);
}
@Override
public <T> T getParameterValue(Parameter<T> param) {
T value = objectQuery.getParameterValue(param);
if (value != null) {
return value;
}
if (idQuery != null) {
value = idQuery.getParameterValue(param);
if (value != null) {
return value;
}
}
return countQuery.getParameterValue(param);
}
@Override
public Object getParameterValue(String name) {
Object value = objectQuery.getParameterValue(name);
if (value != null) {
return value;
}
if (idQuery != null) {
value = idQuery.getParameterValue(name);
if (value != null) {
return value;
}
}
return countQuery.getParameterValue(name);
}
@Override
public Object getParameterValue(int position) {
throw new IllegalArgumentException("Positional parameters unsupported!");
}
@Override
public Parameter<?> getParameter(int position) {
throw new IllegalArgumentException("Positional parameters unsupported!");
}
@Override
public <T> Parameter<T> getParameter(int position, Class<T> type) {
throw new IllegalArgumentException("Positional parameters unsupported!");
}
@Override
public TypedQuery<X> setFlushMode(FlushModeType flushMode) {
objectQuery.setFlushMode(flushMode);
return this;
}
@Override
public FlushModeType getFlushMode() {
return objectQuery.getFlushMode();
}
@Override
public TypedQuery<X> setLockMode(LockModeType lockMode) {
objectQuery.setLockMode(lockMode);
return this;
}
@Override
public LockModeType getLockMode() {
return objectQuery.getLockMode();
}
@Override
public <T> T unwrap(Class<T> cls) {
throw new PersistenceException("Unsupported unwrap: " + cls.getName());
}
}