text:
package org.b3log.symphony.processor;

import com.qiniu.util.Auth;
import java.awt.Graphics;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.imageio.ImageIO;
import javax.inject.Inject;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import jodd.util.Base64;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.b3log.latke.Keys;
import org.b3log.latke.Latkes;
import org.b3log.latke.logging.Level;
import org.b3log.latke.logging.Logger;
import org.b3log.latke.model.Pagination;
import org.b3log.latke.model.Role;
import org.b3log.latke.model.User;
import org.b3log.latke.service.LangPropsService;
import org.b3log.latke.service.ServiceException;
import org.b3log.latke.servlet.HTTPRequestContext;
import org.b3log.latke.servlet.HTTPRequestMethod;
import org.b3log.latke.servlet.annotation.After;
import org.b3log.latke.servlet.annotation.Before;
import org.b3log.latke.servlet.annotation.RequestProcessing;
import org.b3log.latke.servlet.annotation.RequestProcessor;
import org.b3log.latke.servlet.renderer.freemarker.AbstractFreeMarkerRenderer;
import org.b3log.latke.util.Paginator;
import org.b3log.latke.util.Requests;
import org.b3log.latke.util.Strings;
import org.b3log.symphony.cache.DomainCache;
import org.b3log.symphony.model.Article;
import org.b3log.symphony.model.Client;
import org.b3log.symphony.model.Comment;
import org.b3log.symphony.model.Common;
import org.b3log.symphony.model.Domain;
import org.b3log.symphony.model.Liveness;
import org.b3log.symphony.model.Pointtransfer;
import org.b3log.symphony.model.Referral;
import org.b3log.symphony.model.Revision;
import org.b3log.symphony.model.Reward;
import org.b3log.symphony.model.Tag;
import org.b3log.symphony.model.UserExt;
import org.b3log.symphony.processor.advice.AnonymousViewCheck;
import org.b3log.symphony.processor.advice.CSRFCheck;
import org.b3log.symphony.processor.advice.CSRFToken;
import org.b3log.symphony.processor.advice.LoginCheck;
import org.b3log.symphony.processor.advice.stopwatch.StopwatchEndAdvice;
import org.b3log.symphony.processor.advice.stopwatch.StopwatchStartAdvice;
import org.b3log.symphony.processor.advice.validate.ArticleAddValidation;
import org.b3log.symphony.processor.advice.validate.ArticleUpdateValidation;
import org.b3log.symphony.processor.advice.validate.UserRegisterValidation;
import org.b3log.symphony.service.ArticleMgmtService;
import org.b3log.symphony.service.ArticleQueryService;
import org.b3log.symphony.service.CharacterQueryService;
import org.b3log.symphony.service.ClientMgmtService;
import org.b3log.symphony.service.ClientQueryService;
import org.b3log.symphony.service.CommentQueryService;
import org.b3log.symphony.service.DomainQueryService;
import org.b3log.symphony.service.FollowQueryService;
import org.b3log.symphony.service.LivenessMgmtService;
import org.b3log.symphony.service.ReferralMgmtService;
import org.b3log.symphony.service.RewardQueryService;
import org.b3log.symphony.service.ShortLinkQueryService;
import org.b3log.symphony.service.UserMgmtService;
import org.b3log.symphony.service.UserQueryService;
import org.b3log.symphony.service.VoteQueryService;
import org.b3log.symphony.util.Emotions;
import org.b3log.symphony.util.Filler;
import org.b3log.symphony.util.Markdowns;
import org.b3log.symphony.util.Sessions;
import org.b3log.symphony.util.Symphonys;
import org.json.JSONObject;

/**
 * Article processor.
 *
 *

 *
Shows an article (/article/{articleId}), GET

 *
Shows article pre adding form page (/pre-post), GET

 *
Shows article adding form page (/post), GET

 *
Adds an article (/post) locally, POST

 *
Shows an article updating form page (/update) locally, GET

 *
Updates an article (/article/{id}) locally, PUT

 *
Adds an article (/rhythm/article) remotely, POST

 *
Updates an article (/rhythm/article) remotely, PUT

 *
Markdowns text (/markdown), POST

 *
Rewards an article (/article/reward), POST

 *
Gets an article preview content (/article/{articleId}/preview), GET

 *
Sticks an article (/article/stick), POST

 *
Gets article revisions (/article/{articleId}/revisions), GET

 *
Gets article image (/article/{articleId}/image), GET

 *

 *
 *

 * The 'locally' means user post an article on Symphony directly rather than receiving an article from
 * externally (for example Rhythm).
 *

 *
 * @author Liang Ding
 * @version 1.23.18.31, Aug 20, 2016
 * @since 0.2.0
 */
@RequestProcessor
public class ArticleProcessor {

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

    /**
     * Short link query service.
     */
    @Inject
    private ShortLinkQueryService shortLinkQueryService;

    /**
     * Article management service.
     */
    @Inject
    private ArticleMgmtService articleMgmtService;

    /**
     * Article query service.
     */
    @Inject
    private ArticleQueryService articleQueryService;

    /**
     * Comment query service.
     */
    @Inject
    private CommentQueryService commentQueryService;

    /**
     * User query service.
     */
    @Inject
    private UserQueryService userQueryService;

    /**
     * User management service.
     */
    @Inject
    private UserMgmtService userMgmtService;

    /**
     * Client management service.
     */
    @Inject
    private ClientMgmtService clientMgmtService;

    /**
     * Client query service.
     */
    @Inject
    private ClientQueryService clientQueryService;

    /**
     * Language service.
     */
    @Inject
    private LangPropsService langPropsService;

    /**
     * Follow query service.
     */
    @Inject
    private FollowQueryService followQueryService;

    /**
     * Reward query service.
     */
    @Inject
    private RewardQueryService rewardQueryService;

    /**
     * Vote query service.
     */
    @Inject
    private VoteQueryService voteQueryService;

    /**
     * Liveness management service.
     */
    @Inject
    private LivenessMgmtService livenessMgmtService;

    /**
     * Referral management service.
     */
    @Inject
    private ReferralMgmtService referralMgmtService;

    /**
     * Character query service.
     */
    @Inject
    private CharacterQueryService characterQueryService;

    /**
     * Domain query service.
     */
    @Inject
    private DomainQueryService domainQueryService;

    /**
     * Domain cache.
     */
    @Inject
    private DomainCache domainCache;

    /**
     * Filler.
     */
    @Inject
    private Filler filler;

    /**
     * Gets article image.
     *
     * @param context the specified context
     * @param request the specified request
     * @param response the specified response
     * @param articleId the specified article id
     * @throws Exception exception
     */
    @RequestProcessing(value = "/article/{articleId}/image", method = HTTPRequestMethod.GET)
    @Before(adviceClass = {StopwatchStartAdvice.class, LoginCheck.class})
    @After(adviceClass = {StopwatchEndAdvice.class})
    public void getArticleImage(final HTTPRequestContext context, final HttpServletRequest request, final HttpServletResponse response,
            final String articleId) throws Exception {
        final JSONObject article = articleQueryService.getArticle(articleId);
        final String authorId = article.optString(Article.ARTICLE_AUTHOR_ID);

        final Set<JSONObject> characters = characterQueryService.getWrittenCharacters();
        final String articleContent = article.optString(Article.ARTICLE_CONTENT);

        final List<BufferedImage> images = new ArrayList<BufferedImage>();
        for (int i = 0; i < articleContent.length(); i++) {
            final String ch = articleContent.substring(i, i + 1);
            final JSONObject chRecord = org.b3log.symphony.model.Character.getCharacter(ch, characters);
            if (null == chRecord) {
                images.add(org.b3log.symphony.model.Character.createImage(ch));

                continue;
            }

            final String imgData = chRecord.optString(org.b3log.symphony.model.Character.CHARACTER_IMG);
            final byte[] data = Base64.decode(imgData.getBytes());
            final BufferedImage img = ImageIO.read(new ByteArrayInputStream(data));
            final BufferedImage newImage = new BufferedImage(50, 50, img.getType());
            final Graphics g = newImage.getGraphics();
            g.setClip(0, 0, 50, 50);
            g.fillRect(0, 0, 50, 50);
            g.drawImage(img, 0, 0, 50, 50, null);
            g.dispose();

            images.add(newImage);
        }

        final int rowCharacterCount = 30;
        final int rows = (int) Math.ceil((double) images.size() / (double) rowCharacterCount);

        final BufferedImage combined = new BufferedImage(30 * 50, rows * 50, Transparency.TRANSLUCENT);
        int row = 0;
        for (int i = 0; i < images.size(); i++) {
            final BufferedImage image = images.get(i);

            final Graphics g = combined.getGraphics();
            g.drawImage(image, (i % rowCharacterCount) * 50, row * 50, null);

            if (0 == (i + 1) % rowCharacterCount) {
                row++;
            }
        }

        ImageIO.write(combined, "PNG", new File("./hp.png"));

        String url = "";

        final JSONObject ret = new JSONObject();
        ret.put(Keys.STATUS_CODE, true);
        ret.put(Common.URL, (Object) url);

        context.renderJSON(ret);
    }

    /**
     * Gets article revisions.
     *
     * @param context the specified context
     * @param request the specified request
     * @param response the specified response
     * @param articleId the specified article id
     * @throws Exception exception
     */
    @RequestProcessing(value = "/article/{articleId}/revisions", method = HTTPRequestMethod.GET)
    @Before(adviceClass = {StopwatchStartAdvice.class, LoginCheck.class})
    @After(adviceClass = {StopwatchEndAdvice.class})
    public void getArticleRevisions(final HTTPRequestContext context, final HttpServletRequest request, final HttpServletResponse response,
            final String articleId) throws Exception {
        final List<JSONObject> revisions = articleQueryService.getArticleRevisions(articleId);
        final JSONObject ret = new JSONObject();
        ret.put(Keys.STATUS_CODE, true);
        ret.put(Revision.REVISIONS, (Object) revisions);

        context.renderJSON(ret);
    }

    /**
     * Shows pre-add article.
     *
     * @param context the specified context
     * @param request the specified request
     * @param response the specified response
     * @throws Exception exception
     */
    @RequestProcessing(value = "/pre-post", method = HTTPRequestMethod.GET)
    @Before(adviceClass = {StopwatchStartAdvice.class, LoginCheck.class})
    @After(adviceClass = {CSRFToken.class, StopwatchEndAdvice.class})
    public void showPreAddArticle(final HTTPRequestContext context, final HttpServletRequest request, final HttpServletResponse response)
            throws Exception {
        final AbstractFreeMarkerRenderer renderer = new SkinRenderer();
        context.setRenderer(renderer);

        renderer.setTemplateName("/home/pre-post.ftl");
        final Map<String, Object> dataModel = renderer.getDataModel();

        dataModel.put(Common.BROADCAST_POINT, Pointtransfer.TRANSFER_SUM_C_ADD_ARTICLE_BROADCAST);

        filler.fillHeaderAndFooter(request, response, dataModel);
    }

    /**
     * Fills the domains with tags.
     *
     * @param dataModel the specified data model
     */
    private void fillDomainsWithTags(Map<String, Object> dataModel) {
        final List<JSONObject> domains = domainCache.getDomains(Integer.MAX_VALUE);

        dataModel.put(Domain.DOMAINS, domains);

        for (final JSONObject domain : domains) {
            final List<JSONObject> tags = domainQueryService.getTags(domain.optString(Keys.OBJECT_ID));

            domain.put(Domain.DOMAIN_T_TAGS, (Object) tags);
        }
    }

