com.clarionmedia.infinitum.http.rest.impl.RestfulSession.java Source code

Java tutorial

Introduction

Here is the source code for com.clarionmedia.infinitum.http.rest.impl.RestfulSession.java

Source

/*
 * Copyright (c) 2012 Tyler Treat
 * 
 * This file is part of Infinitum Framework.
 *
 * Infinitum Framework is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Infinitum Framework is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Infinitum Framework.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.clarionmedia.infinitum.http.rest.impl;

import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.apache.http.HttpStatus;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;

import android.database.SQLException;

import com.clarionmedia.infinitum.context.InfinitumContext;
import com.clarionmedia.infinitum.context.RestfulContext;
import com.clarionmedia.infinitum.context.RestfulContext.MessageType;
import com.clarionmedia.infinitum.di.annotation.Autowired;
import com.clarionmedia.infinitum.di.annotation.PostConstruct;
import com.clarionmedia.infinitum.exception.InfinitumRuntimeException;
import com.clarionmedia.infinitum.http.rest.RestfulClient;
import com.clarionmedia.infinitum.http.rest.RestfulMapper;
import com.clarionmedia.infinitum.http.rest.RestfulModelMap;
import com.clarionmedia.infinitum.internal.Preconditions;
import com.clarionmedia.infinitum.internal.caching.LruCache;
import com.clarionmedia.infinitum.logging.Logger;
import com.clarionmedia.infinitum.orm.Session;
import com.clarionmedia.infinitum.orm.criteria.Criteria;
import com.clarionmedia.infinitum.orm.exception.SQLGrammarException;
import com.clarionmedia.infinitum.orm.persistence.PersistencePolicy;
import com.clarionmedia.infinitum.orm.persistence.TypeAdapter;

/**
 * <p>
 * {@link Session} implementation for communicating with a RESTful web service
 * using domain objects. Infinitum provides two concrete implementations called
 * {@link RestfulJsonClient}, which is used for web services that respond with
 * JSON, and {@link RestfulXmlClient}, which is used for web services that
 * respond with XML. These can be extended or re-implemented for specific
 * business needs.
 * </p>
 * 
 * @author Tyler Treat
 * @version 1.0 02/27/12
 * @since 1.0
 */
public abstract class RestfulSession implements Session {

    protected static final String ENCODING = "UTF-8";

    @Autowired
    protected PersistencePolicy mPersistencePolicy;

    @Autowired
    protected InfinitumContext mInfinitumContext;

    @Autowired
    protected RestfulContext mRestContext;

    protected boolean mIsOpen;
    protected String mHost;
    protected Logger mLogger;
    protected RestfulMapper mMapper;
    protected RestfulClient mRestClient;
    protected LruCache<Integer, Object> mSessionCache;
    protected int mCacheSize;

    /**
     * Creates a new {@code RestfulSession} with the given
     * {@link InfinitumContext} and cache size.
     * 
     * @param context
     *            the {@code InfinitumContext} of the {@code Session}
     * @param cacheSize
     *            the maximum number of {@code Objects} the {@code Session}
     *            cache can store
     */
    public RestfulSession() {
        mCacheSize = DEFAULT_CACHE_SIZE;
        mSessionCache = new LruCache<Integer, Object>(mCacheSize);
    }

    @PostConstruct
    private void init() {
        mLogger = Logger.getInstance(mInfinitumContext, getClass().getSimpleName());
        switch (mRestContext.getMessageType()) {
        case XML:
            mMapper = mInfinitumContext.getBean("$RestfulXmlMapper", RestfulXmlMapper.class);
            break;
        case JSON:
            mMapper = mInfinitumContext.getBean("$RestfulJsonMapper", RestfulJsonMapper.class);
            break;
        default:
            mMapper = mInfinitumContext.getBean("$RestfulNameValueMapper", RestfulNameValueMapper.class);
        }
        mHost = mRestContext.getRestHost();
        if (!mHost.endsWith("/"))
            mHost += '/';
        mRestClient = new CachingEnabledRestfulClient(mInfinitumContext);
        mRestClient.setHttpParams(getHttpParams());
    }

