diff --git a/src/main/java/com/redfin/sitemapgenerator/GoogleImageSitemapGenerator.java b/src/main/java/com/redfin/sitemapgenerator/GoogleImageSitemapGenerator.java new file mode 100644 index 0000000..6961a6a --- /dev/null +++ b/src/main/java/com/redfin/sitemapgenerator/GoogleImageSitemapGenerator.java @@ -0,0 +1,103 @@ +package com.redfin.sitemapgenerator; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; + +/** + * Builds a sitemap for Google Image search. To configure options use {@link #builder(URL, File)} + * @see Manage your sitemaps + * */ +public class GoogleImageSitemapGenerator extends SitemapGenerator { + + GoogleImageSitemapGenerator(AbstractSitemapGeneratorOptions options) { + super(options, new GoogleImageSitemapGenerator.Renderer()); + } + + /** Configures the generator with a base URL and directory to write the sitemap files. + * + * @param baseUrl All URLs in the generated sitemap(s) should appear under this base URL + * @param baseDir Sitemap files will be generated in this directory as either "sitemap.xml" or "sitemap1.xml" "sitemap2.xml" and so on. + * @throws MalformedURLException + */ + public GoogleImageSitemapGenerator(String baseUrl, File baseDir) + throws MalformedURLException { + this(new SitemapGeneratorOptions(baseUrl, baseDir)); + } + + /**Configures the generator with a base URL and directory to write the sitemap files. + * + * @param baseUrl All URLs in the generated sitemap(s) should appear under this base URL + * @param baseDir Sitemap files will be generated in this directory as either "sitemap.xml" or "sitemap1.xml" "sitemap2.xml" and so on. + */ + public GoogleImageSitemapGenerator(URL baseUrl, File baseDir) { + this(new SitemapGeneratorOptions(baseUrl, baseDir)); + } + + /**Configures the generator with a base URL and a null directory. The object constructed + * is not intended to be used to write to files. Rather, it is intended to be used to obtain + * XML-formatted strings that represent sitemaps. + * + * @param baseUrl All URLs in the generated sitemap(s) should appear under this base URL + */ + public GoogleImageSitemapGenerator(String baseUrl) throws MalformedURLException { + this(new SitemapGeneratorOptions(new URL(baseUrl))); + } + + /**Configures the generator with a base URL and a null directory. The object constructed + * is not intended to be used to write to files. Rather, it is intended to be used to obtain + * XML-formatted strings that represent sitemaps. + * + * @param baseUrl All URLs in the generated sitemap(s) should appear under this base URL + */ + public GoogleImageSitemapGenerator(URL baseUrl) { + this(new SitemapGeneratorOptions(baseUrl)); + } + + /** Configures a builder so you can specify sitemap generator options + * + * @param baseUrl All URLs in the generated sitemap(s) should appear under this base URL + * @param baseDir Sitemap files will be generated in this directory as either "sitemap.xml" or "sitemap1.xml" "sitemap2.xml" and so on. + * @return a builder; call .build() on it to make a sitemap generator + */ + public static SitemapGeneratorBuilder builder(URL baseUrl, File baseDir) { + return new SitemapGeneratorBuilder(baseUrl, baseDir, GoogleImageSitemapGenerator.class); + } + + /** Configures a builder so you can specify sitemap generator options + * + * @param baseUrl All URLs in the generated sitemap(s) should appear under this base URL + * @param baseDir Sitemap files will be generated in this directory as either "sitemap.xml" or "sitemap1.xml" "sitemap2.xml" and so on. + * @return a builder; call .build() on it to make a sitemap generator + * @throws MalformedURLException + */ + public static SitemapGeneratorBuilder builder(String baseUrl, File baseDir) throws MalformedURLException { + return new SitemapGeneratorBuilder(baseUrl, baseDir, GoogleImageSitemapGenerator.class); + } + + private static class Renderer extends AbstractSitemapUrlRenderer implements ISitemapUrlRenderer { + + public Class getUrlClass() { + return GoogleImageSitemapUrl.class; + } + + public String getXmlNamespaces() { + return "xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\""; + } + + public void render(GoogleImageSitemapUrl url, StringBuilder sb, W3CDateFormat dateFormat) { + StringBuilder tagSb = new StringBuilder(); + + for(Image image : url.getImages()) { + tagSb.append(" \n"); + renderTag(tagSb, "image", "loc", image.getUrl()); + renderTag(tagSb, "image", "caption", image.getCaption()); + renderTag(tagSb, "image", "title", image.getTitle()); + renderTag(tagSb, "image", "geo_location", image.getGeoLocation()); + renderTag(tagSb, "image", "license", image.getLicense()); + tagSb.append(" \n"); + } + super.render(url, sb, dateFormat, tagSb.toString()); + } + } +} diff --git a/src/main/java/com/redfin/sitemapgenerator/GoogleImageSitemapUrl.java b/src/main/java/com/redfin/sitemapgenerator/GoogleImageSitemapUrl.java new file mode 100644 index 0000000..5e18c80 --- /dev/null +++ b/src/main/java/com/redfin/sitemapgenerator/GoogleImageSitemapUrl.java @@ -0,0 +1,74 @@ +package com.redfin.sitemapgenerator; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** One configurable Google Image Search URL. To configure, use {@link Options} + * + * @see Options + * @see Creating Image Sitemaps + */ +public class GoogleImageSitemapUrl extends WebSitemapUrl { + + private final List images; + + public GoogleImageSitemapUrl(String url) throws MalformedURLException { + this(new Options(url)); + } + + public GoogleImageSitemapUrl(URL url) { + this(new Options(url)); + } + + public GoogleImageSitemapUrl(Options options) { + super(options); + this.images = options.images; + } + + public void addImage(Image image) { + this.images.add(image); + if(this.images.size() > 1000) { + throw new RuntimeException("A URL cannot have more than 1000 image tags"); + } + } + + /** Options to configure Google Extension URLs */ + public static class Options extends AbstractSitemapUrlOptions { + private List images; + + + public Options(URL url) { + super(url, GoogleImageSitemapUrl.class); + images = new ArrayList(); + } + + public Options(String url) throws MalformedURLException { + super(url, GoogleImageSitemapUrl.class); + images = new ArrayList(); + } + + public Options images(List images) { + if(images != null && images.size() > 1000) { + throw new RuntimeException("A URL cannot have more than 1000 image tags"); + } + this.images = images; + return this; + } + + public Options images(Image...images) { + if(images.length > 1000) { + throw new RuntimeException("A URL cannot have more than 1000 image tags"); + } + return images(Arrays.asList(images)); + + } + } + + /**Retrieves list of images*/ + public List getImages() { + return this.images; + } +} diff --git a/src/main/java/com/redfin/sitemapgenerator/Image.java b/src/main/java/com/redfin/sitemapgenerator/Image.java new file mode 100644 index 0000000..d1773be --- /dev/null +++ b/src/main/java/com/redfin/sitemapgenerator/Image.java @@ -0,0 +1,100 @@ +package com.redfin.sitemapgenerator; + +import java.net.MalformedURLException; +import java.net.URL; + +/** + * Represent a single image and image properties for use in extended sitemaps + * @see Image sitemaps + */ +public class Image { + private final URL url; + private final String title; + private final String caption; + private final String geoLocation; + private final URL license; + + public Image(String url) throws MalformedURLException { + this(new URL(url)); + } + + public Image(URL url) { + this.url = url; + this.title = null; + this.caption = null; + this.geoLocation = null; + this.license = null; + } + + public Image(URL url, String title, String caption, String geoLocation, String license) throws MalformedURLException { + this(url, title, caption, geoLocation, new URL(license)); + } + + public Image(URL url, String title, String caption, String geoLocation, URL license) { + this.url = url; + this.title = title; + this.caption = caption; + this.geoLocation = geoLocation; + this.license = license; + } + + + /** Retrieves URL of Image*/ + public URL getUrl() { return url; } + + /** Retrieves title of image*/ + public String getTitle() { return title; } + + /** Retrieves captionof image*/ + public String getCaption() { return caption; } + + /** Retrieves geolocation string of image*/ + public String getGeoLocation() { return geoLocation; } + + /** Retrieves license string of image*/ + public URL getLicense() { return license; } + + public static class ImageBuilder { + private URL url; + private String title; + private String caption; + private String geoLocation; + private URL license; + + public ImageBuilder(String url) throws MalformedURLException { + this(new URL(url)); + } + + public ImageBuilder(URL url) { + this.url = url; + } + + public ImageBuilder title(String title) { + this.title = title; + return this; + } + + public ImageBuilder caption(String caption) { + this.caption = caption; + return this; + } + + public ImageBuilder geoLocation(String geoLocation) { + this.geoLocation = geoLocation; + return this; + } + + public ImageBuilder license(String license) throws MalformedURLException { + return license(new URL(license)); + } + + public ImageBuilder license(URL license) { + this.license = license; + return this; + } + + public Image build() { + return new Image(url, title, caption, geoLocation, license); + } + } +} diff --git a/src/test/java/com/redfin/sitemapgenerator/GoogleImageSitemapUrlTest.java b/src/test/java/com/redfin/sitemapgenerator/GoogleImageSitemapUrlTest.java new file mode 100644 index 0000000..d6f694f --- /dev/null +++ b/src/test/java/com/redfin/sitemapgenerator/GoogleImageSitemapUrlTest.java @@ -0,0 +1,165 @@ +package com.redfin.sitemapgenerator; + +import junit.framework.TestCase; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +public class GoogleImageSitemapUrlTest extends TestCase { + + private static final URL LANDING_URL = newURL("http://www.example.com/index.html"); + private static final URL CONTENT_URL = newURL("http://www.example.com/index.flv"); + File dir; + GoogleImageSitemapGenerator wsg; + + private static URL newURL(String url) { + try { + return new URL(url); + } catch (MalformedURLException e) {} + return null; + } + + public void setUp() throws Exception { + dir = File.createTempFile(GoogleVideoSitemapUrlTest.class.getSimpleName(), ""); + dir.delete(); + dir.mkdir(); + dir.deleteOnExit(); + } + + public void tearDown() { + wsg = null; + for (File file : dir.listFiles()) { + file.deleteOnExit(); + file.delete(); + } + dir.delete(); + dir = null; + } + + public void testSimpleUrl() throws Exception { + wsg = new GoogleImageSitemapGenerator("http://www.example.com", dir); + GoogleImageSitemapUrl url = new GoogleImageSitemapUrl(LANDING_URL); + url.addImage(new Image("http://cdn.example.com/image1.jpg")); + url.addImage(new Image("http://cdn.example.com/image2.jpg")); + wsg.addUrl(url); + String expected = "\n" + + "\n" + + " \n" + + " http://www.example.com/index.html\n" + + " \n" + + " http://cdn.example.com/image1.jpg\n" + + " \n" + + " \n" + + " http://cdn.example.com/image2.jpg\n" + + " \n" + + " \n" + + ""; + String sitemap = writeSingleSiteMap(wsg); + assertEquals(expected, sitemap); + } + + public void testBaseOptions() throws Exception { + wsg = new GoogleImageSitemapGenerator("http://www.example.com", dir); + GoogleImageSitemapUrl url = new GoogleImageSitemapUrl.Options(LANDING_URL) + .images(new Image("http://cdn.example.com/image1.jpg"), new Image("http://cdn.example.com/image2.jpg")) + .priority(0.5) + .changeFreq(ChangeFreq.WEEKLY) + .build(); + wsg.addUrl(url); + + String expected = "\n" + + "\n" + + " \n" + + " http://www.example.com/index.html\n" + + " weekly\n" + + " 0.5\n" + + " \n" + + " http://cdn.example.com/image1.jpg\n" + + " \n" + + " \n" + + " http://cdn.example.com/image2.jpg\n" + + " \n" + + " \n" + + ""; + + String sitemap = writeSingleSiteMap(wsg); + assertEquals(expected, sitemap); + } + + public void testImageOptions() throws Exception { + wsg = new GoogleImageSitemapGenerator("http://www.example.com", dir); + GoogleImageSitemapUrl url = new GoogleImageSitemapUrl.Options(LANDING_URL) + .images(new Image.ImageBuilder("http://cdn.example.com/image1.jpg") + .title("image1.jpg") + .caption("An image of the number 1") + .geoLocation("Pyongyang, North Korea") + .license("http://cdn.example.com/licenses/imagelicense.txt") + .build(), + new Image.ImageBuilder("http://cdn.example.com/image2.jpg") + .title("image2.jpg") + .caption("An image of the number 2") + .geoLocation("Pyongyang, North Korea") + .license("http://cdn.example.com/licenses/imagelicense.txt") + .build()) + .priority(0.5) + .changeFreq(ChangeFreq.WEEKLY) + .build(); + wsg.addUrl(url); + + String expected = "\n" + + "\n" + + " \n" + + " http://www.example.com/index.html\n" + + " weekly\n" + + " 0.5\n" + + " \n" + + " http://cdn.example.com/image1.jpg\n" + + " An image of the number 1\n" + + " image1.jpg\n" + + " Pyongyang, North Korea\n" + + " http://cdn.example.com/licenses/imagelicense.txt\n" + + " \n" + + " \n" + + " http://cdn.example.com/image2.jpg\n" + + " An image of the number 2\n" + + " image2.jpg\n" + + " Pyongyang, North Korea\n" + + " http://cdn.example.com/licenses/imagelicense.txt\n" + + " \n" + + " \n" + + ""; + + String sitemap = writeSingleSiteMap(wsg); + assertEquals(expected, sitemap); + } + + public void testTooManyImages() throws Exception { + wsg = new GoogleImageSitemapGenerator("http://www.example.com", dir); + List images = new ArrayList(); + for(int i = 0; i <= 1000; i++) { + images.add(new Image("http://cdn.example.com/image" + i + ".jpg")); + } + try { + GoogleImageSitemapUrl url = new GoogleImageSitemapUrl.Options(LANDING_URL) + .images(images) + .priority(0.5) + .changeFreq(ChangeFreq.WEEKLY) + .build(); + fail("Too many images allowed"); + } catch (RuntimeException r) {} + } + + + + private String writeSingleSiteMap(GoogleImageSitemapGenerator wsg) { + List files = wsg.write(); + assertEquals("Too many files: " + files.toString(), 1, files.size()); + assertEquals("Sitemap misnamed", "sitemap.xml", files.get(0).getName()); + return TestUtil.slurpFileAndDelete(files.get(0)); + } + +}