    /**
     * Shows add article.
     *
     * @param context the specified context
     * @param request the specified request
     * @param response the specified response
     * @throws Exception exception
     */
    @RequestProcessing(value = "/post", method = HTTPRequestMethod.GET)
    @Before(adviceClass = {StopwatchStartAdvice.class, LoginCheck.class})
    @After(adviceClass = {CSRFToken.class, StopwatchEndAdvice.class})
    public void showAddArticle(final HTTPRequestContext context, final HttpServletRequest request, final HttpServletResponse response)
            throws Exception {
        final AbstractFreeMarkerRenderer renderer = new SkinRenderer();
        context.setRenderer(renderer);

        renderer.setTemplateName("/home/post.ftl");
        final Map<String, Object> dataModel = renderer.getDataModel();

        // Qiniu file upload authenticate
        final Auth auth = Auth.create(Symphonys.get("qiniu.accessKey"), Symphonys.get("qiniu.secretKey"));
        final String uploadToken = auth.uploadToken(Symphonys.get("qiniu.bucket"));
        dataModel.put("qiniuUploadToken", uploadToken);
        dataModel.put("qiniuDomain", Symphonys.get("qiniu.domain"));

        if (!Symphonys.getBoolean("qiniu.enabled")) {
            dataModel.put("qiniuUploadToken", "");
        }

        final long imgMaxSize = Symphonys.getLong("upload.img.maxSize");
        dataModel.put("imgMaxSize", imgMaxSize);
        final long fileMaxSize = Symphonys.getLong("upload.file.maxSize");
        dataModel.put("fileMaxSize", fileMaxSize);

        fillDomainsWithTags(dataModel);

        String tags = request.getParameter(Tag.TAGS);
        if (StringUtils.isBlank(tags)) {
            tags = "";

            dataModel.put(Tag.TAGS, tags);
        } else {
            tags = Tag.formatTags(tags);
            final String[] tagTitles = tags.split(",");

            final StringBuilder tagBuilder = new StringBuilder();
            for (final String title : tagTitles) {
                final String tagTitle = title.trim();

                if (Strings.isEmptyOrNull(tagTitle)) {
                    continue;
                }

                if (Tag.containsWhiteListTags(tagTitle)) {
                    tagBuilder.append(tagTitle).append(",");

                    continue;
                }

                if (!Tag.TAG_TITLE_PATTERN.matcher(tagTitle).matches()) {
                    continue;
                }

                if (tagTitle.length() > Tag.MAX_TAG_TITLE_LENGTH) {
                    continue;
                }

                final JSONObject currentUser = (JSONObject) request.getAttribute(User.USER);
                if (!Role.ADMIN_ROLE.equals(currentUser.optString(User.USER_ROLE))
                        && ArrayUtils.contains(Symphonys.RESERVED_TAGS, tagTitle)) {
                    continue;
                }

                tagBuilder.append(tagTitle).append(",");
            }
            if (tagBuilder.length() > 0) {
                tagBuilder.deleteCharAt(tagBuilder.length() - 1);
            }

            dataModel.put(Tag.TAGS, tagBuilder.toString());
        }

        final String type = request.getParameter(Common.TYPE);
        if (StringUtils.isBlank(type)) {
            dataModel.put(Article.ARTICLE_TYPE, Article.ARTICLE_TYPE_C_NORMAL);
        } else {
            int articleType = Article.ARTICLE_TYPE_C_NORMAL;

            try {
                articleType = Integer.valueOf(type);
            } catch (final Exception e) {
                LOGGER.log(Level.WARN, "Gets article type error [" + type + "]", e);
            }

            if (Article.isInvalidArticleType(articleType)) {
                articleType = Article.ARTICLE_TYPE_C_NORMAL;
            }

            dataModel.put(Article.ARTICLE_TYPE, articleType);
        }

        final String at = request.getParameter(Common.AT);
        if (StringUtils.isNotBlank(at)) {
            dataModel.put(Common.AT, at);
        }

        filler.fillHeaderAndFooter(request, response, dataModel);

        String rewardEditorPlaceholderLabel = langPropsService.get("rewardEditorPlaceholderLabel");
        rewardEditorPlaceholderLabel = rewardEditorPlaceholderLabel.replace("{point}",
                String.valueOf(Pointtransfer.TRANSFER_SUM_C_ADD_ARTICLE_REWARD));
        dataModel.put("rewardEditorPlaceholderLabel", rewardEditorPlaceholderLabel);
        dataModel.put(Common.BROADCAST_POINT, Pointtransfer.TRANSFER_SUM_C_ADD_ARTICLE_BROADCAST);

        String articleContentErrorLabel = langPropsService.get("articleContentErrorLabel");
        articleContentErrorLabel = articleContentErrorLabel.replace("{maxArticleContentLength}",
                String.valueOf(ArticleAddValidation.MAX_ARTICLE_CONTENT_LENGTH));
        dataModel.put("articleContentErrorLabel", articleContentErrorLabel);
    }

    /**
     * Shows article with the specified article id.
     *
     * @param context the specified context
     * @param request the specified request
     * @param response the specified response
     * @param articleId the specified article id
     * @throws Exception exception
     */
    @RequestProcessing(value = "/article/{articleId}", method = HTTPRequestMethod.GET)
    @Before(adviceClass = {StopwatchStartAdvice.class, AnonymousViewCheck.class})
    @After(adviceClass = {CSRFToken.class, StopwatchEndAdvice.class})
    public void showArticle(final HTTPRequestContext context, final HttpServletRequest request, final HttpServletResponse response,
            final String articleId) throws Exception {
        final AbstractFreeMarkerRenderer renderer = new SkinRenderer();
        context.setRenderer(renderer);

        renderer.setTemplateName("/article.ftl");
        final Map<String, Object> dataModel = renderer.getDataModel();

        final int avatarViewMode = (int) request.getAttribute(UserExt.USER_AVATAR_VIEW_MODE);

        final JSONObject article = articleQueryService.getArticleById(avatarViewMode, articleId);
        if (null == article) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);

