org.wallride.service.PostService.java Source code

Java tutorial

Introduction

Here is the source code for org.wallride.service.PostService.java

Source

/*
 * Copyright 2014 Tagbangers, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.wallride.service;

import com.google.api.services.analytics.Analytics;
import com.google.api.services.analytics.model.GaData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import org.wallride.autoconfigure.WallRideCacheConfiguration;
import org.wallride.domain.*;
import org.wallride.exception.GoogleAnalyticsException;
import org.wallride.exception.ServiceException;
import org.wallride.model.PostSearchRequest;
import org.wallride.repository.PopularPostRepository;
import org.wallride.repository.PostRepository;
import org.wallride.support.GoogleAnalyticsUtils;
import org.wallride.web.controller.guest.article.ArticleDescribeController;
import org.wallride.web.controller.guest.page.PageDescribeController;
import org.wallride.web.support.BlogLanguageRewriteMatch;
import org.wallride.web.support.BlogLanguageRewriteRule;

import javax.persistence.criteria.Join;
import javax.persistence.criteria.Predicate;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import java.io.IOException;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 *
 * @author OGAWA, Takeshi
 */
@Service
@Transactional(rollbackFor = Exception.class)
public class PostService {

    @Autowired
    private CacheManager cacheManager;
    @Autowired
    private ServletContext servletContext;

    @Autowired
    private JobLauncher jobLauncher;
    @Autowired
    private JobExplorer jobExplorer;
    @Autowired
    private Job updatePostViewsJob;

    @Autowired
    private BlogService blogService;

    @Autowired
    private PostRepository postRepository;
    @Autowired
    private PopularPostRepository popularPostRepository;

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

    public List<Post> publishScheduledPosts() {
        logger.info("Starting public posts of the scheduled");

        LocalDateTime now = LocalDateTime.now();
        List<Post> posts = postRepository.findAllByStatusAndDateLessThanEqual(Post.Status.SCHEDULED, now);
        for (Post post : posts) {
            post.setStatus(Post.Status.PUBLISHED);
            postRepository.saveAndFlush(post);
        }

        if (!CollectionUtils.isEmpty(posts)) {
            cacheManager.getCache(WallRideCacheConfiguration.ARTICLE_CACHE).clear();
            cacheManager.getCache(WallRideCacheConfiguration.PAGE_CACHE).clear();
        }

        return posts;
    }

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void updatePostViews() {
        LocalDateTime now = LocalDateTime.now();
        Set<JobExecution> jobExecutions = jobExplorer.findRunningJobExecutions("updatePostViewsJob");
        for (JobExecution jobExecution : jobExecutions) {
            LocalDateTime startTime = LocalDateTime.ofInstant(jobExecution.getStartTime().toInstant(),
                    ZoneId.systemDefault());
            Duration d = Duration.between(now, startTime);
            if (Math.abs(d.toMinutes()) == 0) {
                logger.info("Skip processing because the job is running.");
                return;
            }
        }

        JobParameters params = new JobParametersBuilder()
                .addDate("now", Date.from(now.atZone(ZoneId.systemDefault()).toInstant())).toJobParameters();
        try {
            jobLauncher.run(updatePostViewsJob, params);
        } catch (Exception e) {
            throw new ServiceException(e);
        }
    }

    /**
     *
     * @param blogLanguage
     * @param type
     * @param maxRank
     * @see PostService#getPopularPosts(String, PopularPost.Type)
     */
    @CacheEvict(value = WallRideCacheConfiguration.POPULAR_POST_CACHE, key = "'list.type.' + #blogLanguage.language + '.' + #type")
    public void updatePopularPosts(BlogLanguage blogLanguage, PopularPost.Type type, int maxRank) {
        logger.info("Start update of the popular posts");

        GoogleAnalytics googleAnalytics = blogLanguage.getBlog().getGoogleAnalytics();
        if (googleAnalytics == null) {
            logger.info("Configuration of Google Analytics can not be found");
            return;
        }

        Analytics analytics = GoogleAnalyticsUtils.buildClient(googleAnalytics);

        WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext,
                "org.springframework.web.servlet.FrameworkServlet.CONTEXT.guestServlet");
        if (context == null) {
            logger.info("GuestServlet is not ready yet");
            return;
        }

