package net.pechorina.kontempl.service; import com.google.common.base.Splitter; import com.google.common.collect.Iterables; import com.redfin.sitemapgenerator.ChangeFreq; import com.redfin.sitemapgenerator.W3CDateFormat; import com.redfin.sitemapgenerator.W3CDateFormat.Pattern; import com.redfin.sitemapgenerator.WebSitemapGenerator; import com.redfin.sitemapgenerator.WebSitemapUrl; import net.pechorina.kontempl.data.GenericTreeNode; import net.pechorina.kontempl.data.Page; import net.pechorina.kontempl.data.PageTree; import net.pechorina.kontempl.data.Site; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.ParseException; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.env.Environment; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.TimeZone; @Service public class SitemapService { static final Logger logger = LoggerFactory.getLogger(SitemapService.class); @Autowired private PageService pageService; @Autowired private PageTreeService pageTreeService; @Autowired private SiteService siteService; @Autowired private Environment env; @Value("${sitemapProto}") private String sitemapProto; public void makeSitemap(boolean submit) { String sitemapDef = env.getProperty("sitemaps"); Iterable<String> sitemapItems = Splitter.on(',') .trimResults() .omitEmptyStrings() .split(sitemapDef); for(String item: sitemapItems) { Iterable<String> sitemapProps = Splitter.on(':').trimResults().split(item); String siteName = Iterables.getFirst(sitemapProps, null); String sitemapPath = Iterables.getLast(sitemapProps); Site site = siteService.findByName(siteName); makeSiteSitemap(site, sitemapPath, submit); } } private void makeSiteSitemap(Site site, String sitemapPath, boolean submit) { List<WebSitemapUrl> urls = makeUrlList(site); String siteUrl = sitemapProto + "://" + site.getDomain() + "/"; W3CDateFormat dateFormat = new W3CDateFormat(Pattern.DAY); dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); WebSitemapGenerator wsg = null; try { File path = new File(sitemapPath); wsg = WebSitemapGenerator.builder(siteUrl, path).dateFormat(dateFormat).build(); } catch (MalformedURLException e) { logger.error("Bad site url: " + e); } assert wsg != null; wsg.addUrls(urls); wsg.write(); if (submit) { try { submitSitemap(site); } catch (IOException e) { logger.error("Error submitting sitemap:" + e); } } } private List<WebSitemapUrl> makeUrlList(Site site) { logger.debug("make sitemap for " + site.getDomain()); List<WebSitemapUrl> urls = new ArrayList<>(); String domainName = site.getDomain(); PageTree tree = pageTreeService.getPublicPageTree(site); String url = sitemapProto + "://" + domainName + "/"; WebSitemapUrl homeUrl = null; try { homeUrl = new WebSitemapUrl.Options(url) .lastMod(new Date()).priority(1.0).changeFreq(ChangeFreq.DAILY).build(); urls.add(homeUrl); } catch (MalformedURLException e1) { logger.error("Error generating hoem page url: " + e1); } List<GenericTreeNode<Page>> nodes = tree.listAllChildren(); for(GenericTreeNode<Page> node: nodes) { Page p = node.getData(); if (p.getName().equalsIgnoreCase(site.getHomePage())) { continue; } if (p.isPlaceholder()) continue; WebSitemapUrl u = null; try { u = makeSitemapUrl(p, site); urls.add(u); } catch (MalformedURLException e) { logger.error("Bad url: " + e); } } return urls; } private WebSitemapUrl makeSitemapUrl(Page p, Site s) throws MalformedURLException { boolean useHtml5Urls = env.getProperty("useHtml5Urls", Boolean.class); String m = "/#!/pv/"; if (useHtml5Urls) m = "/pv/"; String u = sitemapProto + "://" + s.getDomain() + m + p.getName(); return new WebSitemapUrl.Options(u) .lastMod(p.getUpdated().toDate()).priority(0.9).changeFreq(ChangeFreq.WEEKLY).build(); } public void onlyUpdateSitemap() { makeSitemap(false); } public void updateSitemap() { makeSitemap(true); } @Scheduled(cron="0 30 4 * * MON-FRI") public void scheduledUpdate() { Boolean submit = env.getProperty("sitemapSubmit", Boolean.class); makeSitemap(submit); } public void submitSitemap(Site site) throws IOException { String sitemapLocation = sitemapProto + "://" + site.getDomain() + env.getProperty("sitemapUrl"); String url = ""; try { url = env.getProperty("sitemapSubmitUrl") + "?sitemap=" + URLEncoder.encode(sitemapLocation, "ISO-8859-1"); } catch (UnsupportedEncodingException e1) { logger.error("UnsupportedEncodingException: " + e1); } logger.debug("URL: " + url); try (CloseableHttpClient httpclient = HttpClients.createDefault()) { HttpGet httpget = new HttpGet(url); // Execute the method. HttpResponse response = httpclient.execute(httpget); if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { logger.error("Submit failed: " + response.getStatusLine()); } // Get hold of the response entity HttpEntity entity = response.getEntity(); String responseStr = EntityUtils.toString(entity); logger.debug("Sitemap submit detailed response: " + responseStr); logger.info("Sitemap submitted to " + url); } catch (IOException e) { logger.error("Fatal IO error: " + e.getMessage()); } catch (ParseException e) { logger.error("Fatal parse error: " + e.getMessage()); } // Release the connection. } }