            return;
        }

        final HttpSession session = request.getSession(false);
        if (null != session) {
            session.setAttribute(Article.ARTICLE_T_ID, articleId);
        }

        filler.fillHeaderAndFooter(request, response, dataModel);

        final String authorId = article.optString(Article.ARTICLE_AUTHOR_ID);
        final JSONObject author = userQueryService.getUser(authorId);

        if (Article.ARTICLE_ANONYMOUS_C_PUBLIC == article.optInt(Article.ARTICLE_ANONYMOUS)) {
            article.put(Article.ARTICLE_T_AUTHOR_NAME, author.optString(User.USER_NAME));
            article.put(Article.ARTICLE_T_AUTHOR_URL, author.optString(User.USER_URL));
            article.put(Article.ARTICLE_T_AUTHOR_INTRO, author.optString(UserExt.USER_INTRO));
        } else {
            article.put(Article.ARTICLE_T_AUTHOR_NAME, UserExt.ANONYMOUS_USER_NAME);
            article.put(Article.ARTICLE_T_AUTHOR_URL, "");
            article.put(Article.ARTICLE_T_AUTHOR_INTRO, "");
        }
        dataModel.put(Article.ARTICLE, article);

        article.put(Common.IS_MY_ARTICLE, false);
        article.put(Article.ARTICLE_T_AUTHOR, author);
        article.put(Common.REWARDED, false);
        if (!article.has(Article.ARTICLE_CLIENT_ARTICLE_PERMALINK)) { // for legacy data
            article.put(Article.ARTICLE_CLIENT_ARTICLE_PERMALINK, "");
        }

        articleQueryService.processArticleContent(article, request);

        final boolean isLoggedIn = (Boolean) dataModel.get(Common.IS_LOGGED_IN);

        String cmtViewModeStr = request.getParameter("m");

        JSONObject currentUser;
        String currentUserId = null;
        if (isLoggedIn) {
            currentUser = (JSONObject) dataModel.get(Common.CURRENT_USER);
            currentUserId = currentUser.optString(Keys.OBJECT_ID);

            article.put(Common.IS_MY_ARTICLE, currentUserId.equals(article.optString(Article.ARTICLE_AUTHOR_ID)));

            final boolean isFollowing = followQueryService.isFollowing(currentUserId, articleId);
            dataModel.put(Common.IS_FOLLOWING, isFollowing);

            final int articleVote = voteQueryService.isVoted(currentUserId, articleId);
            article.put(Article.ARTICLE_T_VOTE, articleVote);

            if (currentUserId.equals(author.optString(Keys.OBJECT_ID))) {
                article.put(Common.REWARDED, true);
            } else {
                article.put(Common.REWARDED,
                        rewardQueryService.isRewarded(currentUserId, articleId, Reward.TYPE_C_ARTICLE));
            }

            if (Strings.isEmptyOrNull(cmtViewModeStr) || !Strings.isNumeric(cmtViewModeStr)) {
                cmtViewModeStr = currentUser.optString(UserExt.USER_COMMENT_VIEW_MODE);
            }
        } else if (Strings.isEmptyOrNull(cmtViewModeStr) || !Strings.isNumeric(cmtViewModeStr)) {
            cmtViewModeStr = "0";
        }

        int cmtViewMode = Integer.valueOf(cmtViewModeStr);

        dataModel.put(UserExt.USER_COMMENT_VIEW_MODE, cmtViewMode);

        if (!(Boolean) request.getAttribute(Keys.HttpRequest.IS_SEARCH_ENGINE_BOT)) {
            articleMgmtService.incArticleViewCount(articleId);
        }

        final JSONObject viewer = (JSONObject) request.getAttribute(User.USER);
        if (null != viewer) {
            livenessMgmtService.incLiveness(viewer.optString(Keys.OBJECT_ID), Liveness.LIVENESS_PV);
        }

        filler.fillRelevantArticles(avatarViewMode, dataModel, article);
        filler.fillRandomArticles(avatarViewMode, dataModel);
        filler.fillSideHotArticles(avatarViewMode, dataModel);

        // Qiniu file upload authenticate
        final Auth auth = Auth.create(Symphonys.get("qiniu.accessKey"), Symphonys.get("qiniu.secretKey"));
        final String uploadToken = auth.uploadToken(Symphonys.get("qiniu.bucket"));
        dataModel.put("qiniuUploadToken", uploadToken);
        dataModel.put("qiniuDomain", Symphonys.get("qiniu.domain"));

        if (!Symphonys.getBoolean("qiniu.enabled")) {
            dataModel.put("qiniuUploadToken", "");
        }

        final long imgMaxSize = Symphonys.getLong("upload.img.maxSize");
        dataModel.put("imgMaxSize", imgMaxSize);
        final long fileMaxSize = Symphonys.getLong("upload.file.maxSize");
        dataModel.put("fileMaxSize", fileMaxSize);

        // Fill article thank
        article.put(Common.THANKED, rewardQueryService.isRewarded(currentUserId, articleId, Reward.TYPE_C_THANK_ARTICLE));
        article.put(Common.THANKED_COUNT, rewardQueryService.rewardedCount(articleId, Reward.TYPE_C_THANK_ARTICLE));

        String stickConfirmLabel = langPropsService.get("stickConfirmLabel");
        stickConfirmLabel = stickConfirmLabel.replace("{point}", Symphonys.get("pointStickArticle"));
        dataModel.put("stickConfirmLabel", stickConfirmLabel);
        dataModel.put("pointThankArticle", Symphonys.get("pointThankArticle"));

        dataModel.put(Common.DISCUSSION_VIEWABLE, article.optBoolean(Common.DISCUSSION_VIEWABLE));
        if (!article.optBoolean(Common.DISCUSSION_VIEWABLE)) {
            article.put(Article.ARTICLE_T_COMMENTS, (Object) Collections.emptyList());
            article.put(Article.ARTICLE_T_NICE_COMMENTS, (Object) Collections.emptyList());

            return;
        }

        String pageNumStr = request.getParameter("p");
        if (Strings.isEmptyOrNull(pageNumStr) || !Strings.isNumeric(pageNumStr)) {
            pageNumStr = "1";
        }

        final int pageNum = Integer.valueOf(pageNumStr);
        final int pageSize = Symphonys.getInt("articleCommentsPageSize");
        final int windowSize = Symphonys.getInt("articleCommentsWindowSize");

        final List<JSONObject> niceComments = commentQueryService.getNiceComments(avatarViewMode, articleId, 3);
        article.put(Article.ARTICLE_T_NICE_COMMENTS, (Object) niceComments);

        double niceCmtScore = Double.MAX_VALUE;
        if (!niceComments.isEmpty()) {
            niceCmtScore = niceComments.get(niceComments.size() - 1).optDouble(Comment.COMMENT_SCORE);

            for (final JSONObject comment : niceComments) {
                String thankTemplate = langPropsService.get("thankConfirmLabel");
                thankTemplate = thankTemplate.replace("{point}", String.valueOf(Symphonys.getInt("pointThankComment")))
                        .replace("{user}", comment.optJSONObject(Comment.COMMENT_T_COMMENTER).optString(User.USER_NAME));
                comment.put(Comment.COMMENT_T_THANK_LABEL, thankTemplate);

                final String commentId = comment.optString(Keys.OBJECT_ID);
                if (isLoggedIn) {
                    comment.put(Common.REWARDED,
                            rewardQueryService.isRewarded(currentUserId, commentId, Reward.TYPE_C_COMMENT));
                    final int commentVote = voteQueryService.isVoted(currentUserId, commentId);
                    comment.put(Comment.COMMENT_T_VOTE, commentVote);
                }

                comment.put(Common.REWARED_COUNT, rewardQueryService.rewardedCount(commentId, Reward.TYPE_C_COMMENT));
            }
        }

        final List<JSONObject> articleComments = commentQueryService.getArticleComments(
                avatarViewMode, articleId, pageNum, pageSize, cmtViewMode);

        article.put(Article.ARTICLE_T_COMMENTS, (Object) articleComments);

        // Fill comment thank
        for (final JSONObject comment : articleComments) {
            comment.put(Comment.COMMENT_T_NICE,
                    comment.optDouble(Comment.COMMENT_SCORE) >= niceCmtScore);

            String thankTemplate = langPropsService.get("thankConfirmLabel");
            thankTemplate = thankTemplate.replace("{point}", String.valueOf(Symphonys.getInt("pointThankComment")))
                    .replace("{user}", comment.optJSONObject(Comment.COMMENT_T_COMMENTER).optString(User.USER_NAME));
            comment.put(Comment.COMMENT_T_THANK_LABEL, thankTemplate);

            final String commentId = comment.optString(Keys.OBJECT_ID);
            if (isLoggedIn) {
                comment.put(Common.REWARDED,
                        rewardQueryService.isRewarded(currentUserId, commentId, Reward.TYPE_C_COMMENT));
                final int commentVote = voteQueryService.isVoted(currentUserId, commentId);
                comment.put(Comment.COMMENT_T_VOTE, commentVote);
            }

            comment.put(Common.REWARED_COUNT, rewardQueryService.rewardedCount(commentId, Reward.TYPE_C_COMMENT));
        }

        final int commentCnt = article.getInt(Article.ARTICLE_COMMENT_CNT);
        final int pageCount = (int) Math.ceil((double) commentCnt / (double) pageSize);

        final List<Integer> pageNums = Paginator.paginate(pageNum, pageSize, pageCount, windowSize);
        if (!pageNums.isEmpty()) {
            dataModel.put(Pagination.PAGINATION_FIRST_PAGE_NUM, pageNums.get(0));
            dataModel.put(Pagination.PAGINATION_LAST_PAGE_NUM, pageNums.get(pageNums.size() - 1));
        }

        dataModel.put(Pagination.PAGINATION_CURRENT_PAGE_NUM, pageNum);
        dataModel.put(Pagination.PAGINATION_PAGE_COUNT, pageCount);
        dataModel.put(Pagination.PAGINATION_PAGE_NUMS, pageNums);
        dataModel.put(Common.ARTICLE_COMMENTS_PAGE_SIZE, pageSize);

        // Referral statistic
        final String referralUserName = request.getParameter("r");
        if (!UserRegisterValidation.invalidUserName(referralUserName)) {
            final JSONObject referralUser = userQueryService.getUserByName(referralUserName);
            if (null == referralUser) {
                return;
            }

            final String viewerIP = Requests.getRemoteAddr(request);

            final JSONObject referral = new JSONObject();
            referral.put(Referral.REFERRAL_CLICK, 1);
            referral.put(Referral.REFERRAL_DATA_ID, articleId);
            referral.put(Referral.REFERRAL_IP, viewerIP);
            referral.put(Referral.REFERRAL_TYPE, Referral.REFERRAL_TYPE_C_ARTICLE);
            referral.put(Referral.REFERRAL_USER, referralUserName);

            referralMgmtService.updateReferral(referral);
        }
    }

    /**
     * Adds an article locally.
     *
     *

     * The request json object (an article):
     *

     * {
     *   "articleTitle": "",
     *   "articleTags": "", // Tags spliting by ','
     *   "articleContent": "",
     *   "articleCommentable": boolean,
     *   "articleType": int,
     *   "articleRewardContent": "",
     *   "articleRewardPoint": int,
     *   "articleAnonymous": boolean
     * }
     *

     *

     *
     * @param context the specified context
     * @param request the specified request
     * @param response the specified response
     * @throws IOException io exception
     * @throws ServletException servlet exception
     */
    @RequestProcessing(value = "/article", method = HTTPRequestMethod.POST)
    @Before(adviceClass = {StopwatchStartAdvice.class, LoginCheck.class, CSRFCheck.class, ArticleAddValidation.class})
    @After(adviceClass = StopwatchEndAdvice.class)
    public void addArticle(final HTTPRequestContext context, final HttpServletRequest request, final HttpServletResponse response)
            throws IOException, ServletException {
        context.renderJSON();

        final JSONObject requestJSONObject = (JSONObject) request.getAttribute(Keys.REQUEST);

        final String articleTitle = requestJSONObject.optString(Article.ARTICLE_TITLE);
        String articleTags = requestJSONObject.optString(Article.ARTICLE_TAGS);
        final String articleContent = requestJSONObject.optString(Article.ARTICLE_CONTENT);
        //final boolean articleCommentable = requestJSONObject.optBoolean(Article.ARTICLE_COMMENTABLE);
        final boolean articleCommentable = true;
        final int articleType = requestJSONObject.optInt(Article.ARTICLE_TYPE, Article.ARTICLE_TYPE_C_NORMAL);
        final String articleRewardContent = requestJSONObject.optString(Article.ARTICLE_REWARD_CONTENT);
        final int articleRewardPoint = requestJSONObject.optInt(Article.ARTICLE_REWARD_POINT);
        final String ip = Requests.getRemoteAddr(request);
        final String ua = request.getHeader("User-Agent");
        final boolean isAnonymous = requestJSONObject.optBoolean(Article.ARTICLE_ANONYMOUS, false);
        final int articleAnonymous = isAnonymous
                ? Article.ARTICLE_ANONYMOUS_C_ANONYMOUS : Article.ARTICLE_ANONYMOUS_C_PUBLIC;

        final JSONObject article = new JSONObject();
        article.put(Article.ARTICLE_TITLE, articleTitle);
        article.put(Article.ARTICLE_CONTENT, articleContent);
        article.put(Article.ARTICLE_EDITOR_TYPE, 0);
        article.put(Article.ARTICLE_COMMENTABLE, articleCommentable);
        article.put(Article.ARTICLE_TYPE, articleType);
        article.put(Article.ARTICLE_REWARD_CONTENT, articleRewardContent);
        article.put(Article.ARTICLE_REWARD_POINT, articleRewardPoint);
        article.put(Article.ARTICLE_IP, "");
        if (StringUtils.isNotBlank(ip)) {
            article.put(Article.ARTICLE_IP, ip);
        }
        article.put(Article.ARTICLE_UA, "");
        if (StringUtils.isNotBlank(ua)) {
            article.put(Article.ARTICLE_UA, ua);
        }
        article.put(Article.ARTICLE_ANONYMOUS, articleAnonymous);

        try {
            final JSONObject currentUser = (JSONObject) request.getAttribute(User.USER);

            article.put(Article.ARTICLE_AUTHOR_ID, currentUser.optString(Keys.OBJECT_ID));

            final String authorEmail = currentUser.optString(User.USER_EMAIL);
            article.put(Article.ARTICLE_AUTHOR_EMAIL, authorEmail);

            if (!Role.ADMIN_ROLE.equals(currentUser.optString(User.USER_ROLE))) {
                articleTags = articleMgmtService.filterReservedTags(articleTags);
            }

            if (Article.ARTICLE_TYPE_C_DISCUSSION == articleType && StringUtils.isBlank(articleTags)) {
                articleTags = "小黑屋";
            }

            if (Article.ARTICLE_TYPE_C_THOUGHT == articleType && StringUtils.isBlank(articleTags)) {
                articleTags = "思绪";
            }

            article.put(Article.ARTICLE_TAGS, articleTags);
            article.put(Article.ARTICLE_T_IS_BROADCAST, false);

            articleMgmtService.addArticle(article);

            context.renderTrueResult();
        } catch (final ServiceException e) {
            final String msg = e.getMessage();
            LOGGER.log(Level.ERROR, "Adds article[title=" + articleTitle + "] failed: {0}", e.getMessage());

            context.renderMsg(msg);
        }
    }

    /**
     * Shows update article.
     *
     * @param context the specified context
     * @param request the specified request
     * @param response the specified response
     * @throws Exception exception
     */
    @RequestProcessing(value = "/update", method = HTTPRequestMethod.GET)
    @Before(adviceClass = {StopwatchStartAdvice.class, LoginCheck.class})
    @After(adviceClass = {CSRFToken.class, StopwatchEndAdvice.class})
    public void showUpdateArticle(final HTTPRequestContext context, final HttpServletRequest request, final HttpServletResponse response)
            throws Exception {
        final String articleId = request.getParameter("id");
        if (Strings.isEmptyOrNull(articleId)) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);

            return;
        }

        final int avatarViewMode = (int) request.getAttribute(UserExt.USER_AVATAR_VIEW_MODE);

        final JSONObject article = articleQueryService.getArticleById(avatarViewMode, articleId);
        if (null == article) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);

            return;
        }

        final JSONObject currentUser = Sessions.currentUser(request);
        if (null == currentUser
                || !currentUser.optString(Keys.OBJECT_ID).equals(article.optString(Article.ARTICLE_AUTHOR_ID))) {
            response.sendError(HttpServletResponse.SC_FORBIDDEN);

            return;
        }

        final AbstractFreeMarkerRenderer renderer = new SkinRenderer();
        context.setRenderer(renderer);

        renderer.setTemplateName("/home/post.ftl");
        final Map<String, Object> dataModel = renderer.getDataModel();

        dataModel.put(Article.ARTICLE, article);

        filler.fillHeaderAndFooter(request, response, dataModel);

        // Qiniu file upload authenticate
        final Auth auth = Auth.create(Symphonys.get("qiniu.accessKey"), Symphonys.get("qiniu.secretKey"));
        final String uploadToken = auth.uploadToken(Symphonys.get("qiniu.bucket"));
        dataModel.put("qiniuUploadToken", uploadToken);
        dataModel.put("qiniuDomain", Symphonys.get("qiniu.domain"));

        if (!Symphonys.getBoolean("qiniu.enabled")) {
            dataModel.put("qiniuUploadToken", "");
        }

        final long imgMaxSize = Symphonys.getLong("upload.img.maxSize");
        dataModel.put("imgMaxSize", imgMaxSize);
        final long fileMaxSize = Symphonys.getLong("upload.file.maxSize");
        dataModel.put("fileMaxSize", fileMaxSize);

        fillDomainsWithTags(dataModel);

        String rewardEditorPlaceholderLabel = langPropsService.get("rewardEditorPlaceholderLabel");
        rewardEditorPlaceholderLabel = rewardEditorPlaceholderLabel.replace("{point}",
                String.valueOf(Pointtransfer.TRANSFER_SUM_C_ADD_ARTICLE_REWARD));
        dataModel.put("rewardEditorPlaceholderLabel", rewardEditorPlaceholderLabel);
        dataModel.put(Common.BROADCAST_POINT, Pointtransfer.TRANSFER_SUM_C_ADD_ARTICLE_BROADCAST);
    }

    /**
     * Updates an article locally.
     *
     *

     * The request json object (an article):
     *

     * {
     *   "articleTitle": "",
     *   "articleTags": "", // Tags spliting by ','
     *   "articleContent": "",
     *   "articleCommentable": boolean,
     *   "articleType": int,
     *   "articleRewardContent": "",
     *   "articleRewardPoint": int
     * }
     *

     *

     *
     * @param context the specified context
     * @param request the specified request
     * @param response the specified response
     * @param id the specified article id
     * @throws Exception exception
     */
    @RequestProcessing(value = "/article/{id}", method = HTTPRequestMethod.PUT)
    @Before(adviceClass = {StopwatchStartAdvice.class, LoginCheck.class, CSRFCheck.class, ArticleUpdateValidation.class})
    @After(adviceClass = StopwatchEndAdvice.class)
    public void updateArticle(final HTTPRequestContext context, final HttpServletRequest request, final HttpServletResponse response,
            final String id) throws Exception {
        if (Strings.isEmptyOrNull(id)) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);

            return;
        }

        final int avatarViewMode = (int) request.getAttribute(UserExt.USER_AVATAR_VIEW_MODE);

        final JSONObject oldArticle = articleQueryService.getArticleById(avatarViewMode, id);
        if (null == oldArticle) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);

            return;
        }

        context.renderJSON();

        final JSONObject requestJSONObject = (JSONObject) request.getAttribute(Keys.REQUEST);

        final String articleTitle = requestJSONObject.optString(Article.ARTICLE_TITLE);
        String articleTags = requestJSONObject.optString(Article.ARTICLE_TAGS);
        final String articleContent = requestJSONObject.optString(Article.ARTICLE_CONTENT);
        //final boolean articleCommentable = requestJSONObject.optBoolean(Article.ARTICLE_COMMENTABLE);
        final boolean articleCommentable = true;
        final int articleType = requestJSONObject.optInt(Article.ARTICLE_TYPE, Article.ARTICLE_TYPE_C_NORMAL);
        final String articleRewardContent = requestJSONObject.optString(Article.ARTICLE_REWARD_CONTENT);
        final int articleRewardPoint = requestJSONObject.optInt(Article.ARTICLE_REWARD_POINT);
        final String ip = Requests.getRemoteAddr(request);
        final String ua = request.getHeader("User-Agent");

        final JSONObject article = new JSONObject();
        article.put(Keys.OBJECT_ID, id);
        article.put(Article.ARTICLE_TITLE, articleTitle);
        article.put(Article.ARTICLE_CONTENT, articleContent);
        article.put(Article.ARTICLE_EDITOR_TYPE, 0);
        article.put(Article.ARTICLE_COMMENTABLE, articleCommentable);
        article.put(Article.ARTICLE_TYPE, articleType);
        article.put(Article.ARTICLE_REWARD_CONTENT, articleRewardContent);
        article.put(Article.ARTICLE_REWARD_POINT, articleRewardPoint);
        article.put(Article.ARTICLE_IP, "");
        if (StringUtils.isNotBlank(ip)) {
            article.put(Article.ARTICLE_IP, ip);
        }
        article.put(Article.ARTICLE_UA, "");
        if (StringUtils.isNotBlank(ua)) {
            article.put(Article.ARTICLE_UA, ua);
        }

        final JSONObject currentUser = (JSONObject) request.getAttribute(User.USER);
        if (null == currentUser
                || !currentUser.optString(Keys.OBJECT_ID).equals(oldArticle.optString(Article.ARTICLE_AUTHOR_ID))) {
            response.sendError(HttpServletResponse.SC_FORBIDDEN);

            return;
        }

        article.put(Article.ARTICLE_AUTHOR_ID, currentUser.optString(Keys.OBJECT_ID));

        final String authorEmail = currentUser.optString(User.USER_EMAIL);
        article.put(Article.ARTICLE_AUTHOR_EMAIL, authorEmail);

        if (!Role.ADMIN_ROLE.equals(currentUser.optString(User.USER_ROLE))) {
            articleTags = articleMgmtService.filterReservedTags(articleTags);
        }

        if (Article.ARTICLE_TYPE_C_DISCUSSION == articleType && StringUtils.isBlank(articleTags)) {
            articleTags = "小黑屋";
        }

        if (Article.ARTICLE_TYPE_C_THOUGHT == articleType && StringUtils.isBlank(articleTags)) {
            articleTags = "思绪";
        }

        article.put(Article.ARTICLE_TAGS, articleTags);

        try {

            articleMgmtService.updateArticle(article);
            context.renderTrueResult();
        } catch (final ServiceException e) {
            final String msg = e.getMessage();
            LOGGER.log(Level.ERROR, "Adds article[title=" + articleTitle + "] failed: {0}", e.getMessage());

            context.renderMsg(msg);
        }
    }

    /**
     * Adds an article remotely.
     *
     *

     * This interface will be called by Rhythm, so here is no article data validation, just only validate the B3
     * key.

     *
     *

     * The request json object, for example,
     *

     * {
     *     "article": {
     *         "articleAuthorEmail": "DL88250@gmail.com",
     *         "articleContent": "<p>test<\/p>",
     *         "articleCreateDate": 1350635469922,
     *         "articlePermalink": "/articles/2012/10/19/1350635469866.html",
     *         "articleTags": "test",
     *         "articleTitle": "test",
     *         "clientArticleId": "1350635469866",
     *         "oId": "1350635469866"
     *     },
     *     "clientAdminEmail": "DL88250@gmail.com",
     *     "clientHost": "http://localhost:11099",
     *     "clientName": "B3log Solo",
     *     "clientTitle": "简约设计の艺术",
     *     "clientRuntimeEnv": "LOCAL",
     *     "clientVersion": "0.5.0",
     *     "symphonyKey": "....",
     *     "userB3Key": "Your key"
     * }
     *

     *

     *
     * @param context the specified context
     * @param request the specified request
     * @param response the specified response
     * @throws Exception exception
     */
    @RequestProcessing(value = "/rhythm/article", method = HTTPRequestMethod.POST)
    @Before(adviceClass = StopwatchStartAdvice.class)
    @After(adviceClass = StopwatchEndAdvice.class)
    public void addArticleFromRhythm(final HTTPRequestContext context,
            final HttpServletRequest request, final HttpServletResponse response) throws Exception {
        context.renderJSON();

        final JSONObject requestJSONObject = Requests.parseRequestJSONObject(request, response);

        final String clientB3Key = requestJSONObject.getString(UserExt.USER_B3_KEY);
        final String symphonyKey = requestJSONObject.getString(Common.SYMPHONY_KEY);
        final String clientAdminEmail = requestJSONObject.getString(Client.CLIENT_ADMIN_EMAIL);
        final String clientName = requestJSONObject.getString(Client.CLIENT_NAME);
        final String clientTitle = requestJSONObject.getString(Client.CLIENT_T_TITLE);
        final String clientVersion = requestJSONObject.getString(Client.CLIENT_VERSION);
        final String clientHost = requestJSONObject.getString(Client.CLIENT_HOST);
        final String clientRuntimeEnv = requestJSONObject.getString(Client.CLIENT_RUNTIME_ENV);

//        final String maybeIP = StringUtils.substringBetween(clientHost, "://", ":");
//        if (Networks.isIPv4(maybeIP)) {
//            LOGGER.log(Level.WARN, "Sync add article error, caused by the client host [{0}] is invalid", clientHost);
//
//            return;
//        }
        final JSONObject user = userQueryService.getUserByEmail(clientAdminEmail);
        if (null == user) {
            LOGGER.log(Level.WARN, "The user[email={0}, host={1}] not found in community", clientAdminEmail, clientHost);

            return;
        }

        final String userName = user.optString(User.USER_NAME);

        String userKey = user.optString(UserExt.USER_B3_KEY);
        if (StringUtils.isBlank(userKey) || (Strings.isNumeric(userKey) && userKey.length() == clientB3Key.length())) {
            userKey = clientB3Key;

            user.put(UserExt.USER_B3_KEY, userKey);
            userMgmtService.updateUser(user.optString(Keys.OBJECT_ID), user);
        }

        if (!Symphonys.get("keyOfSymphony").equals(symphonyKey) || !userKey.equals(clientB3Key)) {
            LOGGER.log(Level.WARN, "B3 key not match, ignored add article [name={0}, email={1}, host={2}, userSymKey={3}, userClientKey={4}]",
                    userName, clientAdminEmail, clientHost, user.optString(UserExt.USER_B3_KEY), clientB3Key);

            return;
        }

        if (UserExt.USER_STATUS_C_VALID != user.optInt(UserExt.USER_STATUS)) {
            LOGGER.log(Level.WARN, "The user[name={0}, email={1}, host={2}] has been forbidden", userName, clientAdminEmail, clientHost);

            return;
        }

        final JSONObject originalArticle = requestJSONObject.getJSONObject(Article.ARTICLE);
        final String authorId = user.optString(Keys.OBJECT_ID);
        final String clientArticleId = originalArticle.optString(Keys.OBJECT_ID);

        final String articleTitle = originalArticle.optString(Article.ARTICLE_TITLE);
        String articleTags = Tag.formatTags(originalArticle.optString(Article.ARTICLE_TAGS));
        String articleContent = originalArticle.optString(Article.ARTICLE_CONTENT);

        final JSONObject article = new JSONObject();
        article.put(Article.ARTICLE_TITLE, articleTitle);
        article.put(Article.ARTICLE_EDITOR_TYPE, 0);
        article.put(Article.ARTICLE_SYNC_TO_CLIENT, false);
        article.put(Article.ARTICLE_CLIENT_ARTICLE_ID, clientArticleId);
        article.put(Article.ARTICLE_AUTHOR_ID, authorId);
        article.put(Article.ARTICLE_AUTHOR_EMAIL, clientAdminEmail.toLowerCase().trim());

        final String permalink = originalArticle.optString(Article.ARTICLE_PERMALINK);

        final JSONObject articleExisted = articleQueryService.getArticleByClientArticleId(authorId, clientArticleId);
        final boolean toAdd = null == articleExisted;
        if (!toAdd) { // Client requests to add an article, but the article already exist in server
            article.put(Keys.OBJECT_ID, articleExisted.optString(Keys.OBJECT_ID));
            article.put(Article.ARTICLE_T_IS_BROADCAST, false);

//            articleContent += "\n\n
该文章同步自 "
//                    + "" + clientTitle + "
";
        } else { // Add
            final boolean isBroadcast = "aBroadcast".equals(permalink);
            if (isBroadcast) {
                articleContent += "\n\n
该广播来自 "
                        + "" + clientTitle + "
";
            } else {
//                articleContent += "\n\n
该文章同步自 "
//                        + "" + clientTitle + "
";
            }

            article.put(Article.ARTICLE_T_IS_BROADCAST, isBroadcast);
        }

        article.put(Article.ARTICLE_CONTENT, articleContent);
        article.put(Article.ARTICLE_CLIENT_ARTICLE_PERMALINK, clientHost + permalink);

        if (!Role.ADMIN_ROLE.equals(user.optString(User.USER_ROLE))) {
            articleTags = articleMgmtService.filterReservedTags(articleTags);
        }

        try {
            articleTags = "B3log," + articleTags;
            articleTags = Tag.formatTags(articleTags);
            article.put(Article.ARTICLE_TAGS, articleTags);

            if (toAdd) {
                articleMgmtService.addArticle(article);
            } else {
                articleMgmtService.updateArticle(article);
            }

            context.renderTrueResult();
        } catch (final ServiceException e) {
            final String msg = langPropsService.get("updateFailLabel") + " - " + e.getMessage();
            LOGGER.log(Level.ERROR, msg, e);

            context.renderMsg(msg);
        }

        // Updates client record
        JSONObject client = clientQueryService.getClientByAdminEmail(clientAdminEmail);
        if (null == client) {
            client = new JSONObject();
            client.put(Client.CLIENT_ADMIN_EMAIL, clientAdminEmail);
            client.put(Client.CLIENT_HOST, clientHost);
            client.put(Client.CLIENT_NAME, clientName);
            client.put(Client.CLIENT_RUNTIME_ENV, clientRuntimeEnv);
            client.put(Client.CLIENT_VERSION, clientVersion);
            client.put(Client.CLIENT_LATEST_ADD_COMMENT_TIME, 0L);
            client.put(Client.CLIENT_LATEST_ADD_ARTICLE_TIME, System.currentTimeMillis());

            clientMgmtService.addClient(client);
        } else {
            client.put(Client.CLIENT_ADMIN_EMAIL, clientAdminEmail);
            client.put(Client.CLIENT_HOST, clientHost);
            client.put(Client.CLIENT_NAME, clientName);
            client.put(Client.CLIENT_RUNTIME_ENV, clientRuntimeEnv);
            client.put(Client.CLIENT_VERSION, clientVersion);
            client.put(Client.CLIENT_LATEST_ADD_ARTICLE_TIME, System.currentTimeMillis());

            clientMgmtService.updateClient(client);
        }
    }

    /**
     * Updates an article remotely.
     *
     *

     * This interface will be called by Rhythm, so here is no article data validation, just only validate the B3
     * key.

     *
     *

     * The request json object, for example,
     *

     * {
     *     "article": {
     *         "articleAuthorEmail": "DL88250@gmail.com",
     *         "articleContent": "<p>test<\/p>",
     *         "articleCreateDate": 1350635469922,
     *         "articlePermalink": "/articles/2012/10/19/1350635469866.html",
     *         "articleTags": "test",
     *         "articleTitle": "test",
     *         "clientArticleId": "1350635469866",
     *         "oId": "1350635469866"
     *     },
     *     "clientAdminEmail": "DL88250@gmail.com",
     *     "clientHost": "http://localhost:11099",
     *     "clientName": "B3log Solo",
     *     "clientTitle": "简约设计の艺术",
     *     "clientRuntimeEnv": "LOCAL",
     *     "clientVersion": "0.5.0",
     *     "symphonyKey": "....",
     *     "userB3Key": "Your key"
     * }
     *

     *

     *
     * @param context the specified context
     * @param request the specified request
     * @param response the specified response
     * @throws Exception exception
     */
    @RequestProcessing(value = "/rhythm/article", method = HTTPRequestMethod.PUT)
    @Before(adviceClass = StopwatchStartAdvice.class)
    @After(adviceClass = StopwatchEndAdvice.class)
    public void updateArticleFromRhythm(final HTTPRequestContext context,
            final HttpServletRequest request, final HttpServletResponse response) throws Exception {
        context.renderJSON();

        final JSONObject requestJSONObject = Requests.parseRequestJSONObject(request, response);

        final String clientB3Key = requestJSONObject.getString(UserExt.USER_B3_KEY);
        final String symphonyKey = requestJSONObject.getString(Common.SYMPHONY_KEY);
        final String clientAdminEmail = requestJSONObject.getString(Client.CLIENT_ADMIN_EMAIL);
        final String clientName = requestJSONObject.getString(Client.CLIENT_NAME);
        final String clientTitle = requestJSONObject.getString(Client.CLIENT_T_TITLE);
        final String clientVersion = requestJSONObject.getString(Client.CLIENT_VERSION);
        final String clientHost = requestJSONObject.getString(Client.CLIENT_HOST);
        final String clientRuntimeEnv = requestJSONObject.getString(Client.CLIENT_RUNTIME_ENV);

//        final String maybeIP = StringUtils.substringBetween(clientHost, "://", ":");
//        if (Networks.isIPv4(maybeIP)) {
//            LOGGER.log(Level.WARN, "Sync update article error, caused by the client host [{0}] is invalid", clientHost);
//
//            return;
//        }
        final JSONObject user = userQueryService.getUserByEmail(clientAdminEmail);
        if (null == user) {
            LOGGER.log(Level.WARN, "The user[email={0}, host={1}] not found in community", clientAdminEmail, clientHost);

            return;
        }

        final String userName = user.optString(User.USER_NAME);

        String userKey = user.optString(UserExt.USER_B3_KEY);
        if (StringUtils.isBlank(userKey) || (Strings.isNumeric(userKey) && userKey.length() == clientB3Key.length())) {
            userKey = clientB3Key;

            user.put(UserExt.USER_B3_KEY, userKey);
            userMgmtService.updateUser(user.optString(Keys.OBJECT_ID), user);
        }

        if (!Symphonys.get("keyOfSymphony").equals(symphonyKey) || !userKey.equals(clientB3Key)) {
            LOGGER.log(Level.WARN, "B3 key not match, ignored update article [name={0}, email={1}, host={2}, userSymKey={3}, userClientKey={4}]",
                    userName, clientAdminEmail, clientHost, user.optString(UserExt.USER_B3_KEY), clientB3Key);

            return;
        }

        if (UserExt.USER_STATUS_C_VALID != user.optInt(UserExt.USER_STATUS)) {
            LOGGER.log(Level.WARN, "The user[name={0}, email={1}, host={2}] has been forbidden", userName, clientAdminEmail, clientHost);

            return;
        }

        final JSONObject originalArticle = requestJSONObject.getJSONObject(Article.ARTICLE);

        final String articleTitle = originalArticle.optString(Article.ARTICLE_TITLE);
        String articleTags = Tag.formatTags(originalArticle.optString(Article.ARTICLE_TAGS));
        String articleContent = originalArticle.optString(Article.ARTICLE_CONTENT);

        final String permalink = originalArticle.optString(Article.ARTICLE_PERMALINK);
//        articleContent += "\n\n
该文章同步自 "
//                + "" + clientTitle + "
";

        final String authorId = user.optString(Keys.OBJECT_ID);
        final String clientArticleId = originalArticle.optString(Keys.OBJECT_ID);
        final JSONObject oldArticle = articleQueryService.getArticleByClientArticleId(authorId, clientArticleId);
        if (null == oldArticle) {
            LOGGER.log(Level.WARN, "Not found article [clientHost={0}, clientArticleId={1}] to update", clientHost, clientArticleId);

            return;
        }

        final JSONObject article = new JSONObject();
        article.put(Keys.OBJECT_ID, oldArticle.optString(Keys.OBJECT_ID));
        article.put(Article.ARTICLE_TITLE, articleTitle);
        article.put(Article.ARTICLE_CONTENT, articleContent);
        article.put(Article.ARTICLE_EDITOR_TYPE, 0);
        article.put(Article.ARTICLE_SYNC_TO_CLIENT, false);
        article.put(Article.ARTICLE_CLIENT_ARTICLE_ID, clientArticleId);
        article.put(Article.ARTICLE_AUTHOR_ID, authorId);
        article.put(Article.ARTICLE_AUTHOR_EMAIL, clientAdminEmail.toLowerCase().trim());
        article.put(Article.ARTICLE_T_IS_BROADCAST, false);
        article.put(Article.ARTICLE_CLIENT_ARTICLE_PERMALINK, clientHost + permalink);

        if (!Role.ADMIN_ROLE.equals(user.optString(User.USER_ROLE))) {
            articleTags = articleMgmtService.filterReservedTags(articleTags);
        }

        try {
            articleTags = "B3log," + articleTags;
            articleTags = Tag.formatTags(articleTags);
            article.put(Article.ARTICLE_TAGS, articleTags);

            articleMgmtService.updateArticle(article);

            context.renderTrueResult();
        } catch (final ServiceException e) {
            final String msg = langPropsService.get("updateFailLabel") + " - " + e.getMessage();
            LOGGER.log(Level.ERROR, msg, e);

            context.renderMsg(msg);
        }

        // Updates client record
        JSONObject client = clientQueryService.getClientByAdminEmail(clientAdminEmail);
        if (null == client) {
            client = new JSONObject();
            client.put(Client.CLIENT_ADMIN_EMAIL, clientAdminEmail);
            client.put(Client.CLIENT_HOST, clientHost);
            client.put(Client.CLIENT_NAME, clientName);
            client.put(Client.CLIENT_RUNTIME_ENV, clientRuntimeEnv);
            client.put(Client.CLIENT_VERSION, clientVersion);
            client.put(Client.CLIENT_LATEST_ADD_COMMENT_TIME, 0L);
            client.put(Client.CLIENT_LATEST_ADD_ARTICLE_TIME, System.currentTimeMillis());

            clientMgmtService.addClient(client);
        } else {
            client.put(Client.CLIENT_ADMIN_EMAIL, clientAdminEmail);
            client.put(Client.CLIENT_HOST, clientHost);
            client.put(Client.CLIENT_NAME, clientName);
            client.put(Client.CLIENT_RUNTIME_ENV, clientRuntimeEnv);
            client.put(Client.CLIENT_VERSION, clientVersion);
            client.put(Client.CLIENT_LATEST_ADD_ARTICLE_TIME, System.currentTimeMillis());

            clientMgmtService.updateClient(client);
        }
    }

    /**
     * Markdowns.
     *
     *

     * Renders the response with a json object, for example,
     *

     * {
     *     "html": ""
     * }
     *

     *

     *
     * @param request the specified http servlet request
     * @param response the specified http servlet response
     * @param context the specified http request context
     * @throws Exception exception
     */
    @RequestProcessing(value = "/markdown", method = HTTPRequestMethod.POST)
    @Before(adviceClass = {StopwatchStartAdvice.class, LoginCheck.class})
    @After(adviceClass = StopwatchEndAdvice.class)
    public void markdown2HTML(final HttpServletRequest request, final HttpServletResponse response, final HTTPRequestContext context)
            throws Exception {
        context.renderJSON(true);

        String markdownText = request.getParameter("markdownText");
        if (Strings.isEmptyOrNull(markdownText)) {
            context.renderJSONValue("html", "");

            return;
        }

        final JSONObject currentUser = userQueryService.getCurrentUser(request);
        if (null == currentUser) {
            response.sendError(HttpServletResponse.SC_FORBIDDEN);

            return;
        }

        final Set<String> userNames = userQueryService.getUserNames(markdownText);
        for (final String userName : userNames) {
            markdownText = markdownText.replace('@' + userName, "@" + userName + "");
        }
        markdownText = shortLinkQueryService.linkArticle(markdownText);
        markdownText = shortLinkQueryService.linkTag(markdownText);
        markdownText = Emotions.convert(markdownText);
        markdownText = Markdowns.toHTML(markdownText);
        markdownText = Markdowns.clean(markdownText, "");

        context.renderJSONValue("html", markdownText);
    }

    /**
     * Gets article preview content.
     *
     *

     * Renders the response with a json object, for example,
     *

     * {
     *     "html": ""
     * }
     *

     *

     *
     * @param request the specified http servlet request
     * @param response the specified http servlet response
     * @param context the specified http request context
     * @param articleId the specified article id
     * @throws Exception exception
     */
    @RequestProcessing(value = "/article/{articleId}/preview", method = HTTPRequestMethod.GET)
    @Before(adviceClass = StopwatchStartAdvice.class)
    @After(adviceClass = StopwatchEndAdvice.class)
    public void getArticlePreviewContent(final HttpServletRequest request, final HttpServletResponse response,
            final HTTPRequestContext context, final String articleId) throws Exception {
        final String content = articleQueryService.getArticlePreviewContent(articleId, request);
        if (StringUtils.isBlank(content)) {
            context.renderJSON().renderFalseResult();

            return;
        }

        context.renderJSON(true).renderJSONValue("html", content);
    }

    /**
     * Article rewards.
     *
     * @param request the specified http servlet request
     * @param response the specified http servlet response
     * @param context the specified http request context
     * @throws Exception exception
     */
    @RequestProcessing(value = "/article/reward", method = HTTPRequestMethod.POST)
    @Before(adviceClass = StopwatchStartAdvice.class)
    @After(adviceClass = StopwatchEndAdvice.class)
    public void reward(final HttpServletRequest request, final HttpServletResponse response, final HTTPRequestContext context)
            throws Exception {
        final JSONObject currentUser = userQueryService.getCurrentUser(request);
        if (null == currentUser) {
            response.sendError(HttpServletResponse.SC_FORBIDDEN);

            return;
        }

        final String articleId = request.getParameter(Article.ARTICLE_T_ID);
        if (Strings.isEmptyOrNull(articleId)) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST);

            return;
        }

        context.renderJSON();

        try {
            articleMgmtService.reward(articleId, currentUser.optString(Keys.OBJECT_ID));
        } catch (final ServiceException e) {
            context.renderMsg(langPropsService.get("transferFailLabel"));

            return;
        }

        final JSONObject article = articleQueryService.getArticle(articleId);
        articleQueryService.processArticleContent(article, request);

        context.renderTrueResult().
                renderJSONValue(Article.ARTICLE_REWARD_CONTENT, article.optString(Article.ARTICLE_REWARD_CONTENT));
    }

    /**
     * Article thanks.
     *
     * @param request the specified http servlet request
     * @param response the specified http servlet response
     * @param context the specified http request context
     * @throws Exception exception
     */
    @RequestProcessing(value = "/article/thank", method = HTTPRequestMethod.POST)
    @Before(adviceClass = StopwatchStartAdvice.class)
    @After(adviceClass = StopwatchEndAdvice.class)
    public void thank(final HttpServletRequest request, final HttpServletResponse response, final HTTPRequestContext context)
            throws Exception {
        final JSONObject currentUser = userQueryService.getCurrentUser(request);
        if (null == currentUser) {
            response.sendError(HttpServletResponse.SC_FORBIDDEN);

            return;
        }

        final String articleId = request.getParameter(Article.ARTICLE_T_ID);
        if (Strings.isEmptyOrNull(articleId)) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST);

            return;
        }

        context.renderJSON();

        try {
            articleMgmtService.thank(articleId, currentUser.optString(Keys.OBJECT_ID));
        } catch (final ServiceException e) {
            context.renderMsg(langPropsService.get("transferFailLabel"));

            return;
        }

        context.renderTrueResult();
    }

    /**
     * Sticks an article.
     *
     * @param request the specified HTTP servlet request
     * @param response the specified HTTP servlet response
     * @param context the specified HTTP request context
     * @throws Exception exception
     */
    @RequestProcessing(value = "/article/stick", method = HTTPRequestMethod.POST)
    @Before(adviceClass = StopwatchStartAdvice.class)
    @After(adviceClass = StopwatchEndAdvice.class)
    public void stickArticle(final HttpServletRequest request, final HttpServletResponse response, final HTTPRequestContext context)
            throws Exception {
        final JSONObject currentUser = userQueryService.getCurrentUser(request);
        if (null == currentUser) {
            response.sendError(HttpServletResponse.SC_FORBIDDEN);

            return;
        }

        final String articleId = request.getParameter(Article.ARTICLE_T_ID);
        if (Strings.isEmptyOrNull(articleId)) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST);

            return;
        }

        final JSONObject article = articleQueryService.getArticle(articleId);
        if (null == article) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);

            return;
        }

        if (!currentUser.optString(Keys.OBJECT_ID).equals(article.optString(Article.ARTICLE_AUTHOR_ID))) {
            response.sendError(HttpServletResponse.SC_FORBIDDEN);

            return;
        }

        context.renderJSON();

        try {
            articleMgmtService.stick(articleId);
        } catch (final ServiceException e) {
            context.renderMsg(e.getMessage());

            return;
        }

        context.renderTrueResult().renderMsg(langPropsService.get("stickSuccLabel"));
    }

    /**
     * Expires a sticked article.
     *
     * @param request the specified HTTP servlet request
     * @param response the specified HTTP servlet response
     * @param context the specified HTTP request context
     * @throws Exception exception
     */
    @RequestProcessing(value = "/cron/article/stick-expire", method = HTTPRequestMethod.GET)
    @Before(adviceClass = StopwatchStartAdvice.class)
    @After(adviceClass = StopwatchEndAdvice.class)
    public void expireStickArticle(final HttpServletRequest request, final HttpServletResponse response, final HTTPRequestContext context)
            throws Exception {
        final String key = Symphonys.get("keyOfSymphony");
        if (!key.equals(request.getParameter("key"))) {
            response.sendError(HttpServletResponse.SC_FORBIDDEN);

            return;
        }

        articleMgmtService.expireStick();

        context.renderJSON().renderTrueResult();
    }
}


text:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using OAI.Packets;

using OAI.Packets.Events;
using OAI.Packets.Events.Commands;
using OAI.Packets.Events.Agent;
using OAI.Packets.Events.Call;
using OAI.Packets.Events.Feature;
using OAI.Packets.Events.Gateway;
using OAI.Packets.Events.Misc;
using OAI.Packets.Events.System;
using OAI.Packets.Events.Unknown;

using OAI.Queues;
using OAI.Tools;

namespace OAI.Factories
{
    public class OAIEventFactory
    {
        public static OAIEvent Production(byte[] bytes)
        {
            if (0 == bytes.Length)
            {
                return null;
            }

            String packet = OAIUtils.GetString(bytes);

            if (null == packet)
            {
                return null;
            }

            string[] parts = OAIUtils.PBXPacketSplit(packet);

            if (0 == parts.Length)
            {
                return null;
            }

            return ProcessEvent(parts);
        }

        public static OAIEvent ProcessEvent(string[] parts)
        {
            // CF - Confirmation
            if (0 == OAIConfirmation.EVENT.CompareTo(parts[1]))
            {
                return ConfirmationLine(parts);
            }

            OAIEvent evt = null;

            // Agent Events
            if (null != (evt = AgentEvent(parts)))
            {
                return evt;
            }

            // Call Events
            if (null != (evt = CallEvent(parts)))
            {
                return evt;
            }

            // Feature Events
            if (null != (evt = FeatureEvent(parts)))
            {
                return evt;
            }

            // Gateway Events
            if (null != (evt = GatewayEvent(parts)))
            {
                return evt;
            }

            // Misc Events
            if (null != (evt = MiscEvent(parts)))
            {
                return evt;
            }

            // System Events
            if (null != (evt = SystemEvent(parts)))
            {
                return evt;
            }

            return new OAIUnknownEvent(parts);
        }