        final RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);

        Map<Post, Long> posts = new LinkedHashMap<>();

        int startIndex = 1;
        int currentRetry = 0;
        int totalResults = 0;

        do {
            try {
                LocalDate now = LocalDate.now();
                LocalDate startDate;
                switch (type) {
                case DAILY:
                    startDate = now.minusDays(1);
                    break;
                case WEEKLY:
                    startDate = now.minusWeeks(1);
                    break;
                case MONTHLY:
                    startDate = now.minusMonths(1);
                    break;
                default:
                    throw new ServiceException();
                }

                DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
                Analytics.Data.Ga.Get get = analytics.data().ga()
                        .get(googleAnalytics.getProfileId(), startDate.format(dateTimeFormatter),
                                now.format(dateTimeFormatter), "ga:sessions")
                        .setDimensions(String.format("ga:pagePath", googleAnalytics.getCustomDimensionIndex()))
                        .setSort(String.format("-ga:sessions", googleAnalytics.getCustomDimensionIndex()))
                        .setStartIndex(startIndex).setMaxResults(GoogleAnalyticsUtils.MAX_RESULTS);
                if (blogLanguage.getBlog().isMultiLanguage()) {
                    get.setFilters("ga:pagePath=~/" + blogLanguage.getLanguage() + "/");
                }

                logger.info(get.toString());
                final GaData gaData = get.execute();
                if (CollectionUtils.isEmpty(gaData.getRows())) {
                    break;
                }

                for (List row : gaData.getRows()) {
                    UriComponents uriComponents = UriComponentsBuilder.fromUriString((String) row.get(0)).build();

                    MockHttpServletRequest request = new MockHttpServletRequest(servletContext);
                    request.setRequestURI(uriComponents.getPath());
                    request.setQueryString(uriComponents.getQuery());
                    MockHttpServletResponse response = new MockHttpServletResponse();

                    BlogLanguageRewriteRule rewriteRule = new BlogLanguageRewriteRule(blogService);
                    BlogLanguageRewriteMatch rewriteMatch = (BlogLanguageRewriteMatch) rewriteRule.matches(request,
                            response);
                    try {
                        rewriteMatch.execute(request, response);
                    } catch (ServletException e) {
                        throw new ServiceException(e);
                    } catch (IOException e) {
                        throw new ServiceException(e);
                    }

                    request.setRequestURI(rewriteMatch.getMatchingUrl());

                    HandlerExecutionChain handler;
                    try {
                        handler = mapping.getHandler(request);
                    } catch (Exception e) {
                        throw new ServiceException(e);
                    }

                    if (!(handler.getHandler() instanceof HandlerMethod)) {
                        continue;
                    }

                    HandlerMethod method = (HandlerMethod) handler.getHandler();
                    if (!method.getBeanType().equals(ArticleDescribeController.class)
                            && !method.getBeanType().equals(PageDescribeController.class)) {
                        continue;
                    }

                    // Last path mean code of post
                    String code = uriComponents.getPathSegments().get(uriComponents.getPathSegments().size() - 1);
                    Post post = postRepository.findOneByCodeAndLanguage(code,
                            rewriteMatch.getBlogLanguage().getLanguage());
                    if (post == null) {
                        logger.debug("Post not found [{}]", code);
                        continue;
                    }

                    if (!posts.containsKey(post)) {
                        posts.put(post, Long.parseLong((String) row.get(1)));
                    }
                    if (posts.size() >= maxRank) {
                        break;
                    }
                }

                if (posts.size() >= maxRank) {
                    break;
                }

                startIndex += GoogleAnalyticsUtils.MAX_RESULTS;
                totalResults = gaData.getTotalResults();
            } catch (IOException e) {
                logger.warn("Failed to synchronize with Google Analytics", e);
                if (currentRetry >= GoogleAnalyticsUtils.MAX_RETRY) {
                    throw new GoogleAnalyticsException(e);
                }

                currentRetry++;
                logger.info("{} ms to sleep...", GoogleAnalyticsUtils.RETRY_INTERVAL);
                try {
                    Thread.sleep(GoogleAnalyticsUtils.RETRY_INTERVAL);
                } catch (InterruptedException ie) {
                    throw new GoogleAnalyticsException(e);
                }
                logger.info("Retry for the {} time", currentRetry);
            }
        } while (startIndex <= totalResults);

        popularPostRepository.deleteByType(blogLanguage.getLanguage(), type);

        int rank = 1;
        for (Map.Entry<Post, Long> entry : posts.entrySet()) {
            PopularPost popularPost = new PopularPost();
            popularPost.setLanguage(blogLanguage.getLanguage());
            popularPost.setType(type);
            popularPost.setRank(rank);
            popularPost.setViews(entry.getValue());
            popularPost.setPost(entry.getKey());
            popularPostRepository.saveAndFlush(popularPost);
            rank++;
        }

        logger.info("Complete the update of popular posts");
    }

    public Page<Post> getPosts(PostSearchRequest request) {
        Pageable pageable = new PageRequest(0, 10);
        return getPosts(request, pageable);
    }

    public Page<Post> getPosts(PostSearchRequest request, Pageable pageable) {
        return postRepository.search(request, pageable);
    }

    /**
     *
     * @param language
     * @param type
     * @return
     * @see PostService#updatePopularPosts(BlogLanguage, PopularPost.Type, int)
     */
    @Cacheable(value = WallRideCacheConfiguration.POPULAR_POST_CACHE, key = "'list.type.' + #language + '.' + #type")
    public SortedSet<PopularPost> getPopularPosts(String language, PopularPost.Type type) {
        Specification<PopularPost> spec = (root, query, cb) -> {
            Join<PopularPost, Post> post = (Join<PopularPost, Post>) root.fetch(PopularPost_.post);
            post.fetch(Post_.cover);
            post.fetch(Post_.author);

            List<Predicate> predicates = new ArrayList<>();
            predicates.add(cb.equal(root.get(PopularPost_.language), language));
            predicates.add(cb.equal(root.get(PopularPost_.type), type));
            predicates.add(cb.equal(post.get(Post_.status), Post.Status.PUBLISHED));
            return cb.and(predicates.toArray(new Predicate[0]));
        };
        return popularPostRepository.findAll(spec);
    }

    public Post getPostById(long id, String language) {
        return postRepository.findOneByIdAndLanguage(id, language);
    }
}