org.b3log.latke.remote.RepositoryAccessor.java Source code

Java tutorial

Introduction

Here is the source code for org.b3log.latke.remote.RepositoryAccessor.java

Source

/*
 * Copyright (c) 2009, 2010, 2011, 2012, 2013, B3log 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 org.b3log.latke.remote;

import java.io.BufferedReader;
import java.io.IOException;
import java.net.URLDecoder;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.time.DateUtils;
import org.b3log.latke.Keys;
import org.b3log.latke.Latkes;
import org.b3log.latke.RuntimeEnv;
import org.b3log.latke.model.Pagination;
import org.b3log.latke.repository.AbstractRepository;
import org.b3log.latke.repository.Query;
import org.b3log.latke.repository.Repositories;
import org.b3log.latke.repository.Repository;
import org.b3log.latke.repository.Transaction;
import org.b3log.latke.repository.jdbc.util.JdbcRepositories;
import org.b3log.latke.servlet.HTTPRequestContext;
import org.b3log.latke.servlet.HTTPRequestMethod;
import org.b3log.latke.servlet.annotation.RequestProcessing;
import org.b3log.latke.servlet.annotation.RequestProcessor;
import org.b3log.latke.servlet.renderer.JSONRenderer;
import org.b3log.latke.util.Strings;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

/**
 * Accesses repository via HTTP protocol.
 *
 * @author <a href="mailto:DL88250@gmail.com">Liang Ding</a>
 * @version 1.0.1.4, Jan 19, 2013
 */
@RequestProcessor
public final class RepositoryAccessor {

    /**
     * Logger.
     */
    private static final Logger LOGGER = Logger.getLogger(RepositoryAccessor.class.getName());

    /**
     * Gets whether repositories is writable.
     * 
     * <p>
     * Query parameters:
     * /latke/remote/repositories/writable?<em>userName=xxx&password=xxx</em><br/>
     * All parameters are required.
     * </p>
     * 
     * <p>
     * Renders response like the following: 
     * <pre>
     * {
     *     "sc":200,
     *     "writable": true,
     *     "msg":"Gets repositories writable[true]"
     * }
     * </pre>
     * </p>
     * 
     * @param context the specified HTTP request context
     * @param request the specified HTTP servlet request
     * @param response the specified HTTP servlet response 
     */
    @RequestProcessing(value = "/latke/remote/repositories/writable", method = HTTPRequestMethod.GET)
    public void getRepositoriesWritable(final HTTPRequestContext context, final HttpServletRequest request,
            final HttpServletResponse response) {
        final JSONRenderer renderer = new JSONRenderer();

        context.setRenderer(renderer);

        final JSONObject jsonObject = new JSONObject();

        renderer.setJSONObject(jsonObject);

        if (!authSucc(request, jsonObject)) {
            return;
        }

        final boolean writable = Repositories.getReposirotiesWritable();

        jsonObject.put(Keys.STATUS_CODE, HttpServletResponse.SC_OK);
        jsonObject.put(Keys.MSG, "Gets repositories writable[" + writable + "]");
        jsonObject.put("writable", writable);
    }

    /**
     * Sets whether repositories is writable.
     * 
     * <p>
     * Query parameters:
     * /latke/remote/repositories/writable?<em>userName=xxx&password=xxx&writable=true</em><br/>
     * All parameters are required.
     * </p>
     * 
     * <p>
     * Renders response like the following: 
     * <pre>
     * {
     *     "sc":200,
     *     "msg":"Sets repositories writable[true]"
     * }
     * </pre>
     * </p>
     * 
     * @param context the specified HTTP request context
     * @param request the specified HTTP servlet request
     * @param response the specified HTTP servlet response 
     */
    @RequestProcessing(value = "/latke/remote/repositories/writable", method = HTTPRequestMethod.PUT)
    public void setRepositoriesWritable(final HTTPRequestContext context, final HttpServletRequest request,
            final HttpServletResponse response) {
        final JSONRenderer renderer = new JSONRenderer();

        context.setRenderer(renderer);

        final JSONObject jsonObject = new JSONObject();

        renderer.setJSONObject(jsonObject);

        final String writable = request.getParameter("writable");

        if (!"true".equals(writable) && !"false".equals(writable)) {
            jsonObject.put(Keys.STATUS_CODE, HttpServletResponse.SC_BAD_REQUEST);
            jsonObject.put(Keys.MSG, "Requires parameter[writable], optional value is [true] or [false]");

            return;
        }

        if (!authSucc(request, jsonObject)) {
            return;
        }

        Repositories.setRepositoriesWritable(Boolean.parseBoolean(writable));

        jsonObject.put(Keys.STATUS_CODE, HttpServletResponse.SC_OK);
        jsonObject.put(Keys.MSG, "Sets repositories writable[" + writable + "]");
    }