        public static OAIAgentEvent AgentEvent(string[] parts)
        {
            // LF - Logoff
            if (0 == OAILogoff.EVENT.CompareTo(parts[1]))
            {
                return new OAILogoff(parts);
            }

            // LN - Logon
            if (0 == OAILogon.EVENT.CompareTo(parts[1]))
            {
                return new OAILogon(parts);
            }

            // NRY - Not Ready
            if (0 == OAINotReady.EVENT.CompareTo(parts[1]))
            {
                return new OAINotReady(parts);
            }

            // RY - Ready
            if (0 == OAIReady.EVENT.CompareTo(parts[1]))
            {
                return new OAIReady(parts);
            }

            // WNR - Work Not Ready
            if (0 == OAIWorkNotReady.EVENT.CompareTo(parts[1]))
            {
                return new OAIWorkNotReady(parts);
            }

            return null;
        }

        public static OAIEvent CallEvent(string[] parts)
        {
            // CC - Call Cleared
            if (0 == OAICallCleared.EVENT.CompareTo(parts[1]))
            {
                return new OAICallCleared(parts);
            }

            // CO - Conferenced
            if (0 == OAIConferenced.EVENT.CompareTo(parts[1]))
            {
                return new OAIConferenced(parts);
            }

            // XC - Connection Cleared
            if (0 == OAIConnectionCleared.EVENT.CompareTo(parts[1]))
            {
                return new OAIConnectionCleared(parts);
            }

            // DE - Delivered
            if (0 == OAIDelivered.EVENT.CompareTo(parts[1]))
            {
                return new OAIDelivered(parts);
            }

            // DI - Diverted
            if (0 == OAIDiverted.EVENT.CompareTo(parts[1]))
            {
                return new OAIDiverted(parts);
            }

            // ES - Established
            if (0 == OAIEstablished.EVENT.CompareTo(parts[1]))
            {
                return new OAIEstablished(parts);
            }

            // ER - Established Routing
            if (0 == OAIEstablishedRouting.EVENT.CompareTo(parts[1]))
            {
                return new OAIEstablishedRouting(parts);
            }

            // FA - Failed
            if (0 == OAIFailed.EVENT.CompareTo(parts[1]))
            {
                return new OAIFailed(parts);
            }

            // HE - Held
            if (0 == OAIHeld.EVENT.CompareTo(parts[1]))
            {
                return new OAIHeld(parts);
            }

            // NT - Network Reached
            if (0 == OAINetworkReached.EVENT.CompareTo(parts[1]))
            {
                return new OAINetworkReached(parts);
            }

            // OR - Originated
            if (0 == OAIOriginated.EVENT.CompareTo(parts[1]))
            {
                return new OAIOriginated(parts);
            }

            // QU - Queued
            if (0 == OAIQueued.EVENT.CompareTo(parts[1]))
            {
                return new OAIQueued(parts);
            }

            // RE - Retrieved
            if (0 == OAIRetrieved.EVENT.CompareTo(parts[1]))
            {
                return new OAIRetrieved(parts);
            }

            // SI - Service Initiated
            if (0 == OAIServiceInitiated.EVENT.CompareTo(parts[1]))
            {
                return new OAIServiceInitiated(parts);
            }

            // TR - Transferred
            if (0 == OAITransferred.EVENT.CompareTo(parts[1]))
            {
                return new OAITransferred(parts);
            }

            return null;
        }

