All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.querydsl.jdo.AbstractJDOQuery Maven / Gradle / Ivy

/*
 * Copyright 2015, The Querydsl Team (http://www.querydsl.com/team)
 *
 * 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.querydsl.jdo;

import java.io.Closeable;
import java.io.IOException;
import java.util.*;

import javax.annotation.Nullable;
import javax.jdo.JDOUserException;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

import com.google.common.collect.Lists;
import com.mysema.commons.lang.CloseableIterator;
import com.mysema.commons.lang.IteratorAdapter;
import com.querydsl.core.*;
import com.querydsl.core.support.FetchableSubQueryBase;
import com.querydsl.core.types.*;

/**
 * Abstract base class for custom implementations of the {@link JDOQLQuery} interface.
 *
 * @author tiwe
 *
 * @param  result type
 * @param  concrete subclass
 */
public abstract class AbstractJDOQuery> extends FetchableSubQueryBase implements JDOQLQuery {

    private static final Logger logger = LoggerFactory.getLogger(JDOQuery.class);

    private final Closeable closeable = new Closeable() {
        @Override
        public void close() throws IOException {
            AbstractJDOQuery.this.close();
        }
    };

    private final boolean detach;

    private List orderedConstants = new ArrayList();

    @Nullable
    private final PersistenceManager persistenceManager;

    private final List queries = new ArrayList(2);

    private final JDOQLTemplates templates;

    protected final Set fetchGroups = new HashSet();

    @Nullable
    protected Integer maxFetchDepth;

    @Nullable
    private FactoryExpression projection;

    public AbstractJDOQuery(@Nullable PersistenceManager persistenceManager) {
        this(persistenceManager, JDOQLTemplates.DEFAULT, new DefaultQueryMetadata(), false);
    }

    @SuppressWarnings("unchecked")
    public AbstractJDOQuery(
            @Nullable PersistenceManager persistenceManager,
            JDOQLTemplates templates,
            QueryMetadata metadata, boolean detach) {
        super(new JDOQueryMixin(metadata));
        this.queryMixin.setSelf((Q) this);
        this.templates = templates;
        this.persistenceManager = persistenceManager;
        this.detach = detach;
    }

    /**
     * Add the fetch group to the set of active fetch groups.
     *
     * @param fetchGroupName fetch group name
     * @return the current object
     */
    @Override
    public Q addFetchGroup(String fetchGroupName) {
        fetchGroups.add(fetchGroupName);
        return queryMixin.getSelf();
    }

    /**
     * Close the query and related resources
     */
    @Override
    public void close() {
        for (Query query : queries) {
            query.closeAll();
        }
    }

    @Override
    public long fetchCount() {
        try {
            Query query = createQuery(true);
            query.setUnique(true);
            Long rv = (Long) execute(query, true);
            if (rv != null) {
                return rv;
            } else {
                throw new QueryException("Query returned null");
            }
        } finally {
            reset();
        }

    }

    private Expression getSource() {
        return queryMixin.getMetadata().getJoins().get(0).getTarget();
    }

    private Query createQuery(boolean forCount) {
        Expression source = getSource();

        // serialize
        JDOQLSerializer serializer = new JDOQLSerializer(getTemplates(), source);
        serializer.serialize(queryMixin.getMetadata(), forCount, false);

        logQuery(serializer.toString(), serializer.getConstantToLabel());

        // create Query
        Query query = persistenceManager.newQuery(serializer.toString());
        orderedConstants = serializer.getConstants();
        queries.add(query);

        if (!forCount) {
            Expression projection = queryMixin.getMetadata().getProjection();
            if (projection instanceof FactoryExpression) {
                this.projection = (FactoryExpression) projection;
            }
            if (!fetchGroups.isEmpty()) {
                query.getFetchPlan().setGroups(fetchGroups);
            }
            if (maxFetchDepth != null) {
                query.getFetchPlan().setMaxFetchDepth(maxFetchDepth);
            }
        }

        return query;
    }

    protected void logQuery(String queryString, Map parameters) {
        if (logger.isDebugEnabled()) {
            String normalizedQuery = queryString.replace('\n', ' ');
            MDC.put(MDC_QUERY, normalizedQuery);
            MDC.put(MDC_PARAMETERS, String.valueOf(parameters));
            logger.debug(normalizedQuery);
        }
    }

