diff --git a/build.gradle b/build.gradle index 23c6e1d..2db4684 100644 --- a/build.gradle +++ b/build.gradle @@ -13,17 +13,7 @@ repositories { } dependencies { - compile 'io.javaslang:javaslang:2.0.2' + compile group: 'io.javaslang', name: 'javaslang', version: '2.0.5' compile 'org.jsoup:jsoup:1.10.2' - - compile 'org.slf4j:slf4j-api:1.6.4' - compile 'ch.qos.logback:logback-classic:1.0.1' - compile 'ch.qos.logback:logback-core:1.0.1' - - compileOnly "org.projectlombok:lombok:1.16.10" - - testCompile 'org.spockframework:spock-core:1.0-groovy-2.4' - testCompile 'org.codehaus.groovy:groovy:2.4.7' - testCompile 'cglib:cglib:3.2.2' } diff --git a/src/main/java/com/softwaremill/java_fp_example/FacebookImageVersion0.java b/src/main/java/com/softwaremill/java_fp_example/FacebookImageVersion0.java deleted file mode 100644 index f75d54d..0000000 --- a/src/main/java/com/softwaremill/java_fp_example/FacebookImageVersion0.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.softwaremill.java_fp_example; - -import javaslang.collection.List; -import lombok.extern.slf4j.Slf4j; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; - -import java.io.IOException; -import java.net.URL; - -import static com.softwaremill.java_fp_example.DefaultImage.DEFAULT_IMAGE; - -@Slf4j -public class FacebookImageVersion0 { - - private final static String FACEBOOK_IMAGE_TAG = "og:image"; - private final static int TEN_SECONDS = 10_000; - - public static String extractImageAddressFrom(String pageUrl) { - Document document; - try { - document = Jsoup.parse(new URL(pageUrl), TEN_SECONDS); - } catch (IOException e) { - log.error("Unable to extract og:image from url {}. Problem: {}", pageUrl, e.getMessage()); - return DEFAULT_IMAGE; - } - List ogImages = List - .ofAll(document.head().getElementsByTag("meta")) - .filter(e -> FACEBOOK_IMAGE_TAG.equals(e.attr("property"))); - if (ogImages.isEmpty()) { - log.warn("No {} found for blog post {}", FACEBOOK_IMAGE_TAG, pageUrl); - return DEFAULT_IMAGE; - } - return ogImages.get(0).attr("content"); - } - -} diff --git a/src/main/java/com/softwaremill/java_fp_example/FacebookImageVersion1MoreObjectOriented.java b/src/main/java/com/softwaremill/java_fp_example/FacebookImageVersion1MoreObjectOriented.java deleted file mode 100644 index 87e28e6..0000000 --- a/src/main/java/com/softwaremill/java_fp_example/FacebookImageVersion1MoreObjectOriented.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.softwaremill.java_fp_example; - -import javaslang.collection.List; -import lombok.extern.slf4j.Slf4j; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; - -import java.io.IOException; -import java.net.URL; - -import static com.softwaremill.java_fp_example.DefaultImage.DEFAULT_IMAGE; - -@Slf4j -public class FacebookImageVersion1MoreObjectOriented { - - private final static String FACEBOOK_IMAGE_TAG = "og:image"; - private final static int TEN_SECONDS = 10_000; - - private final String url; - - public FacebookImageVersion1MoreObjectOriented(String pageUrl) { - Document document; - try { - document = Jsoup.parse(new URL(pageUrl), TEN_SECONDS); - } catch (IOException e) { - log.error("Unable to extract og:image from url {}. Problem: {}", pageUrl, e.getMessage()); - url = DEFAULT_IMAGE; - return; - } - List ogImages = List - .ofAll(document.head().getElementsByTag("meta")) - .filter(e -> FACEBOOK_IMAGE_TAG.equals(e.attr("property"))); - if (ogImages.isEmpty()) { - log.warn("No {} found for blog post {}", FACEBOOK_IMAGE_TAG, pageUrl); - url = DEFAULT_IMAGE; - } else { - url = ogImages.get(0).attr("content"); - } - - } - - public String getUrl() { - return url; - } - -} diff --git a/src/main/java/com/softwaremill/java_fp_example/FacebookImageVersion2Javaslang.java b/src/main/java/com/softwaremill/java_fp_example/FacebookImageVersion2Javaslang.java deleted file mode 100644 index 84b6de8..0000000 --- a/src/main/java/com/softwaremill/java_fp_example/FacebookImageVersion2Javaslang.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.softwaremill.java_fp_example; - -import javaslang.collection.List; -import javaslang.control.Try; -import lombok.extern.slf4j.Slf4j; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; - -import java.net.URL; - -import static com.softwaremill.java_fp_example.DefaultImage.DEFAULT_IMAGE; - -@Slf4j -public class FacebookImageVersion2Javaslang { - - private final static String FACEBOOK_IMAGE_TAG = "og:image"; - private final static int TEN_SECONDS = 10_000; - - private final String url; - - public FacebookImageVersion2Javaslang(String pageUrl) { - Try imageTry = Try.of(() -> { - Document document = Jsoup.parse(new URL(pageUrl), TEN_SECONDS); - List ogImages = List.ofAll(document.head().getElementsByTag("meta")) - .filter(e -> FACEBOOK_IMAGE_TAG.equals(e.attr("property"))); - if (ogImages.isEmpty()) { - log.warn("No {} found for blog post {}", FACEBOOK_IMAGE_TAG, pageUrl); - return DEFAULT_IMAGE; - } else { - return ogImages.get(0).attr("content"); - } - }); - - url = imageTry - .onFailure(error -> log.error("Unable to extract og:image from url {}. Problem: {}", pageUrl, error.getMessage())) - .getOrElse(DEFAULT_IMAGE); - } - - public String getUrl() { - return url; - } - -} diff --git a/src/main/java/com/softwaremill/java_fp_example/FacebookImageVersion3BetterJavaslang.java b/src/main/java/com/softwaremill/java_fp_example/FacebookImageVersion3BetterJavaslang.java deleted file mode 100644 index b620102..0000000 --- a/src/main/java/com/softwaremill/java_fp_example/FacebookImageVersion3BetterJavaslang.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.softwaremill.java_fp_example; - -import javaslang.collection.List; -import javaslang.control.Try; -import javaslang.control.Try.CheckedFunction; -import javaslang.control.Try.CheckedSupplier; -import lombok.extern.slf4j.Slf4j; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; - -import java.net.URL; -import java.util.function.Consumer; -import java.util.function.Function; - -import static com.softwaremill.java_fp_example.DefaultImage.DEFAULT_IMAGE; - -@Slf4j -public class FacebookImageVersion3BetterJavaslang { - - private final static String FACEBOOK_IMAGE_TAG = "og:image"; - private final static int TEN_SECONDS = 10_000; - - private final String url; - - public FacebookImageVersion3BetterJavaslang(String pageUrl) { - - CheckedSupplier parseDocument = () -> Jsoup.parse(new URL(pageUrl), TEN_SECONDS); - CheckedFunction> findElementsWithPropertyTag = - document -> List.ofAll(document.head().getElementsByTag("meta")); - CheckedFunction, List> findElementsWithFacebookImageProperty = - elements -> elements.filter(e -> FACEBOOK_IMAGE_TAG.equals(e.attr("property"))); - Consumer> warnIfEmpty = elements -> { - if (elements.isEmpty()) { - log.warn("No {} found for blog post {}", FACEBOOK_IMAGE_TAG, pageUrl); - } - }; - CheckedFunction, Element> findFirst = elements -> elements.get(0); - Function content = getContentValue -> getContentValue.attr("content"); - - url = Try.of(parseDocument) - .mapTry(findElementsWithPropertyTag) - .mapTry(findElementsWithFacebookImageProperty) - .peek(warnIfEmpty) - .mapTry(findFirst) - .toOption() - .map(content) - .getOrElse(DEFAULT_IMAGE); - } - - public String getUrl() { - return url; - } - -} diff --git a/src/main/java/com/softwaremill/java_fp_example/contest/wkromolicki/FacebookImage.java b/src/main/java/com/softwaremill/java_fp_example/contest/wkromolicki/FacebookImage.java new file mode 100644 index 0000000..999153d --- /dev/null +++ b/src/main/java/com/softwaremill/java_fp_example/contest/wkromolicki/FacebookImage.java @@ -0,0 +1,36 @@ +package com.softwaremill.java_fp_example.contest.wkromolicki; + +import static com.softwaremill.java_fp_example.DefaultImage.DEFAULT_IMAGE; +import static com.softwaremill.java_fp_example.contest.wkromolicki.ParserFunctions.*; + +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.jsoup.nodes.Element; + +import javaslang.control.Try; + +public class FacebookImage { + + private final static Predicate FACEBOOK_IMAGE_TAG_FILTER = el -> "og:image".equals(el.attr("property")); + + public static String extractImage(String pageUrl) { + return findImage.apply(FACEBOOK_IMAGE_TAG_FILTER) + .apply(pageUrl) + .onFailure(handleExceptionFor(pageUrl)) + .getOrElse(DEFAULT_IMAGE); + } + + private static Function, Function>> findImage = + filter -> fetchUrl.andThen(lift(parseUrl)).andThen(lift(findMetaTag.apply(filter))).andThen(lift(extractContent)); + + + private static Consumer handleExceptionFor(String pageUrl) { + return e -> log(String.format("Unable to extract og:image from url %s. Problem: %s", pageUrl, e.getMessage())); + } + + private static void log(String message) { + System.out.println(message); + } +} diff --git a/src/main/java/com/softwaremill/java_fp_example/contest/wkromolicki/FacebookImageVersion.java b/src/main/java/com/softwaremill/java_fp_example/contest/wkromolicki/FacebookImageVersion.java new file mode 100644 index 0000000..6698066 --- /dev/null +++ b/src/main/java/com/softwaremill/java_fp_example/contest/wkromolicki/FacebookImageVersion.java @@ -0,0 +1,53 @@ +package com.softwaremill.java_fp_example.contest.wkromolicki; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Scanner; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.GZIPInputStream; + +public class FacebookImageVersion { + + public static final String DEFAULT_IMAGE = "https://softwaremill.com/images/logo-vertical.023d8496.png"; + private static final Pattern FACEBOOK_IMAGE_PATTERN = Pattern.compile(".*?(]*?property=\"og:image\".*?\\/?>).*?<\\/head>", Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE); + private static final Pattern TAG_CONTENT_PATTERN = Pattern.compile("content=\"(.+?)\"", Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE); + private static final int TEN_SECONDS = 10_000; + private static final Logger log = Logger.getLogger(FacebookImageVersion.class.getName()); + + public static String extractImageAddressFrom(String pageUrl) { + HttpURLConnection c = null; + try { + c = (HttpURLConnection) new URL(pageUrl).openConnection(); + } catch (IOException e) { + log.severe("Cannot open connection to " + pageUrl + " - reason: " + e.toString()); + return DEFAULT_IMAGE; + } + c.setConnectTimeout(TEN_SECONDS); + c.setReadTimeout(TEN_SECONDS); + String contentEncoding = c.getHeaderField("Content-Encoding"); + String html = null; + try (InputStream is = contentEncoding != null && contentEncoding.contains("gzip") ? new GZIPInputStream(c.getInputStream()) : c.getInputStream(); + Scanner s = new Scanner(is, StandardCharsets.UTF_8.name())) { + html = s.useDelimiter("\\A").next(); + } catch (IOException e) { + log.severe("Cannot read from " + pageUrl + " - reason: " + e.toString()); + return DEFAULT_IMAGE; + } + try { + Matcher tagMatch = FACEBOOK_IMAGE_PATTERN.matcher(html); + tagMatch.find(); + Matcher contentMatch = TAG_CONTENT_PATTERN.matcher(tagMatch.group(1)); + contentMatch.find(); + return contentMatch.group(1); + } catch (Exception e) { + log.warning("No og:image found for blog post " + pageUrl); + return DEFAULT_IMAGE; + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/softwaremill/java_fp_example/contest/your_github_login/LICENSE-template.txt b/src/main/java/com/softwaremill/java_fp_example/contest/wkromolicki/LICENSE.txt similarity index 95% rename from src/main/java/com/softwaremill/java_fp_example/contest/your_github_login/LICENSE-template.txt rename to src/main/java/com/softwaremill/java_fp_example/contest/wkromolicki/LICENSE.txt index df01d55..e901b08 100644 --- a/src/main/java/com/softwaremill/java_fp_example/contest/your_github_login/LICENSE-template.txt +++ b/src/main/java/com/softwaremill/java_fp_example/contest/wkromolicki/LICENSE.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2017 +Copyright (c) 2017 wkromolicki Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/main/java/com/softwaremill/java_fp_example/contest/wkromolicki/ParserFunctions.java b/src/main/java/com/softwaremill/java_fp_example/contest/wkromolicki/ParserFunctions.java new file mode 100644 index 0000000..f808819 --- /dev/null +++ b/src/main/java/com/softwaremill/java_fp_example/contest/wkromolicki/ParserFunctions.java @@ -0,0 +1,38 @@ +package com.softwaremill.java_fp_example.contest.wkromolicki; + +import java.net.URL; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; + +import javaslang.collection.List; +import javaslang.control.Try; + +public class ParserFunctions { + public final static int TIMEOUT = 10_000; + + public static Function, Function>> findMetaTag = + tagFilter -> document -> List.ofAll(document.head().getElementsByTag("meta")).filter(tagFilter).headOption().toTry(); + + + public static Function> extractContent = safe(el -> el.attr("content")); + + public static Function> fetchUrl = safe(URL::new); + + public static Function> parseUrl = safe(url -> Jsoup.parse(url, TIMEOUT)); + + + //below are helper methods which do not belong to parser domain and should be in some library + public static Function, Try> lift(Function> func) { + return in -> in.flatMap(func::apply); + } + + public static Function> safe(Try.CheckedFunction func) { + return a -> Try.of(() -> func.apply(a)); + } + + +} diff --git a/src/main/java/com/softwaremill/java_fp_example/contest/your_github_login/Readme.txt b/src/main/java/com/softwaremill/java_fp_example/contest/wkromolicki/Readme.txt similarity index 100% rename from src/main/java/com/softwaremill/java_fp_example/contest/your_github_login/Readme.txt rename to src/main/java/com/softwaremill/java_fp_example/contest/wkromolicki/Readme.txt diff --git a/src/test/groovy/com/softwaremill/java_fp_example/FacebookImageSpec.groovy b/src/test/groovy/com/softwaremill/java_fp_example/FacebookImageSpec.groovy deleted file mode 100644 index 8e8be37..0000000 --- a/src/test/groovy/com/softwaremill/java_fp_example/FacebookImageSpec.groovy +++ /dev/null @@ -1,78 +0,0 @@ -package com.softwaremill.java_fp_example - -import spock.lang.Specification -import spock.lang.Unroll - -import static com.softwaremill.java_fp_example.DefaultImage.DEFAULT_IMAGE - - -class FacebookImageSpec extends Specification { - - @Unroll - def "should test Initial Version with address #postAddress"() { - given: - FacebookImageVersion0 facebookImage = new FacebookImageVersion0() - - when: - String imageAddress = facebookImage.extractImageAddressFrom(postAddress) - - then: - imageAddress == expectedImageUrl - - where: - postAddress || expectedImageUrl - "https://softwaremill.com/the-wrong-abstraction-recap/" || "https://softwaremill.com/images/uploads/2017/02/street-shoe-chewing-gum.0526d557.jpg" - "https://softwaremill.com/using-kafka-as-a-message-queue/" || "https://softwaremill.com/images/uploads/2017/02/kmq.93f842cf.png" - "https://twitter.com/softwaremill" || DEFAULT_IMAGE - "http://i-do-not-exist.pl" || DEFAULT_IMAGE - } - - @Unroll - def "should test More Object Oriented version with address #postAddress"() { - when: - FacebookImageVersion1MoreObjectOriented facebookImage = new FacebookImageVersion1MoreObjectOriented(postAddress) - - then: - facebookImage.getUrl() == expectedImageUrl - - where: - postAddress || expectedImageUrl - "https://softwaremill.com/the-wrong-abstraction-recap/" || "https://softwaremill.com/images/uploads/2017/02/street-shoe-chewing-gum.0526d557.jpg" - "https://softwaremill.com/using-kafka-as-a-message-queue/" || "https://softwaremill.com/images/uploads/2017/02/kmq.93f842cf.png" - "https://twitter.com/softwaremill" || DEFAULT_IMAGE - "http://i-do-not-exist.pl" || DEFAULT_IMAGE - } - - @Unroll - def "should test Javaslang version with address #postAddress"() { - when: - FacebookImageVersion2Javaslang facebookImage = new FacebookImageVersion2Javaslang(postAddress) - - then: - facebookImage.getUrl() == expectedImageUrl - - where: - postAddress || expectedImageUrl - "https://softwaremill.com/the-wrong-abstraction-recap/" || "https://softwaremill.com/images/uploads/2017/02/street-shoe-chewing-gum.0526d557.jpg" - "https://softwaremill.com/using-kafka-as-a-message-queue/" || "https://softwaremill.com/images/uploads/2017/02/kmq.93f842cf.png" - "https://twitter.com/softwaremill" || DEFAULT_IMAGE - "http://i-do-not-exist.pl" || DEFAULT_IMAGE - } - - @Unroll - def "should test Better Javaslang version with address #postAddress"() { - when: - FacebookImageVersion2Javaslang facebookImage = new FacebookImageVersion2Javaslang(postAddress) - - then: - facebookImage.getUrl() == expectedImageUrl - - where: - postAddress || expectedImageUrl - "https://softwaremill.com/the-wrong-abstraction-recap/" || "https://softwaremill.com/images/uploads/2017/02/street-shoe-chewing-gum.0526d557.jpg" - "https://softwaremill.com/using-kafka-as-a-message-queue/" || "https://softwaremill.com/images/uploads/2017/02/kmq.93f842cf.png" - "https://twitter.com/softwaremill" || DEFAULT_IMAGE - "http://i-do-not-exist.pl" || DEFAULT_IMAGE - } - -}