        public static OAIEvent FeatureEvent(string[] parts)
        {
            // CB - Callback
            if (0 == OAICallback.EVENT.CompareTo(parts[1]))
            {
                return new OAICallback(parts);
            }

            // CI - Call Info
            if (0 == OAICallInfo.EVENT.CompareTo(parts[1]))
            {
                return new OAICallInfo(parts);
            }

            // OL - Device Offline
            if (0 == OAIDeviceOffline.EVENT.CompareTo(parts[1]))
            {
                return new OAIDeviceOffline(parts);
            }

            // DCE - Display Control Eliminated
            if (0 == OAIDisplayControlEliminated.EVENT.CompareTo(parts[1]))
            {
                return new OAIDisplayControlEliminated(parts);
            }

            // DLC - Display Language Changed
            if (0 == OAIDisplayLanguageChanged.EVENT.CompareTo(parts[1]))
            {
                return new OAIDisplayLanguageChanged(parts);
            }

            // DND - Do-Not-Disturb
            if (0 == OAIDoNotDisturb.EVENT.CompareTo(parts[1]))
            {
                return new OAIDoNotDisturb(parts);
            }

            // ET - Elapsed Time For CO Call
            if (0 == OAIElapsedTimeForCOCall.EVENT.CompareTo(parts[1]))
            {
                return new OAIElapsedTimeForCOCall(parts);
            }

            // FE - Feature Status
            if (0 == OAIFeatureStatus.EVENT.CompareTo(parts[1]))
            {
                return new OAIFeatureStatus(parts);
            }

            // FW - Forwarded
            if (0 == OAIForwarded.EVENT.CompareTo(parts[1]))
            {
                return new OAIForwarded(parts);
            }

            // GI - General Information
            if (0 == OAIGeneralInformation.EVENT.CompareTo(parts[1]))
            {
                return new OAIGeneralInformation(parts);
            }

            // MU - Keyset Mute
            if (0 == OAIKeysetMute.EVENT.CompareTo(parts[1]))
            {
                return new OAIKeysetMute(parts);
            }

            // MW - MSG Waiting
            if (0 == OAIMSGWaiting.EVENT.CompareTo(parts[1]))
            {
                return new OAIMSGWaiting(parts);
            }

            // OE - Offering Ended
            if (0 == OAIOfferingEnded.EVENT.CompareTo(parts[1]))
            {
                return new OAIOfferingEnded(parts);
            }

            return null;
        }