    protected void cleanupMDC() {
        MDC.remove(MDC_QUERY);
        MDC.remove(MDC_PARAMETERS);
    }

    @SuppressWarnings("unchecked")
    private  T detach(T results) {
        if (results instanceof Collection) {
            return (T) persistenceManager.detachCopyAll((Collection) results);
        } else {
            return persistenceManager.detachCopy(results);
        }
    }

    private Object project(FactoryExpression expr, Object row) {
        if (row == null) {
            return null;
        } else if (row.getClass().isArray()) {
            return expr.newInstance((Object[]) row);
        } else {
            return expr.newInstance(new Object[]{row});
        }
    }

    @Nullable
    private Object execute(Query query, boolean forCount) {
        Object rv;
        if (!orderedConstants.isEmpty()) {
            rv = query.executeWithArray(orderedConstants.toArray());
        } else {
            rv = query.execute();
        }
        if (isDetach()) {
            rv = detach(rv);
        }
        if (projection != null && !forCount) {
            if (rv instanceof List) {
                List original = (List) rv;
                rv = Lists.newArrayList();
                for (Object o : original) {
                    ((List) rv).add(project(projection, o));
                }
            } else {
                rv = project(projection, rv);
            }
        }

        return rv;
    }

    @Override
    public Q from(EntityPath... args) {
        return queryMixin.from(args);
    }

    @Override
    public  Q from(CollectionExpression path, Path alias) {
        return queryMixin.from(ExpressionUtils.as((Path) path, alias));
    }

    public JDOQLTemplates getTemplates() {
        return templates;
    }

    public boolean isDetach() {
        return detach;
    }

    @Override
    public CloseableIterator iterate() {
        return new IteratorAdapter(fetch().iterator(), closeable);
    }

    @Override
    public List fetch() {
        try {
            Object rv = execute(createQuery(false), false);
            @SuppressWarnings("unchecked") // Compile time checking of user code mandates it to be T
            List result = rv instanceof List ? (List) rv : Collections.singletonList((T) rv);
            return result;
        } finally {
            reset();
        }
    }

    @Override
    public QueryResults fetchResults() {
        try {
            Query countQuery = createQuery(true);
            countQuery.setUnique(true);
            countQuery.setResult("count(this)");
            long total = (Long) execute(countQuery, true);
            if (total > 0) {
                QueryModifiers modifiers = queryMixin.getMetadata().getModifiers();
                Query query = createQuery(false);
                return new QueryResults((List) execute(query, false), modifiers, total);
            } else {
                return QueryResults.emptyResults();
            }
        } finally {
            reset();
        }
    }

    private void reset() {
        cleanupMDC();
    }

    /**
     * Set the maximum fetch depth when fetching.
     * A value of 0 has no meaning and will throw a {@link JDOUserException}.
     * A value of -1 means that no limit is placed on fetching.
     * A positive integer will result in that number of references from the
     * initial object to be fetched.
     *
     * @param depth fetch depth
     * @return the current object
     */
    @Override
    public Q setMaxFetchDepth(int depth) {
        maxFetchDepth = depth;
        return queryMixin.getSelf();
    }

    @Override
    public String toString() {
        if (!queryMixin.getMetadata().getJoins().isEmpty()) {
            Expression source = getSource();
            JDOQLSerializer serializer = new JDOQLSerializer(getTemplates(), source);
            serializer.serialize(queryMixin.getMetadata(), false, false);
            return serializer.toString().trim();
        } else {
            return super.toString();
        }
    }

    @Nullable
    @Override
    public T fetchOne() {
        if (getMetadata().getModifiers().getLimit() == null) {
            limit(2);
        }
        try {
            Query query = createQuery(false);
            Object rv = execute(query, false);
            if (rv instanceof List) {
                @SuppressWarnings("unchecked") // Compile time checking of user code mandates this
                List list = (List) rv;
                if (!list.isEmpty()) {
                    if (list.size() > 1) {
                        throw new NonUniqueResultException();
                    }
                    return list.get(0);
                } else {
                    return null;
                }
            } else {
                // it is not a List typed expression
                @SuppressWarnings("unchecked") // Compile time checking of user code mandates this
                T result = (T) rv;
                return result;
            }
        } finally {
            reset();
        }

    }

}