    /**
     * Gets repository names.
     * 
     * <p>
     * Query parameters:
     * /latke/remote/repository/names?<em>userName=xxx&password=xxx</em><br/>
     * All parameters are required.
     * </p>
     * 
     * <p>
     * Renders response like the following: 
     * <pre>
     * {
     *     "sc":200,
     *     "msg":"Got data",
     *     "repositoryNames" : [
     *         "repository1", "repository2", ....
     *     ] 
     * }
     * </pre>
     * </p>
     * 
     * @param context the specified HTTP request context
     * @param request the specified HTTP servlet request
     * @param response the specified HTTP servlet response 
     */
    @RequestProcessing(value = "/latke/remote/repository/names", method = HTTPRequestMethod.GET)
    public void getRepositoryNames(final HTTPRequestContext context, final HttpServletRequest request,
            final HttpServletResponse response) {
        final JSONRenderer renderer = new JSONRenderer();

        context.setRenderer(renderer);

        final JSONObject jsonObject = new JSONObject();

        renderer.setJSONObject(jsonObject);

        jsonObject.put(Keys.STATUS_CODE, HttpServletResponse.SC_OK);
        jsonObject.put(Keys.MSG, "Got data");

        if (!authSucc(request, jsonObject)) {
            return;
        }

        jsonObject.put("repositoryNames", Repositories.getRepositoryNames());
    }