        public static OAIEvent GatewayEvent(string[] parts)
        {
            // CS - Connection Status Event
            if (0 == OAIConnectionStatusEvent.EVENT.CompareTo(parts[1]))
            {
                return new OAIConnectionStatusEvent(parts);
            }

            // GDA - Gateway Data from Application
            if (0 == OAIGatewayDataFromApplication.EVENT.CompareTo(parts[1]))
            {
                return new OAIGatewayDataFromApplication(parts);
            }

            // LS - Link Status Event
            if (0 == OAILinkStatusEvent.EVENT.CompareTo(parts[1]))
            {
                return new OAILinkStatusEvent(parts);
            }

            return null;
        }

        public static OAIEvent MiscEvent(string[] parts)
        {
            // DEE - Display Entered Extension
            if (0 == OAIDisplayEnteredExtension.EVENT.CompareTo(parts[1]))
            {
                return new OAIDisplayEnteredExtension(parts);
            }

            // DSC - Display Station Change
            if (0 == OAIDisplayStationChange.EVENT.CompareTo(parts[1]))
            {
                return new OAIDisplayStationChange(parts);
            }

            // ME - Monitor Ended
            if (0 == OAIMonitorEnded.EVENT.CompareTo(parts[1]))
            {
                return new OAIMonitorEnded(parts);
            }

            // OF - Offered
            if (0 == OAIOffered.EVENT.CompareTo(parts[1]))
            {
                return new OAIOffered(parts);
            }

            // PE - Ports Exceeded
            if (0 == OAIPortsExceeded.EVENT.CompareTo(parts[1]))
            {
                return new OAIPortsExceeded(parts);
            }

            // RD - Resync Ended
            if (0 == OAIResyncEnded.EVENT.CompareTo(parts[1]))
            {
                return new OAIResyncEnded(parts);
            }

            // RS - Resync Response
            if (0 == OAIResyncResponse.EVENT.CompareTo(parts[1]))
            {
                return new OAIResyncResponse(parts);
            }

            // SMR - Station Message Recording
            if (0 == OAIStationMessageRecording.EVENT.CompareTo(parts[1]))
            {
                return new OAIStationMessageRecording(parts);
            }

            return null;
        }

        public static OAIEvent SystemEvent(string[] parts)
        {
            // AG - Alarm Generation
            if (0 == OAIAlarmGeneration.EVENT.CompareTo(parts[1]))
            {
                return new OAIAlarmGeneration(parts);
            }

            // DC - Database Change
            if (0 == OAIDatabaseChange.EVENT.CompareTo(parts[1]))
            {
                return new OAIDatabaseChange(parts);
            }

            // EC - Extension Change
            if (0 == OAIExtensionChange.EVENT.CompareTo(parts[1]))
            {
                return new OAIExtensionChange(parts);
            }

            // NM - Night Mode Status
            if (0 == OAINightModeStatus.EVENT.CompareTo(parts[1]))
            {
                return new OAINightModeStatus(parts);
            }

            return null;
        }

        public static OAIConfirmation ConfirmationLine(string[] parts)
        {
            // _QX -
            if (0 == OAIQueryListExtendedCF.COMMAND.CompareTo(parts[2]))
            {
                return new OAIQueryListExtendedCF(parts);
            }

            // _QH -
            if (0 == OAIQueryHuntGroupCF.COMMAND.CompareTo(parts[2]))
            {
                return new OAIQueryHuntGroupCF(parts);
            }

            // _CC -
            if (0 == OAIClearCallCF.COMMAND.CompareTo(parts[2]))
            {
                return new OAIClearCallCF(parts);
            }

            // _CX -
            if (0 == OAIClearConnectionCF.COMMAND.CompareTo(parts[2]))
            {
                return new OAIClearConnectionCF(parts);
            }

            // _MC -
            if (0 == OAIMakeCallCF.COMMAND.CompareTo(parts[2]))
            {
                return new OAIMakeCallCF(parts);
            }

            // _NO -
            if (0 == OAINoOperationCF.COMMAND.CompareTo(parts[2]))
            {
                return new OAINoOperationCF(parts);
            }

            // _RR -
            if (0 == OAIResyncRequestCF.COMMAND.CompareTo(parts[2]))
            {
                return new OAIResyncRequestCF(parts);
            }

            // Unknown confirmation
            return new OAIConfirmation(parts);
        }
    }
}

redpajama_set_name: RedPajamaGithub


text:
using SimpleSql.Infrastructure;
using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.Linq;
using System.Text;

namespace SimpleSql
{
    public static class BaseExtensions
    {

        private static void SetupCommand(IDbCommand cmd, string sql, object parm = null)
        {
            cmd.CommandText = sql;
            cmd.CommandType = CommandType.Text;
            if (parm != null)
            {
                var paramters = GetParamters(parm).parameters;
                foreach (var item in paramters.Values)
                {
                    var p = cmd.CreateParameter();
                    p.ParameterName = item.Name;
                    p.Value = item.Value;
                    var dbType = item.DbType;
                    //查找DbType
                    if (dbType == null && item.Value != null)
                        dbType = DbTypeMap.Lookup(item.Value.GetType());
                    if (dbType.HasValue)
                        p.DbType = dbType.Value;
                    if (item.Size.HasValue)
                        p.Size = item.Size.Value;
                    if (item.Direction.HasValue)
                        p.Direction = item.Direction.Value;
                    cmd.Parameters.Add(p);
                }

            }

        }
        private static DynamicParameters GetParamters(object param)
        {
            if (param is DynamicParameters) return param as DynamicParameters;
            var type = param.GetType();
            if (!type.IsClass)
            {
                throw new Exception($"{type.Name}不是Class");
            }
            //TODO 缓存属性信息
            var props = type.GetProperties();
            var dynamicParamters = new DynamicParameters();
            foreach (var p in props)
            {
                dynamicParamters.Add(p.Name, p.GetValue(param));
            }
            return dynamicParamters;
        }
        public static int Execute(this IDbConnection conn, string sql, object param = null)
        {
            if (conn.State == ConnectionState.Closed) conn.Open();
            var cmd = conn.CreateCommand();
            SetupCommand(cmd, sql, param); ;
            var res = cmd.ExecuteNonQuery();
            if (conn.State == ConnectionState.Open) conn.Close();
            return res;
        }
        public static T ExecuteScalar<T>(this IDbConnection conn, string sql, object param = null)
        {
            if (conn.State == ConnectionState.Closed) conn.Open();
            var cmd = conn.CreateCommand();
            SetupCommand(cmd, sql, param);
            var res = (T)Convert.ChangeType(cmd.ExecuteScalar(), typeof(T), CultureInfo.InvariantCulture);
            if (conn.State == ConnectionState.Open) conn.Close();
            return res;
        }
        public static IEnumerable<T> Query<T>(this IDbConnection conn, string sql, object param = null)
        {
            if (conn.State == ConnectionState.Closed) conn.Open();
            var cmd = conn.CreateCommand();
            SetupCommand(cmd, sql, param); ;
            var reader = cmd.ExecuteReader();
            var type = typeof(T);
            while (reader.Read() && reader.FieldCount != 0)
            {
                //TODO Emit优化反射部分
                if (type.IsClass&&type!=typeof(string))
                {
                    var props = type.GetProperties();
                    var obj = Activator.CreateInstance<T>();
                    for (int i = 0; i < reader.FieldCount; i++)
                    {
                        var colName = reader.GetName(i).ToLower();
                        var p = props.FirstOrDefault(x => x.Name.ToLower() == colName);
                        if (p != null)
                        {
                            var val = reader.GetValue(i);
                            if (val is DBNull)
                                continue;
                            if (p.PropertyType == typeof(Boolean))
                            {
                                p.SetValue(obj, Convert.ToInt32(val) == 1);
                            }
                            //else if (p.PropertyType.IsEnum)
                            //{

                            //}
                            else
                            {
                                p.SetValue(obj, val);
                            }

                        }
                    }
                    yield return obj;
                }
                else
                {
                    yield return (T)Convert.ChangeType(reader[0], type, CultureInfo.InvariantCulture);
                }

            }
            if (conn.State == ConnectionState.Open) conn.Close();
        }
        public static T QueryFirst<T>(this IDbConnection conn, string sql, object param = null)
        {
            if (conn.State == ConnectionState.Closed) conn.Open();
            var cmd = conn.CreateCommand();
            SetupCommand(cmd, sql, param); ;
            var reader = cmd.ExecuteReader();
            var type = typeof(T);
            var result = default(T);
            if (reader.Read() && reader.FieldCount != 0)
            {
                //TODO Emit优化反射部分
                if (type.IsClass)
                {
                    var props = type.GetProperties();
                    var obj = Activator.CreateInstance<T>();
                    for (int i = 0; i < reader.FieldCount; i++)
                    {
                        var colName = reader.GetName(i).ToLower();
                        var p = props.FirstOrDefault(x => x.Name.ToLower() == colName);
                        if (p != null)
                        {
                            var val = reader.GetValue(i);
                            if (val is DBNull)
                                continue;
                            p.SetValue(obj, val);
                        }
                    }
                    result = obj;
                }
                else
                {
                    result = (T)Convert.ChangeType(reader[0], type, CultureInfo.InvariantCulture);
                }

            }
            if (conn.State == ConnectionState.Open) conn.Close();
            return result;
        }
    }

}


text:
This is the Spring portion of a [benchmarking test suite](../) comparing a variety of web development platforms.

