blob: 0699b77126ca481ac980caa7c868b20902dde9ab [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2013 Oracle and/or its affiliates. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
*
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
******************************************************************************/
package eclipselink.example.moxy.dynamic.flickr;
import java.awt.Desktop;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.StringTokenizer;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.stream.StreamSource;
import org.eclipse.persistence.dynamic.DynamicEntity;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
import org.eclipse.persistence.jaxb.MarshallerProperties;
import org.eclipse.persistence.jaxb.UnmarshallerProperties;
import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContext;
import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContextFactory;
import org.eclipse.persistence.oxm.MediaType;
import com.likethecolor.alchemy.api.Client;
import com.likethecolor.alchemy.api.call.RankedConceptsCall;
import com.likethecolor.alchemy.api.call.type.CallTypeText;
import com.likethecolor.alchemy.api.entity.ConceptAlchemyEntity;
import com.likethecolor.alchemy.api.entity.Response;
import com.likethecolor.alchemy.api.params.ConceptParams;
public class Main {
// The JAXBContext that we will use to read Reddit / Flickr data, and write HTML
private DynamicJAXBContext context;
// RedditPost:url -> RedditPost
HashMap<Object, DynamicEntity> redditMap;
// RedditPost:url -> FlickrResults
HashMap<Object, DynamicEntity> flickrMap;
File outputFile;
// ========================================================================
public static void main(String[] args) {
new Main().run();
}
private void run() {
try {
init();
readRedditPosts();
findFlickrResults();
writeHtml();
launchSystemBrowser();
} catch (Exception e) {
e.printStackTrace();
}
}
private void init() throws Exception {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
InputStream redditBindings = loader.getResourceAsStream(REDDIT_BINDINGS);
InputStream flickrBindings = loader.getResourceAsStream(FLICKR_BINDINGS);
InputStream htmlBindings = loader.getResourceAsStream(HTML_BINDINGS);
ArrayList<InputStream> dataBindings = new ArrayList<InputStream>(3);
dataBindings.add(redditBindings);
dataBindings.add(flickrBindings);
dataBindings.add(htmlBindings);
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, dataBindings);
context = DynamicJAXBContextFactory.createContextFromOXM(loader, properties);
redditMap = new HashMap<Object, DynamicEntity>();
flickrMap = new HashMap<Object, DynamicEntity>();
outputFile = new File("output.html");
}
private void readRedditPosts() throws Exception {
Unmarshaller u = context.createUnmarshaller();
u.setProperty(UnmarshallerProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON);
u.setProperty(UnmarshallerProperties.JSON_INCLUDE_ROOT, false);
Class<? extends DynamicEntity> redditResultsClass = context.newDynamicEntity("eclipselink.example.moxy.dynamic.flickr.RedditResults").getClass();
System.out.println();
System.out.print("Reading Today's Hot Topics from Reddit r/" + SUBREDDIT + "... ");
DynamicEntity redditResults = u.unmarshal(new StreamSource(REDDIT_URL), redditResultsClass).getValue();
System.out.println("Done.");
ArrayList<DynamicEntity> posts = redditResults.get("posts");
for (DynamicEntity post : posts) {
redditMap.put(post.get("url"), post);
}
}
private void findFlickrResults() throws Exception {
for (Object postUrl : redditMap.keySet()) {
DynamicEntity post = redditMap.get(postUrl);
System.out.println();
System.out.println("Headline: [" + post.get("title") + "]");
String keywords = extractConcepts(post.get("title").toString());
String flickrUrlString = FLICKR_URL + keywords;
System.out.print("Searching Flickr: [" + flickrUrlString + "]... ");
InputStream flickrStream = new URL(flickrUrlString).openConnection().getInputStream();
System.out.println("Done.");
Unmarshaller u = context.createUnmarshaller();
DynamicEntity flickrResults = (DynamicEntity) u.unmarshal(flickrStream);
flickrMap.put(postUrl, flickrResults);
ArrayList<DynamicEntity> flickerItems = flickrResults.get("items");
if (flickerItems != null) {
int size = flickerItems.size();
if (size >= IMAGE_LIMIT) {
size = IMAGE_LIMIT;
}
for (int i = 0; i < size; i++) {
System.out.println("\t" + flickerItems.get(i).get("imageUrl"));
}
} else {
System.out.println("\tNo results found.");
}
}
}
private void writeHtml() throws Exception {
DynamicEntity html = context.newDynamicEntity("eclipselink.example.moxy.dynamic.flickr.HtmlPage");
html.set("title", "EclipseLink MOXy - Dynamic JAXB");
html.set("css", "style.css");
html.set("rel", "stylesheet");
html.set("type", "text/css");
html.set("media", "screen");
DynamicEntity body = context.newDynamicEntity("eclipselink.example.moxy.dynamic.flickr.HtmlBody");
body.set("title", "Reddit /" + SUBREDDIT + " - Today's Top Posts");
ArrayList<DynamicEntity> divs = new ArrayList<DynamicEntity>();
for (Object postUrl : redditMap.keySet()) {
DynamicEntity post = redditMap.get(postUrl);
DynamicEntity flickrResults = flickrMap.get(postUrl);
// Article Link
// ================================================================
DynamicEntity redditDiv = context.newDynamicEntity("eclipselink.example.moxy.dynamic.flickr.HtmlDiv");
redditDiv.set("id", "redditPost");
ArrayList<DynamicEntity> divContent = new ArrayList<DynamicEntity>();
DynamicEntity redditLink = context.newDynamicEntity("eclipselink.example.moxy.dynamic.flickr.HtmlTextLink");
redditLink.set("url", post.get("url"));
redditLink.set("title", post.get("title"));
divContent.add(redditLink);
redditDiv.set("span", divContent);
// Flickr description and images
// ================================================================
DynamicEntity flickrDiv = context.newDynamicEntity("eclipselink.example.moxy.dynamic.flickr.HtmlDiv");
flickrDiv.set("id", "flickrResults");
divContent = new ArrayList<DynamicEntity>();
DynamicEntity flickrDescription = context.newDynamicEntity("eclipselink.example.moxy.dynamic.flickr.HtmlText");
flickrDescription.set("text", flickrResults.get("description"));
divContent.add(flickrDescription);
ArrayList<DynamicEntity> flickrItems = flickrResults.get("items");
int counter = 0;
if (flickrItems != null) {
Collections.shuffle(flickrItems, new Random(System.nanoTime()));
for (DynamicEntity flickrItem : flickrItems) {
DynamicEntity flickrImageLink = context.newDynamicEntity("eclipselink.example.moxy.dynamic.flickr.HtmlImageLink");
flickrImageLink.set("url", flickrItem.get("flickrPage"));
flickrImageLink.set("image", flickrItem.get("imageUrl"));
flickrImageLink.set("height", HTML_IMAGE_HEIGHT);
divContent.add(flickrImageLink);
counter++;
if (counter == IMAGE_LIMIT) {
break;
}
}
} else {
DynamicEntity noneText = context.newDynamicEntity("eclipselink.example.moxy.dynamic.flickr.HtmlText");
noneText.set("text", "No results found.");
divContent.add(noneText);
}
flickrDiv.set("span", divContent);
divs.add(redditDiv);
divs.add(flickrDiv);
}
body.set("divs", divs);
html.set("body", body);
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
m.setProperty(Marshaller.JAXB_FRAGMENT, true);
m.marshal(html, outputFile);
}
// ========================================================================
/**
* This method uses the AlchemyAPI web service to determine the "concepts"
* contained in the headline. It does not use EclipseLink and is used solely for
* the purpose of this example application.
*
* @see <a href="http://www.alchemyapi.com">AlchemyAPI</a>
*/
@SuppressWarnings("unchecked")
private String extractConcepts(String postTitle) {
try {
Client client = new Client(ALCHEMY_KEY);
ConceptParams params = new ConceptParams();
params.setMaxRetrieve(ALCHEMY_CONCEPTS_TO_RETURN);
CallTypeText callType = new CallTypeText(postTitle);
RankedConceptsCall call = new RankedConceptsCall(callType, params);
Response<ConceptAlchemyEntity> response = client.call(call);
String concepts = "";
Iterator<ConceptAlchemyEntity> it = response.iterator();
if (!it.hasNext()) {
// If Alchemy didn't find any concepts, use string length algorithm
return extractKeywords(postTitle);
} else {
while (it.hasNext()) {
ConceptAlchemyEntity alchemyEntity = it.next();
concepts += alchemyEntity.getConcept();
if (it.hasNext()) {
concepts += ",";
}
}
}
concepts = concepts.replace(" ", "+");
return concepts;
} catch (Exception e) {
return extractKeywords(postTitle);
}
}
private String extractKeywords(String postTitle) {
StringTokenizer tokenizer = new StringTokenizer(postTitle, ",.!?()[]'\"- \t\n\r\f");
ArrayList<String> words = new ArrayList<String>();
while (tokenizer.hasMoreElements()) {
words.add(tokenizer.nextToken());
}
// Sort words, longest one first
Collections.sort(words, new StringLengthComparator());
String keywords = null;
if (words.size() > 1) {
keywords = words.get(0) + "," + words.get(1);
} else {
keywords = words.get(0);
}
return keywords;
}
private void launchSystemBrowser() {
try {
Desktop.getDesktop().browse(outputFile.toURI());
} catch (Exception e) {
e.printStackTrace();
}
}
private void writeJsonBindings() throws Exception {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
InputStream bindingsXml = loader.getResourceAsStream("META-INF/bindings-flickr.xml");
JAXBContext ctx = JAXBContext.newInstance("org.eclipse.persistence.jaxb.xmlmodel");
Object bindings = ctx.createUnmarshaller().unmarshal(bindingsXml);
Marshaller m = ctx.createMarshaller();
m.setProperty(MarshallerProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON);
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
m.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, false);
m.marshal(bindings, System.out);
}
// ========================================================================
public class StringLengthComparator implements Comparator<String> {
public int compare(String o1, String o2) {
if (o1.length() > o2.length()) {
return -1;
} else if (o1.length() < o2.length()) {
return 1;
} else {
return 0;
}
}
}
// ========================================================================
// See http://www.reddit.com/dev/api
private final int REDDIT_LIMIT = 5;
private final String SUBREDDIT = "technology";
private final String REDDIT_URL = "http://www.reddit.com/r/" + SUBREDDIT + "/top/.json?sort=top&t=today&limit=" + REDDIT_LIMIT;
// See http://www.flickr.com/services/feeds/
private final int IMAGE_LIMIT = 6;
private final String FLICKR_URL = "http://api.flickr.com/services/feeds/photos_public.gne?safe_search=1&tags=";
private final String REDDIT_BINDINGS = "META-INF/bindings-reddit.xml";
private final String FLICKR_BINDINGS = "META-INF/bindings-flickr.json";
private final String HTML_BINDINGS = "META-INF/bindings-html.xml";
private final int ALCHEMY_CONCEPTS_TO_RETURN = 2;
private final String ALCHEMY_KEY = "/META-INF/a.key";
private final Integer HTML_IMAGE_HEIGHT = Integer.valueOf(80);
}