    /**
     * Returns an instance of the given persistent model {@link Class} as
     * identified by the specified primary key or {@code null} if no such entity
     * exists.
     * 
     * @param c
     *            the {@code Class} of the persistent instance to load
     * @param id
     *            the primary key value of the persistent instance to load
     * @return the persistent instance
     */
    protected abstract <T> T loadEntity(Class<T> type, Serializable id);

    @Override
    public Session open() throws SQLException {
        mIsOpen = true;
        mLogger.debug("Session opened");
        return this;
    }

    @Override
    public Session close() {
        recycleCache();
        mIsOpen = false;
        mLogger.debug("Session closed");
        return this;
    }

    @Override
    public boolean isOpen() {
        return mIsOpen;
    }

    @Override
    public Session beginTransaction() {
        throw new UnsupportedOperationException("RestfulSession does not support transactions!");
    }

    @Override
    public Session commit() {
        throw new UnsupportedOperationException("RestfulSession does not support transactions!");
    }

    @Override
    public Session rollback() {
        throw new UnsupportedOperationException("RestfulSession does not support transactions!");
    }

    @Override
    public boolean isTransactionOpen() {
        throw new UnsupportedOperationException("RestfulSession does not support transactions!");
    }

    @Override
    public Session setAutocommit(boolean autocommit) {
        throw new UnsupportedOperationException("RestfulSession does not support transactions!");
    }

    @Override
    public boolean isAutocommit() {
        throw new UnsupportedOperationException("RestfulSession does not support transactions!");
    }

    @Override
    public Session recycleCache() {
        mSessionCache.clear();
        return this;
    }

    @Override
    public Session setCacheSize(int cacheSize) {
        mCacheSize = cacheSize;
        return this;
    }

    @Override
    public int getCacheSize() {
        return mCacheSize;
    }

    @Override
    public boolean cache(int hash, Object model) {
        if (mSessionCache.size() >= mCacheSize)
            return false;
        mSessionCache.put(hash, model);
        return true;
    }

    @Override
    public boolean checkCache(int hash) {
        return mSessionCache.containsKey(hash);
    }