This is using several [Spring projects](https://spring.io/projects) to build an application.

## How to run this app locally

You can easily run this sample application with an embedded H2 database.
For that, simply run:

    SPRING_PROFILES_ACTIVE=local mvn spring-boot:run

## Guides
* [Building a REST service](https://spring.io/guides/gs/rest-service/)
* [Building an application with Spring Boot](https://spring.io/guides/gs/spring-boot/)
* [Accessing data with JPA](https://spring.io/guides/gs/accessing-data-jpa/)

## Documentation
See [Spring projects documentation](https://spring.io/docs).

## Application Endpoints

When deployed locally, the application uses the 'ROOT' context; for the benchmark, all
URLs are prefixed with "/spring".
Check out [SampleApplication, the main Application file](src/main/java/com/techempower/spring/SampleApplication.java)

### JSON serialization

* http://localhost:8080/json
* [JSON Controller](src/main/java/com/techempower/spring/web/HelloController.java)

### Single database query

* http://localhost:8080/db
* [Database Controller](src/main/java/com/techempower/spring/web/WorldDatabaseController.java)
* [Database Entity](src/main/java/com/techempower/spring/domain/World.java)
* [Database Repository](src/main/java/com/techempower/spring/service/WorldRepository.java)

### Multiple database queries

* http://localhost:8080/queries?queries=5
* [Database Controller](src/main/java/com/techempower/spring/web/WorldDatabaseController.java)

### Fortunes

* http://localhost:8080/fortunes
* [Fortune Controller](src/main/java/com/techempower/spring/web/FortuneController.java)

### Database updates

* http://localhost:8080/updates?queries=5
* [Database Controller](src/main/java/com/techempower/spring/web/WorldDatabaseController.java)

### Plaintext

* http://localhost:8080/plaintext
* [Controller](src/main/java/com/techempower/spring/web/HelloController.java)

## Infrastructure Software Versions
The tests were run with:

* [Spring 4.0.2.RELEASE](http://projects.spring.io/spring-framework/)
* [Spring Boot 1.0.0.RC4](http://projects.spring.io/spring-boot/)
* [Spring Data JPA 1.5.0.RELEASE](http://projects.spring.io/spring-data-jpa/)
* [Java OpenJDK 1.7.0_09](http://openjdk.java.net/)
* [Tomcat 8.0.3](https://tomcat.apache.org/)


text:
<!doctype html>

		<meta charset="utf-8">
		<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
		<title>Polymer • TodoMVC</title>
		<link rel="stylesheet" href="/app/app.css">
		<link rel="import" href="/lib-elements/polymer-localstorage.html">

		<link rel="import" href="/elements/td-todos.html">

todos

		<polymer-localstorage id="storage" name="todos-polymer"></polymer-localstorage>

Double-click to edit a todo

Created by The Polymer Authors

Part of TodoMVC


text:
require_relative 'constants'
require 'json'

module VcoWorkflows
  # WorkflowParameter is an object wrapper for workflow input and output
  # parameters.
  class WorkflowParameter
    # Parameter name
    # @return [String] parameter name
    attr_reader :name

    # Parameter type
    # @return [String] parameter type
    attr_reader :type

    # Parameter subtype (used when type is 'Array')
    # @return [String] parameter subtype
    attr_reader :subtype

    # Parameter value
    # @return [Object] parameter value
    attr_reader :value

    # rubocop:disable MethodLength

    # Create a new workflow parameter object
    # @param [String] name Name of the workflow parameter
    # @param [String] type Data type of the parameter (according to vCO)
    # @return [VcoWorkflows::WorkflowParameter]
    def initialize(name = nil, type = nil, options = {})
      # Merge provided options with our defaults
      options = {
        required: false,
        value: nil
      }.merge(options)

      @name = name

      case type
      when %r{\/}
        @type = type.gsub(%r{\/.*$}, '')
        @subtype = type.gsub(%r{^.*\/}, '')
      else
        @type = type
        @subtype = nil
      end

      @required = options[:required]

      # If value is supposed to be an array but we dont' have a value yet,
      # create an empty array. If it's not supposed to be an array, just
      # set the value, even if it's still nil.
      if options[:value].nil?
        @type.eql?('Array') ? @value = [] : @value = nil
      else
        @value = set(options[:value])
      end
    end
    # rubocop:enable MethodLength

    # Set the parameter value
    # @param [Object] value Value for the parameter
    def set(value)
      # Do some basic checking for Arrays.
      case @type
      when 'Array'
        fail(IOError, ERR[:param_verify_failed]) unless value.is_a?(Array)
      end unless value.nil?
      @value = value
    end
    # rubocop:enable CyclomaticComplexity

    # Has a value been set for this parameter?
    # @return [Boolean]
    def set?
      case value.class
      when Array
        value.size == 0 ? false : true
      else
        value.nil? ? false : true
      end
    end

    # rubocop:disable TrivialAccessors

    # Set whether or not this WorkflowParameter is required
    # @param [Boolean] required Set this parameter as required (if not specified)
    def required(required = true)
      @required = required
    end
    # rubocop:enable LineLength

    # Determine whether or not this WorkflowParameter has been marked as
    # required
    # @return [Boolean]
    def required?
      @required
    end
    # rubocop:enable TrivialAccessors

    # rubocop:disable CyclomaticComplexity, PerceivedComplexity, MethodLength

    # Return a string representation of the parameter
    # @return [String] Pretty-formatted string
    def to_s
      string = "#{@name}"
      # If value is either nil or an empty array
      if @value.nil? || @value.is_a?(Array) && @value.size == 0
        string << " (#{@type}"
        string << "/#{@subtype}" if @subtype
        string << ')'
        string << ' [required]' if @required
      else
        if @type.eql?('Array')
          string << ' ='
          @value.each { |v| string << "\n  - #{v}" }
        else
          string << " = #{@value}"
        end
      end
      string << "\n"
    end
    # rubocop:enable CyclomaticComplexity, PerceivedComplexity, MethodLength

    # Public
    # Return a JSON document representation of this object
    # @return [String] JSON representation
    def to_json
      as_struct.to_json
    end

    # rubocop:disable LineLength

    # Hashify the parameter (primarily useful for converting to JSON or YAML)
    # @return [Hash] Contents of this object as a hash
    def as_struct
      attributes = { type: @type, name: @name, scope: 'local' }

      # If the value is an array, we need to build it in the somewhat silly
      # manner that vCO requires it to be presented. Otherwise, just paste
      # it on the end of the hash.
      if @type.eql?('Array')
        fail(IOError, ERR[:wtf]) unless @value.is_a?(Array)
        attributes[:value] = { @type.downcase => { elements: [] } }
        @value.each { |val| attributes[:value][@type.downcase][:elements] << { @subtype => { value: val } } }
      else
        attributes[:value] = { @type => { value: @value } }
      end
      attributes
    end
    # rubocop:enable LineLength
  end
end


text:
package uk.gov.hmrc.ct.ct600.v3

import uk.gov.hmrc.ct.box._
import uk.gov.hmrc.ct.ct600.v3.retriever.CT600BoxRetriever

case class REPAYMENTSQ1(value: Option[Boolean]) extends CtBoxIdentifier("Send Repayment in all cases?")
with CtOptionalBoolean with Input with ValidatableBox[CT600BoxRetriever] {

  def validate(boxRetriever: CT600BoxRetriever): Set[CtValidation] = validateAsMandatory(this)
}


text:
namespace School.Test
{
    using System;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Model;

    [TestClass]
    public class CourseTest
    {
        private const string courseName = "Csharp";
        private const string studentName = "Georgi";
        private const int uniqueNumber = 77777;

        [TestMethod]
        public void Course_ConstructorNameTest()
        {
            var testCourse = new Course(courseName);

            Assert.AreEqual(courseName, testCourse.CourseName);
        }

        [TestMethod]
        public void Couse_StudentListTest()
        {
            var course = new Course(courseName);
            var student = new Student(studentName, uniqueNumber);

            course.AddStudent(student);

            Assert.IsTrue(course.Students.Contains(student));
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Course_NullNameTest()
        {
            string name = null;
            var course = new Course(name);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Course_EmptyNameTest()
        {
            var name = string.Empty;
            var course = new Course(name);
        }

        [TestMethod]
        public void Course_AddingStudentTest()
        {
            var course = new Course(courseName);

            for (var i = 0; i < 10; i++)
            {
;               var student = new Student(studentName, uniqueNumber + i);
                course.AddStudent(student);
            }

            Assert.IsTrue(course.Students.Count == 10);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Course_AddingNullStudentTest()
        {
            var course = new Course(courseName);
            Student student = null;

            course.AddStudent(student);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void Course_AddingStudent_ArgumentException()
        {
            var course = new Course(courseName);
            var ivan = new Student(studentName, uniqueNumber);

            course.AddStudent(ivan);
            course.AddStudent(ivan);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void Course_MaximumStudentsTest()
        {
            var course = new Course(courseName);

            for (var i = 0; i < 31; i++)
            {
                var student = new Student(studentName, uniqueNumber + i);
                course.AddStudent(student);
            }
        }

        [TestMethod]
        public void Course_RemoveStudentTest()
        {
            var course = new Course(courseName);
            var student = new Student(studentName, uniqueNumber);

            course.AddStudent(student);
            course.RemoveStudent(student);

            Assert.AreEqual(course.Students.Count, 0);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void Course_RemoveNonExistStudentTest()
        {
            var course = new Course(courseName);
            var student = new Student(studentName, uniqueNumber);

            course.RemoveStudent(student);
        }

        [TestMethod]
        public void Course_ToStringTest()
        {
            var course = new Course(courseName);
            var student = new Student(studentName, uniqueNumber);
            course.AddStudent(student);

            var expected = $"Course name {courseName}; Student {studentName}, ID {uniqueNumber}; ";
            var actual = course.ToString();

            Assert.AreEqual(expected, actual);
        }
    }
}


text:
var AuthProvider = require('../security/auth-provider'),
    scopes = require('../constants').scopes;

var tenantsCache = {};

var makeTenantClient = (function() {
  var c;
  return function() {
    return (c || (c = require('../clients/platform/tenant'))).apply(this, arguments);
  };
}());

function urlRequiresTenantPod(template) {
  return template.indexOf('{+tenantPod}') !== -1;
}

function urlRequiresPciPod(template) {
  return template.indexOf('{+pciPod}') !== -1;
}

function cacheTenantsFromTicket(ticket) {
  var tenants = ticket.availableTenants;
  if (tenants) {
    for (var i = 0; i < tenants.length; i++) {
      tenantsCache[tenants[i].id] = tenants[i];
    }
  }
  delete ticket.availableTenants;
  return ticket;
}

function getTenantInfo(id) {
  return tenantsCache[id];
}

  /**
 * Return an array of tasks (functions returning Promises) that performs, in sequence, all necessary authentication tasks for the given scope.
 * @param  {Client} client The client in whose context to run the tasks. AuthProvider will cache the claims per client.
 * @param  {Scope} scope  A scope (bitmask). If the scope is not NONE, then app claims will be added. If the scope is DEVELOPER xor ADMINUSER, user claims will be added.
 * @return {Array}        A list of tasks. If no auth is required, the list will be empty.
 */
 function getTasks(client, options, requestConfig) {

  var scope;
  // support a named scope, or a reference to a scope bitmask
  if (options && options.scope) {
    if (scopes[options.scope]) {
      scope = scopes[options.scope];
    } else {
      scope = options.scope;
    }
  } else {
    scope = requestConfig.scope;
  }

  var tasks = [];

  if (scope & scopes.DEVELOPER) {
    tasks.push(function() {
      return AuthProvider.addDeveloperUserClaims(client).then(cacheTenantsFromTicket);
    });
  } else if (scope & scopes.ADMINUSER) {
    tasks.push(function() {
      return AuthProvider.addAdminUserClaims(client).then(cacheTenantsFromTicket);
    });
  }
  if (!scope && AuthProvider.addMostRecentUserClaims) {
    tasks.push(function() {
      return AuthProvider.addMostRecentUserClaims(client);
    });
  }

  //if url requires a PCI pod but context is not set throw error
  if (urlRequiresPciPod(requestConfig.url)) {
    if (!client.context.tenant) {
      throw new Error("Could not perform request to PCI service '" + requestConfig.url + "'; no tenant ID was set.");
    }
  }

  // if url requires a tenant pod but we don't have one...
  var currentTenant;
  if (urlRequiresTenantPod(requestConfig.url) && !client.context.tenantPod) {
    if (!client.context.tenant) {
      throw new Error("Could not perform request to tenant-scoped service '" + requestConfig.url + "'; no tenant ID was set.");
    }
    currentTenant = getTenantInfo(client.context.tenant);
    if (currentTenant) {
      client.context.tenantPod = 'https://' + currentTenant.domain + '/';
    } else {
      tasks.push(function() {
        return makeTenantClient(client).getTenant().then(function(tenant) {
          tenantsCache[tenant.id] = tenant;
          client.context.tenantPod = 'https://' + tenant.domain + '/';
        });
      });
    }
  }

  if (!(scope & scopes.NONE)) {
    tasks.push(function() {
      return AuthProvider.addPlatformAppClaims(client);
    });
  }

  return tasks;
}

module.exports = {
  getTasks: getTasks
};