/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.jooby.sitemap;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import org.jooby.Route;
import org.jooby.internal.sitemap.JSitemap;
import cz.jiripinkas.jsitemapgenerator.WebPage;
import cz.jiripinkas.jsitemapgenerator.generator.SitemapGenerator;
import javaslang.Function1;
/**
* <h1>sitemap</h1>
* <p>
* Generate <a href="https://en.wikipedia.org/wiki/Sitemaps">sitemap.xml</a> files using
* <a href="https://github.com/jirkapinkas/jsitemapgenerator">jsitemapgenerator</a>.
* </p>
*
* <h2>usage</h2>
* <pre>{@code
* {
* use(new Sitemap());
*
* get("/page1", ..);
* get("/page2", ..);
* }
* }</pre>
*
* <p>
* The module exports a <code>/sitemap.xml</code> route.
* </p>
*
* <h2>baseurl</h2>
* <p>
* The <code>sitemap.xml</code> specification requires an absolute url. The way we provide this
* absolute url is at creation time or using the <code>sitemap.url</code> property:
* </p>
* <pre>{@code
* {
* use(new Sitemap("https://foo.bar"));
*
* get("/page1", ..);
* get("/page2", ..);
* }
* }</pre>
*
* or
*
* <pre>
* sitemap.url = "http://foo.bar"
* </pre>
*
* <h2>customize</h2>
* <p>
* The sitemap generator builds a <code>sitemap.xml</code> file with <code>loc</code> elements. You
* can customize the output in one of two ways:
* </p>
*
* <h3>declarative</h3>
*
* <pre>
* {
*
* get("/")
* .get("/page1", ..)
* .get("/page2", ..)
* .attr("changefreq", "weekly")
* .attr("priority", "1");
* }
* </pre>
*
* <p>
* We first group route under a common path: <code>/</code> and add some routers. Then for each
* router we set the <code>changefrequency</code> and <code>priority</code>.
* </p>
*
* <h3>programmatically</h3>
*
* <pre>{@code
* {
*
* use(new Sitemap().with(r -> {
* WebPage page = new WebPage();
* page.setName(r.pattern());
* page.setChangeFreq(ChangeFreq.ALWAYS);
* page.setPriority(1);
* return Arrays.asList(page);
* }));
*
* get("/")
* .get("/page1", ..)
* .get("/page2", ..);
* }
* }</pre>
*
* <p>
* Here we built {@link WebPage} objects and set frequency and priority.
* </p>
*
* <h2>dynamic page generation</h2>
* <p>
* Suppose you have a <strong>product</strong> route dynamically mapped as:
* </p>
*
* <pre>{@code
* {
* get("/products/:sku", ...);
* }
* }</pre>
*
* <h3>
* How do you generate urls for all your products?
* </h3>
*
* <p>
* Dynamic urls are supported via custom {@link WebPageProvider}:
* </p>
*
* <pre>{@code
* {
* use(new Sitemap().with(SKUPageProvider.class));
*
* get("/products/:sku", ...);
* }
* }</pre>
*
* SKUPageProvider.java:
* <pre>{@code
* public class SKUPageProvider implements WebPageProvider {
*
* private MyDatabase db;
*
* @Inject
* public SKUPageProvider(MyDatabase db) {
* this.db = db;
* }
*
* public List<WebPage> apply(Route.Definition route) {
* if (route.pattern().startsWith("/products")) {
* // multiple urls
* return db.findSKUS().stream().map(sku -> {
* WebPage webpage = new WebPage();
* webpage.setName(route.reverse(sku));
* return webpage;
* }).collect(Collectors.toList());
* }
* // single url
* WebPage webpage = new WebPage();
* webpage.setName(route.pattern());
* return Arrays.asList(webpage);
* }
* }
* }</pre>
*
* <h2>filter</h2>
* <p>
* The {@link #filter(java.util.function.Predicate)} option allows to excludes routes from final
* output:
* </p>
*
* <pre>{@code
* {
* use(new Sitemap().filter(route -> !route.pattern().startsWith("/api")));
* }
* }</pre>
*
* @author edgar
* @since 1.0.0.CR
*/
public class Sitemap extends JSitemap<Sitemap> {
/**
* Creates a new {@link Sitemap}.
*/
public Sitemap() {
this(Optional.empty());
}
/**
* Creates a new {@link Sitemap} and set the base url.
*
* @param baseurl Base url to use.
*/
public Sitemap(final String baseurl) {
this(Optional.of(baseurl));
}
private Sitemap(final Optional<String> baseurl) {
super(SITEMAP, baseurl, WebPageProvider.SITEMAP);
}
/**
* <h2>filter</h2>
* <p>
* The {@link #filter(java.util.function.Predicate)} option allows to excludes routes from final
* output:
* </p>
*
* <pre>{@code
* {
* use(new Sitemap().filter(route -> !route.pattern().startsWith("/api")));
* }
* }</pre>
*
*/
@Override
public Sitemap filter(final Predicate<Route.Definition> filter) {
return super.filter(filter);
}
/**
* Set a custom {@link WebPageProvider}.
*
* <pre>{@code
* {
*
* use(new Sitemap().with(r -> {
* WebPage page = new WebPage();
* page.setName(r.pattern());
* page.setChangeFreq(ChangeFreq.ALWAYS);
* page.setPriority(1);
* return Arrays.asList(page);
* }));
* }
* }</pre>
*
* @param wpp A web page provider.
*/
@Override
public Sitemap with(final WebPageProvider wpp) {
return super.with(wpp);
}
/**
* Set a custom {@link WebPageProvider}.
*
* <pre>{@code
* {
* use(new Sitemap().with(MyWebPageProvider.class));
* }
* }</pre>
*
* The <code>MyWebPageProvider</code> will be created and injected by Guice.
*
* @param wpp A web page provider.
*/
@Override
public Sitemap with(final Class<? extends WebPageProvider> wpp) {
return super.with(wpp);
}
@Override
protected Function1<List<WebPage>, String> gen(final String baseurl) {
return pages -> {
SitemapGenerator generator = new SitemapGenerator(baseurl);
generator.addPages(pages);
return generator.constructSitemapString();
};
}
}