    @Override
    public Object searchCache(int hash) {
        return mSessionCache.get(hash);
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T load(Class<T> type, Serializable id) throws InfinitumRuntimeException, IllegalArgumentException {
        Preconditions.checkPersistenceForLoading(type, mPersistencePolicy);
        // TODO Validate primary key
        int objHash = mPersistencePolicy.computeModelHash(type, id);
        if (checkCache(objHash))
            return (T) searchCache(objHash);
        return loadEntity(type, id);
    }

    @Override
    public long save(Object model) {
        Preconditions.checkPersistenceForModify(model, mPersistencePolicy);
        mLogger.debug("Sending POST request to save entity");
        String uri = mHost + mPersistencePolicy.getRestEndpoint(model.getClass());
        Map<String, String> headers = new HashMap<String, String>();
        RestfulModelMap modelMap = mMapper.mapModel(model);
        if (mRestContext.getMessageType() == MessageType.JSON)
            headers.put("Content-Type", "application/json");
        else if (mRestContext.getMessageType() == MessageType.XML)
            headers.put("Content-Type", "application/xml");
        RestResponse response = mRestClient.executePost(uri, modelMap.toHttpEntity(), headers);
        if (response == null)
            return -1;
        return response.getStatusCode() < 400 ? 0 : -1;
    }

    @Override
    public boolean delete(Object model) {
        Preconditions.checkPersistenceForModify(model, mPersistencePolicy);
        mLogger.debug("Sending DELETE request to delete entity");
        Serializable pk = mPersistencePolicy.getPrimaryKey(model);
        String uri = mHost + mPersistencePolicy.getRestEndpoint(model.getClass()) + "/" + pk.toString();
        Map<String, String> headers = new HashMap<String, String>();
        RestResponse response = mRestClient.executeDelete(uri, headers);
        if (response == null)
            return false;
        switch (response.getStatusCode()) {
        case HttpStatus.SC_OK:
        case HttpStatus.SC_ACCEPTED:
        case HttpStatus.SC_NO_CONTENT:
            return true;
        default:
            return false;
        }
    }

    @Override
    public boolean update(Object model) throws InfinitumRuntimeException {
        Preconditions.checkPersistenceForModify(model, mPersistencePolicy);
        mLogger.debug("Sending PUT request to update entity");
        String uri = mHost + mPersistencePolicy.getRestEndpoint(model.getClass());
        Map<String, String> headers = new HashMap<String, String>();
        RestfulModelMap modelMap = mMapper.mapModel(model);
        if (mRestContext.getMessageType() == MessageType.JSON)
            headers.put("Content-Type", "application/json");
        else if (mRestContext.getMessageType() == MessageType.XML)
            headers.put("Content-Type", "application/xml");
        RestResponse response = mRestClient.executePut(uri, modelMap.toHttpEntity(), headers);
        switch (response.getStatusCode()) {
        case HttpStatus.SC_OK:
        case HttpStatus.SC_NO_CONTENT:
            return true;
        default:
            return false;
        }
    }

    /**
     * Makes an HTTP request to the web service to update the given model or
     * save it if it does not exist in the database.
     * 
     * @param model
     *            the model to save or update
     * @return 0 if the model was updated, 1 if the model was saved, or -1 if
     *         the operation failed
     */
    @Override
    public long saveOrUpdate(Object model) {
        Preconditions.checkPersistenceForModify(model, mPersistencePolicy);
        mLogger.debug("Sending PUT request to save or update entity");
        String uri = mHost + mPersistencePolicy.getRestEndpoint(model.getClass());
        Map<String, String> headers = new HashMap<String, String>();
        RestfulModelMap modelMap = mMapper.mapModel(model);
        if (mRestContext.getMessageType() == MessageType.JSON)
            headers.put("Content-Type", "application/json");
        else if (mRestContext.getMessageType() == MessageType.XML)
            headers.put("Content-Type", "application/xml");
        RestResponse response = mRestClient.executePut(uri, modelMap.toHttpEntity(), headers);
        switch (response.getStatusCode()) {
        case HttpStatus.SC_CREATED:
            return 1;
        case HttpStatus.SC_OK:
        case HttpStatus.SC_NO_CONTENT:
            return 0;
        default:
            return -1;
        }
    }

    @Override
    public int saveOrUpdateAll(Collection<? extends Object> models) throws InfinitumRuntimeException {
        int count = 0;
        for (Object model : models) {
            if (saveOrUpdate(model) >= 0)
                count++;
        }
        return count;
    }

    @Override
    public int saveAll(Collection<? extends Object> models) throws InfinitumRuntimeException {
        int count = 0;
        for (Object model : models) {
            if (save(model) == 0)
                count++;
        }
        return count;
    }

    @Override
    public int deleteAll(Collection<? extends Object> models) throws InfinitumRuntimeException {
        int count = 0;
        for (Object model : models) {
            if (delete(model))
                count++;
        }
        return count;
    }

    @Override
    public Session execute(String sql) throws SQLGrammarException {
        throw new UnsupportedOperationException("RestfulSession does not support SQL operations!");
    }

    @Override
    public <T> Criteria<T> createCriteria(Class<T> entityClass) {
        throw new UnsupportedOperationException("RestfulSession does not support criteria operations!");
    }

    @Override
    public <T> Session registerTypeAdapter(Class<T> type, TypeAdapter<T> adapter) {
        mMapper.registerTypeAdapter(type, adapter);
        return this;
    }

    @Override
    public Map<Class<?>, ? extends TypeAdapter<?>> getRegisteredTypeAdapters() {
        return mMapper.getRegisteredTypeAdapters();
    }

    /**
     * Returns a {@link HttpParams} configured using the
     * {@link InfinitumContext}.
     * 
     * @return {@code HttpParams}
     */
    protected HttpParams getHttpParams() {
        HttpParams httpParams = new BasicHttpParams();
        HttpConnectionParams.setConnectionTimeout(httpParams, mRestContext.getConnectionTimeout());
        HttpConnectionParams.setSoTimeout(httpParams, mRestContext.getResponseTimeout());
        HttpConnectionParams.setTcpNoDelay(httpParams, true);
        return httpParams;
    }

}