    /**
     * Gets repository data.
     * 
     * <p>
     * Query parameters:
     * /latke/remote/repository/data?<em>userName=xxx&password=xxx&repositoryName=xxx&pageNum=xxx&pageSize=xxx</em><br/>
     * All parameters are required.
     * </p>
     * 
     * <p>
     * Renders response like the following:
     * <pre>
     * {
     *   "sc":200,
     *   "msg":"Got data",
     *   "pagination":{
     *      "paginationPageCount":11
     *   },
     *   "rslts":[{}, {}, ....]
     * }
     * </pre>
     * </p>
     * 
     * @param context the specified HTTP request context
     * @param request the specified HTTP servlet request
     * @param response the specified HTTP servlet response 
     */
    @RequestProcessing(value = "/latke/remote/repository/data", method = HTTPRequestMethod.GET)
    public void getData(final HTTPRequestContext context, final HttpServletRequest request,
            final HttpServletResponse response) {
        final JSONRenderer renderer = new JSONRenderer();

        context.setRenderer(renderer);

        final JSONObject jsonObject = new JSONObject();

        renderer.setJSONObject(jsonObject);

        jsonObject.put(Keys.STATUS_CODE, HttpServletResponse.SC_OK);
        jsonObject.put(Keys.MSG, "Got data");

        if (badGetDataRequest(request, jsonObject) || !authSucc(request, jsonObject)) {
            return;
        }

        final String repositoryName = request.getParameter("repositoryName");
        final Repository repository = Repositories.getRepository(repositoryName);

        if (null == repository) {
            jsonObject.put(Keys.STATUS_CODE, HttpServletResponse.SC_BAD_REQUEST);
            jsonObject.put(Keys.MSG, "Not found repository[name=" + repositoryName + "]");

            return;
        }

        final boolean cacheEnabled = repository.isCacheEnabled();

        repository.setCacheEnabled(false);

        final Query query = new Query().setCurrentPageNum(Integer.valueOf(request.getParameter("pageNum")))
                .setPageSize(Integer.valueOf(request.getParameter("pageSize")));

        try {
            final JSONObject result = repository.get(query);
            final JSONObject pagination = result.getJSONObject(Pagination.PAGINATION);
            final JSONArray data = result.getJSONArray(Keys.RESULTS);

            jsonObject.put(Pagination.PAGINATION, pagination);
            jsonObject.put(Keys.RESULTS, data);
        } catch (final Exception e) {
            LOGGER.log(Level.SEVERE, "Gets data failed", e);

            jsonObject.put(Keys.STATUS_CODE, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            jsonObject.put(Keys.MSG, "Gets data failed[errorMsg=" + e.getMessage() + "]");
        } finally {
            repository.setCacheEnabled(cacheEnabled);
        }
    }

    /**
     * Puts data to repository.
     * 
     * <p>
     * Query parameters:
     * /latke/remote/repository/data?<em>userName=xxx&password=xxx&repositoryName=xxx</em><br/>
     * All parameters are required.
     * </p>
     * 
     * <p>
     * The post body, for example, "data": {....} or [] // JSON object or JSON array, content of the backup file
     * </p>
     * 
     * <p>
     * Renders response like the following:
     * <pre>
     * {
     *   "sc":200,
     *   "msg":"Put data"
     * }
     * </pre>
     * </p>
     * 
     * @param context the specified HTTP request context
     * @param request the specified HTTP servlet request
     * @param response the specified HTTP servlet response 
     */
    @RequestProcessing(value = "/latke/remote/repository/data", method = HTTPRequestMethod.POST)
    public void putData(final HTTPRequestContext context, final HttpServletRequest request,
            final HttpServletResponse response) {
        final JSONRenderer renderer = new JSONRenderer();

        context.setRenderer(renderer);

        final JSONObject jsonObject = new JSONObject();

        renderer.setJSONObject(jsonObject);

        jsonObject.put(Keys.STATUS_CODE, HttpServletResponse.SC_OK);
        jsonObject.put(Keys.MSG, "Put data");

        final StringBuilder dataBuilder = new StringBuilder();

        if (badPutDataRequest(request, jsonObject, dataBuilder) || !authSucc(request, jsonObject)) {
            return;
        }

        final String repositoryName = request.getParameter("repositoryName");
        Repository repository = Repositories.getRepository(repositoryName);

        if (null == repository) {
            repository = new AbstractRepository(repositoryName) {
            };
        }

        repository.setCacheEnabled(false);

        final Transaction transaction = repository.beginTransaction();

        try {
            final String dataContent = dataBuilder.toString();
            final JSONArray data = new JSONArray(dataContent);

            for (int i = 0; i < data.length(); i++) {
                final JSONObject record = data.getJSONObject(i);

                // Date type fixing
                final JSONArray keysDescription = Repositories.getRepositoryKeysDescription(repositoryName);

                for (int j = 0; j < keysDescription.length(); j++) {
                    final JSONObject keyDescription = keysDescription.optJSONObject(j);
                    final String key = keyDescription.optString("name");
                    final String type = keyDescription.optString("type");

                    if ("Date".equals(type)) {
                        final Locale defaultLocale = Locale.getDefault();

                        Locale.setDefault(Locale.US);
                        record.put(key,
                                DateUtils.parseDate(record.optString(key),
                                        new String[] { "EEE MMM dd HH:mm:ss z yyyy", "EEE MMM d HH:mm:ss z yyyy",
                                                "yyyy-MM-dd HH:mm:ss.SSS" }));
                        Locale.setDefault(defaultLocale);
                    }
                }

                repository.add(record);
            }

            transaction.commit();
        } catch (final Exception e) {
            if (transaction.isActive()) {
                transaction.rollback();
            }

            LOGGER.log(Level.SEVERE, "Puts data failed", e);

            jsonObject.put(Keys.STATUS_CODE, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            jsonObject.put(Keys.MSG, "Puts data failed[errorMsg=" + e.getMessage() + "]");
        } finally {
            repository.setCacheEnabled(true);
        }
    }

    /**
     * Creates tables.
     * 
     * <p>
     * Query parameters:
     * /latke/remote/repository/tables?<em>userName=xxx&password=xxx&repositoryName=xxx</em><br/>
     * All parameters are required.
     * </p>
     * 
     * <p>
     * Renders response like the following: 
     * <pre>
     * {
     *     "sc":200,
     *     "msg":"Created tables",
     * }
     * </pre>
     * </p>
     * 
     * @param context the specified HTTP request context
     * @param request the specified HTTP servlet request
     * @param response the specified HTTP servlet response 
     */
    @RequestProcessing(value = "/latke/remote/repository/tables", method = HTTPRequestMethod.PUT)
    public void createTables(final HTTPRequestContext context, final HttpServletRequest request,
            final HttpServletResponse response) {
        final JSONRenderer renderer = new JSONRenderer();

        context.setRenderer(renderer);

        final JSONObject jsonObject = new JSONObject();

        renderer.setJSONObject(jsonObject);

        jsonObject.put(Keys.STATUS_CODE, HttpServletResponse.SC_OK);
        jsonObject.put(Keys.MSG, "Created tables");

        if (!authSucc(request, jsonObject)) {
            return;
        }

        if (RuntimeEnv.GAE == Latkes.getRuntimeEnv()) {
            jsonObject.put(Keys.MSG, "GAE runtime enviorment dose not need to create tables");

            return;
        }

        JdbcRepositories.initAllTables();
    }

    /**
     * Determines whether the specified request is authenticated.
     * 
     * <p>
     * If the specified request is unauthenticated, puts {@link Keys#STATUS_CODE sc} and {@link Keys#MSG msg}
     * into the specified json object to render.
     * </p>
     * 
     * @param request the specified request
     * @param jsonObject the specified json object
     * @return {@code true} if authenticated, returns {@code false} otherwise
     */
    private boolean authSucc(final HttpServletRequest request, final JSONObject jsonObject) {
        if (!Latkes.isRemoteEnabled()) {
            jsonObject.put(Keys.STATUS_CODE, HttpServletResponse.SC_NOT_IMPLEMENTED);
            jsonObject.put(Keys.MSG, "Latke remote interfaces are disabled");
            return false;
        }

        final String userName = request.getParameter("userName");
        final String password = request.getParameter("password");

        if (Strings.isEmptyOrNull(userName)) {
            jsonObject.put(Keys.STATUS_CODE, HttpServletResponse.SC_BAD_REQUEST);
            jsonObject.put(Keys.MSG, "Requires parameter[userName]");
            return false;
        }

        if (Strings.isEmptyOrNull(password)) {
            jsonObject.put(Keys.STATUS_CODE, HttpServletResponse.SC_BAD_REQUEST);
            jsonObject.put(Keys.MSG, "Requires parameter[password]");
            return false;
        }

        final String repositoryAccessorUserName = Latkes.getRemoteProperty("repositoryAccessor.userName");
        final String repositoryAccessorPassword = Latkes.getRemoteProperty("repositoryAccessor.password");

        if (userName.equals(repositoryAccessorUserName) && password.equals(repositoryAccessorPassword)) {
            return true;
        }

        jsonObject.put(Keys.STATUS_CODE, HttpServletResponse.SC_FORBIDDEN);
        jsonObject.put(Keys.MSG, "Auth failed[userName=" + userName + ", password=" + password + "]");

        return false;
    }

    /**
     * Determines whether the specified get data request is bad.
     * 
     * <p>
     * If the specified request is bad, puts {@link Keys#STATUS_CODE sc} and {@link Keys#MSG msg}
     * into the specified json object to render.
     * </p>
     * 
     * @param request the specified request
     * @param jsonObject the specified jsonObject
     * @return {@code true} if it is bad, returns {@code false} otherwise
     */
    private boolean badGetDataRequest(final HttpServletRequest request, final JSONObject jsonObject) {
        final String repositoryName = request.getParameter("repositoryName");
        final String pageNumString = request.getParameter("pageNum");
        final String pageSizeString = request.getParameter("pageSize");

        if (Strings.isEmptyOrNull(repositoryName)) {
            jsonObject.put(Keys.STATUS_CODE, HttpServletResponse.SC_BAD_REQUEST);
            jsonObject.put(Keys.MSG, "Requires parameter[repositoryName]");
            return true;
        }

        if (Strings.isEmptyOrNull(pageNumString)) {
            jsonObject.put(Keys.STATUS_CODE, HttpServletResponse.SC_BAD_REQUEST);
            jsonObject.put(Keys.MSG, "Requires parameter[pageNum]");
            return true;
        }

        if (Strings.isEmptyOrNull(pageSizeString)) {
            jsonObject.put(Keys.STATUS_CODE, HttpServletResponse.SC_BAD_REQUEST);
            jsonObject.put(Keys.MSG, "Requires parameter[pageSize]");
            return true;
        }

        try {
            Integer.parseInt(pageNumString);
        } catch (final Exception e) {
            jsonObject.put(Keys.STATUS_CODE, HttpServletResponse.SC_BAD_REQUEST);
            jsonObject.put(Keys.MSG, "Parameter[pageNum] must be a integer");
            return true;
        }

        try {
            Integer.parseInt(pageSizeString);
        } catch (final Exception e) {
            jsonObject.put(Keys.STATUS_CODE, HttpServletResponse.SC_BAD_REQUEST);
            jsonObject.put(Keys.MSG, "Parameter[pageSize] must be a integer");
            return true;
        }

        return false;
    }

    /**
     * Determines whether the specified put data request is bad.
     * 
     * <p>
     * If the specified request is bad, puts {@link Keys#STATUS_CODE sc} and {@link Keys#MSG msg}
     * into the specified json object to render.
     * </p>
     * 
     * @param request the specified request
     * @param jsonObject the specified jsonObject
     * @param dataBuilder the specified data builder
     * @return {@code true} if it is bad, returns {@code false} otherwise
     */
    private boolean badPutDataRequest(final HttpServletRequest request, final JSONObject jsonObject,
            final StringBuilder dataBuilder) {
        final String repositoryName = request.getParameter("repositoryName");

        if (Strings.isEmptyOrNull(repositoryName)) {
            jsonObject.put(Keys.STATUS_CODE, HttpServletResponse.SC_BAD_REQUEST);
            jsonObject.put(Keys.MSG, "Requires parameter[repositoryName]");
            return true;
        }

        String dataContent = request.getParameter("data");

        if (Strings.isEmptyOrNull(dataContent)) {
            try {
                final BufferedReader reader = request.getReader();

                dataContent = IOUtils.toString(reader);
                final String str = dataContent.split("=")[1];

                dataContent = URLDecoder.decode(str, "UTF-8");
            } catch (final IOException e) {
                LOGGER.log(Level.WARNING, e.getMessage(), e);
            }
        }

        if (Strings.isEmptyOrNull(dataContent)) {
            jsonObject.put(Keys.STATUS_CODE, HttpServletResponse.SC_BAD_REQUEST);
            jsonObject.put(Keys.MSG, "Requires parameter[data]");
            return true;
        }

        try {
            new JSONArray(dataContent);
        } catch (final JSONException e) {
            jsonObject.put(Keys.STATUS_CODE, HttpServletResponse.SC_BAD_REQUEST);
            jsonObject.put(Keys.MSG, "Parameter[data] must be a JSON object or a JSON array");
            return true;
        }

        dataBuilder.append(dataContent);

        return false;
    }
}