This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-hapi-client.git
commit 12be3328f0214fa66a6713f43aa93eeb68ee8984 Author: Bertrand Delacretaz <[email protected]> AuthorDate: Tue Oct 13 08:56:08 2015 +0000 SLING-5136 - HApi microdata client, contributed by Andrei Dulvac, thanks! git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1708306 13f79535-47bb-0310-9956-ffa450edef68 --- .gitignore | 15 + pom.xml | 97 +++++ .../apache/sling/hapi/client/ClientException.java | 32 ++ .../org/apache/sling/hapi/client/Document.java | 29 ++ .../org/apache/sling/hapi/client/HtmlClient.java | 72 ++++ .../java/org/apache/sling/hapi/client/Item.java | 82 +++++ .../java/org/apache/sling/hapi/client/Items.java | 35 ++ .../apache/sling/hapi/client/forms/FormValues.java | 116 ++++++ .../org/apache/sling/hapi/client/forms/Vals.java | 60 ++++ .../hapi/client/impl/AbstractHtmlClientImpl.java | 192 ++++++++++ .../hapi/client/microdata/MicrodataDocument.java | 399 +++++++++++++++++++++ .../hapi/client/microdata/MicrodataHtmlClient.java | 44 +++ src/test/java/FormTest.java | 114 ++++++ src/test/java/GetPostTest.java | 111 ++++++ src/test/java/ItemsTest.java | 121 +++++++ src/test/resources/items.html | 24 ++ src/test/resources/items_forms.html | 28 ++ src/test/resources/items_links.html | 20 ++ 18 files changed, 1591 insertions(+) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..518c151 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +target/ +.project +.classpath +.settings/ +/sling/ +derby.log +*.iml +*.ipr +*.iws +coverage.em +coverage.ec +coverage/ +.DS_Store +.idea/ +bundles/extensions/models/integration-tests/sling diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..9d95953 --- /dev/null +++ b/pom.xml @@ -0,0 +1,97 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +~ 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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.sling</groupId> + <artifactId>sling</artifactId> + <version>20</version> + <relativePath/> + </parent> + + <artifactId>org.apache.sling.hapi.client</artifactId> + <version>1.0.0-SNAPSHOT</version> + + <name>HApi - Sling Hypermedia API client-side tools</name> + <description>Sling client-side tools for HApi</description> + + <properties> + <sling.java.version>6</sling.java.version> + </properties> + + <build> + </build> + + <dependencies> + <dependency> + <groupId>org.apache.httpcomponents</groupId> + <artifactId>httpclient</artifactId> + <version>4.5.1</version> + </dependency> + <dependency> + <groupId>org.apache.httpcomponents</groupId> + <artifactId>httpmime</artifactId> + <version>4.5</version> + </dependency> + <dependency> + <groupId>commons-lang</groupId> + <artifactId>commons-lang</artifactId> + <version>2.4</version> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-io</artifactId> + <version>1.3.2</version> + </dependency> + <dependency> + <groupId>org.jsoup</groupId> + <artifactId>jsoup</artifactId> + <version>1.8.3</version> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <version>1.7.12</version> + </dependency> + <!-- test scope --> + <dependency> + <groupId>org.apache.httpcomponents</groupId> + <artifactId>httpclient</artifactId> + <version>4.5.1</version> + <classifier>tests</classifier> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> + <version>1.7.12</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.12</version> + <scope>test</scope> + </dependency> + </dependencies> + +</project> diff --git a/src/main/java/org/apache/sling/hapi/client/ClientException.java b/src/main/java/org/apache/sling/hapi/client/ClientException.java new file mode 100644 index 0000000..9bda81e --- /dev/null +++ b/src/main/java/org/apache/sling/hapi/client/ClientException.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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.apache.sling.hapi.client; + +/** + * An HTTP client exception + */ +public class ClientException extends Exception { + public ClientException(String message, Throwable cause) { + super(message, cause); + } + + public ClientException(String message) { + super(message); + } +} diff --git a/src/main/java/org/apache/sling/hapi/client/Document.java b/src/main/java/org/apache/sling/hapi/client/Document.java new file mode 100644 index 0000000..9344e5d --- /dev/null +++ b/src/main/java/org/apache/sling/hapi/client/Document.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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.apache.sling.hapi.client; + +/** + * An HTML document representation + */ +public interface Document { + Items link(String rel) throws ClientException; + Items form(String rel) throws ClientException; + Items item(String rel) throws ClientException; + Items items() throws ClientException; +} diff --git a/src/main/java/org/apache/sling/hapi/client/HtmlClient.java b/src/main/java/org/apache/sling/hapi/client/HtmlClient.java new file mode 100644 index 0000000..078f46b --- /dev/null +++ b/src/main/java/org/apache/sling/hapi/client/HtmlClient.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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.apache.sling.hapi.client; + +import org.apache.http.HttpEntity; + +public interface HtmlClient { + + /** + * Enters a url and return a Document + * + * @param url + * @return + * @throws ClientException + */ + <T extends Document> T enter(String url) throws ClientException; + + /** + * Performs a GET request and returns a Document + * + * @param url the URL String to perform an HTTP GET on + * @return + * @throws ClientException + */ + <T extends Document> T get(String url) throws ClientException; + + + /** + * Performs a POST request. + * + * @param url the URL String to perform an HTTP post on + * @param entity data to post + * @return + * @throws ClientException + */ + <T extends Document> T post(String url, HttpEntity entity) throws ClientException; + + /** + * Performs a DELETE request and returns a Document + * + * @param url the URL String to perform an HTTP DELETE on + * @return + * @throws ClientException + */ + <T extends Document> T delete(String url) throws ClientException; + + /** + * Method to create a new Document representation from an HTML content String + * @param html + * @param <T> + * @return + */ + <T extends Document> T newDocument(String html); + +} diff --git a/src/main/java/org/apache/sling/hapi/client/Item.java b/src/main/java/org/apache/sling/hapi/client/Item.java new file mode 100644 index 0000000..f1d8865 --- /dev/null +++ b/src/main/java/org/apache/sling/hapi/client/Item.java @@ -0,0 +1,82 @@ +package org.apache.sling.hapi.client; + +/******************************************************************************* + * 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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. + ******************************************************************************/ + +import org.apache.http.NameValuePair; + +import java.util.List; +import java.util.Set; + +/** + * + */ +public interface Item { + /** + * Returns the property of the item having the given name. + */ + Items prop(String name) throws ClientException; + + /** + * Return a List of all the properties of this item + */ + Set<String> props() throws ClientException; + + /** + * Returns the child links that have the given relation + */ + Items link(String rel) throws ClientException; + + /** + * Returns the child forms that have the given relation + */ + Items form(String rel) throws ClientException; + + /** + * Returns the text value of the property. + */ + String text() throws ClientException; + + /** + * Returns he boolean value of the property + */ + boolean bool() throws ClientException; + + int number() throws ClientException; + + /** + * Returns the href value of the property. This only makes sense for hyperlinks (<a> or <link>). + */ + String href(); + + /** + * Returns the <i>src</i> value of the property. + */ + String src(); + + /** + * Follow a hyperlink and get a new {@see Document} representation + */ + Document follow() throws ClientException; + + /** + * Submits this form item and returns a new {@see Document} representation + */ + Document submit(Iterable<NameValuePair> data) throws ClientException; +} diff --git a/src/main/java/org/apache/sling/hapi/client/Items.java b/src/main/java/org/apache/sling/hapi/client/Items.java new file mode 100644 index 0000000..4ccbc4d --- /dev/null +++ b/src/main/java/org/apache/sling/hapi/client/Items.java @@ -0,0 +1,35 @@ +package org.apache.sling.hapi.client; + +/******************************************************************************* + * 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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. + ******************************************************************************/ + +/** + * A composite object representing items in a {@see Document} + */ +public interface Items extends Iterable<Item>, Item { + /** + * Returns the item at one index + */ + Item at(int index) throws IndexOutOfBoundsException; + + /** + * Returns the amount of items contained in this collection. + */ + int length(); +} diff --git a/src/main/java/org/apache/sling/hapi/client/forms/FormValues.java b/src/main/java/org/apache/sling/hapi/client/forms/FormValues.java new file mode 100644 index 0000000..9a12cb1 --- /dev/null +++ b/src/main/java/org/apache/sling/hapi/client/forms/FormValues.java @@ -0,0 +1,116 @@ +/******************************************************************************* + * 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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.apache.sling.hapi.client.forms; + +import java.io.UnsupportedEncodingException; + +import org.apache.http.HttpEntity; +import org.apache.http.NameValuePair; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.utils.URLEncodedUtils; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.message.BasicNameValuePair; +import org.jsoup.nodes.Element; + +/** + * + */ +public class FormValues { + private Element form; + + private Iterable<NameValuePair> values; + + private Vals list = new Vals(); + + public FormValues(Element form, Iterable<NameValuePair> values) { + this.form = form; + this.values = values; + + build(); + resolve(); + } + + /** + * @return + * {@see http://www.w3.org/TR/html5/forms.html#constructing-the-form-data-set} + */ + private FormValues build() { + for (Element input : form.select("button, input, select, textarea")) { + String type = input.attr("type"); + + if (input.hasAttr("disabled")) continue; + if (input.tagName().equalsIgnoreCase("button") && !type.equals("submit")) continue; + if (input.tagName().equalsIgnoreCase("input") && (type.equals("button") || type.equals("reset"))) continue; + if (type.equals("checkbox") && input.hasAttr("checked")) continue; + if (type.equals("radio") && input.hasAttr("checked")) continue; + if (!type.equals("image") && input.attr("name").length() == 0) continue; + if (input.parents().is("datalist")) continue; + + if (type.equals("image") || type.equals("file")) continue; // don't support files for now + String name = input.attr("name"); + + if (input.tagName().equalsIgnoreCase("select")) { + for (Element o : input.select("option[selected]")) { + if (o.hasAttr("disabled")) continue; + list.add(name, new BasicNameValuePair(name, o.val())); + } + } else if (type.equals("checkbox") || type.equals("radio")) { + String value = input.hasAttr("value") ? input.val() : "on"; + list.add(name, new BasicNameValuePair(name, value)); + } else { + list.add(name, new BasicNameValuePair(name, input.val())); + } + } + return this; + } + + private FormValues resolve() { + for (NameValuePair o : values) { + if (list.has(o.getName())) { + list.set(o); + } else { + // for now just set the value even if the form doesn't have a submittable input for the name. + // this is to support custom field that generate input dynamically + list.set(o); + } + } + return this; + } + + public String toString() { + return URLEncodedUtils.format(list.flatten(), "UTF-8"); + } + + public HttpEntity toUrlEncodedEntity() { + try { + return new UrlEncodedFormEntity(list.flatten(), "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + public HttpEntity toMultipartEntity() { + MultipartEntityBuilder eb = MultipartEntityBuilder.create(); + for (NameValuePair p : list.flatten()) { + eb.addTextBody(p.getName(), p.getValue()); + } + return eb.build(); + } +} diff --git a/src/main/java/org/apache/sling/hapi/client/forms/Vals.java b/src/main/java/org/apache/sling/hapi/client/forms/Vals.java new file mode 100644 index 0000000..9e728a6 --- /dev/null +++ b/src/main/java/org/apache/sling/hapi/client/forms/Vals.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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.apache.sling.hapi.client.forms; + +import org.apache.http.NameValuePair; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class Vals { + private Map<String, List<NameValuePair>> data = new LinkedHashMap<String, List<NameValuePair>>(); + + public void add(String name, NameValuePair pair) { + if (data.containsKey(name)) { + data.get(name).add(pair); + } else { + ArrayList<NameValuePair> list = new ArrayList<NameValuePair>(); + list.add(pair); + data.put(name, list); + } + } + + public boolean has(String name) { + return data.containsKey(name); + } + + public void set(NameValuePair pair) { + ArrayList<NameValuePair> list = new ArrayList<NameValuePair>(); + list.add(pair); + data.put(pair.getName(), list); + } + + public List<? extends NameValuePair> flatten() { + List<NameValuePair> result = new ArrayList<NameValuePair>(); + + for (List<NameValuePair> c : data.values()) { + result.addAll(c); + } + + return result; + } +} diff --git a/src/main/java/org/apache/sling/hapi/client/impl/AbstractHtmlClientImpl.java b/src/main/java/org/apache/sling/hapi/client/impl/AbstractHtmlClientImpl.java new file mode 100644 index 0000000..5c3444a --- /dev/null +++ b/src/main/java/org/apache/sling/hapi/client/impl/AbstractHtmlClientImpl.java @@ -0,0 +1,192 @@ +/******************************************************************************* + * 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.apache.sling.hapi.client.impl; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.HttpClient; +import org.apache.http.client.ResponseHandler; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.utils.URIUtils; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.LaxRedirectStrategy; +import org.apache.http.params.HttpParams; +import org.apache.http.protocol.HttpContext; +import org.apache.http.util.EntityUtils; +import org.apache.sling.hapi.client.ClientException; +import org.apache.sling.hapi.client.Document; +import org.apache.sling.hapi.client.HtmlClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +public abstract class AbstractHtmlClientImpl implements HttpClient, HtmlClient { + protected static final Logger LOG = LoggerFactory.getLogger(AbstractHtmlClientImpl.class); + protected CloseableHttpClient client; + protected URI baseUrl; + + public AbstractHtmlClientImpl(CloseableHttpClient client, String baseUrl) throws URISyntaxException { + this.client = client; + this.baseUrl = new URI(baseUrl); + } + + public AbstractHtmlClientImpl(String baseUrl) throws URISyntaxException { + this(HttpClientBuilder.create().setRedirectStrategy(new LaxRedirectStrategy()).build(), baseUrl); + } + + public AbstractHtmlClientImpl(String baseUrl, String user, String password) throws URISyntaxException { + this.baseUrl = new URI(baseUrl); + HttpHost targetHost = URIUtils.extractHost(this.baseUrl); + BasicCredentialsProvider credsProvider = new BasicCredentialsProvider(); + credsProvider.setCredentials( + new AuthScope(targetHost.getHostName(), targetHost.getPort()), + new UsernamePasswordCredentials(user, password)); + this.client = HttpClientBuilder.create() + .setDefaultCredentialsProvider(credsProvider) + .setRedirectStrategy(new LaxRedirectStrategy()) + .build(); + } + + + @Override + public <T extends Document> T enter(String url) throws ClientException { + return get(url); + } + + @Override + public <T extends Document> T get(String url) throws ClientException { + try { + URI absoluteUri = absoluteUri(url); + LOG.info("GET " + absoluteUri); + HttpResponse response = this.execute(new HttpGet(absoluteUri)); + return newDocument(EntityUtils.toString(response.getEntity())); + + } catch (URISyntaxException e) { + throw new ClientException("Invalid get url " + url, e); + } catch (Exception e) { + throw new ClientException("Could not execute GET request", e); + } + } + + @Override + public <T extends Document> T post(String url, HttpEntity entity) throws ClientException { + try { + URI absoluteUri = absoluteUri(url); + LOG.info("POST " + absoluteUri); + HttpPost post = new HttpPost(absoluteUri); + post.setEntity(entity); + HttpResponse response = this.execute(post); + return newDocument(EntityUtils.toString(response.getEntity())); + } catch (URISyntaxException e) { + throw new ClientException("Invalid post url " + url, e); + } catch (Exception e) { + throw new ClientException("Could not execute POST request", e); + } + } + + @Override + public <T extends Document> T delete(String url) throws ClientException { + try { + URI absoluteUri = absoluteUri(url); + LOG.info("DELETE " + absoluteUri); + HttpResponse response = this.execute(new HttpDelete(absoluteUri)); + return newDocument(response.getEntity().toString()); + } catch (URISyntaxException e) { + throw new ClientException("Invalid post url " + url, e); + } catch (Exception e) { + throw new ClientException("Could not execute DELETE request", e); + } + } + + @Override + public abstract <T extends Document> T newDocument(String html); + + + private URI absoluteUri(String url) throws URISyntaxException { + URI getUrl = new URI(url); + return this.baseUrl.resolve(getUrl); + } + + /* + Overrides of HttpClient methods + */ + @Override + public HttpParams getParams() { + return client.getParams(); + } + + @Override + public ClientConnectionManager getConnectionManager() { + return client.getConnectionManager(); + } + + @Override + public HttpResponse execute(HttpUriRequest httpUriRequest) throws IOException, ClientProtocolException { + return client.execute(httpUriRequest); + } + + @Override + public HttpResponse execute(HttpUriRequest httpUriRequest, HttpContext httpContext) throws IOException, ClientProtocolException { + return client.execute(httpUriRequest, httpContext); + } + + @Override + public HttpResponse execute(HttpHost httpHost, HttpRequest httpRequest) throws IOException, ClientProtocolException { + return client.execute(httpHost, httpRequest); + } + + @Override + public HttpResponse execute(HttpHost httpHost, HttpRequest httpRequest, HttpContext httpContext) throws IOException, ClientProtocolException { + return client.execute(httpHost, httpRequest, httpContext); + } + + @Override + public <T> T execute(HttpUriRequest httpUriRequest, ResponseHandler<? extends T> responseHandler) throws IOException, ClientProtocolException { + return client.execute(httpUriRequest, responseHandler); + } + + @Override + public <T> T execute(HttpUriRequest httpUriRequest, ResponseHandler<? extends T> responseHandler, HttpContext httpContext) throws IOException, ClientProtocolException { + return client.execute(httpUriRequest, responseHandler, httpContext); + } + + @Override + public <T> T execute(HttpHost httpHost, HttpRequest httpRequest, ResponseHandler<? extends T> responseHandler) throws IOException, ClientProtocolException { + return client.execute(httpHost, httpRequest, responseHandler); + } + + @Override + public <T> T execute(HttpHost httpHost, HttpRequest httpRequest, ResponseHandler<? extends T> responseHandler, HttpContext httpContext) throws IOException, ClientProtocolException { + return client.execute(httpHost, httpRequest, responseHandler, httpContext); + } +} diff --git a/src/main/java/org/apache/sling/hapi/client/microdata/MicrodataDocument.java b/src/main/java/org/apache/sling/hapi/client/microdata/MicrodataDocument.java new file mode 100644 index 0000000..2f6ee91 --- /dev/null +++ b/src/main/java/org/apache/sling/hapi/client/microdata/MicrodataDocument.java @@ -0,0 +1,399 @@ +/******************************************************************************* + * 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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.apache.sling.hapi.client.microdata; + +import org.apache.http.HttpEntity; +import org.apache.http.NameValuePair; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.sling.hapi.client.*; +import org.apache.sling.hapi.client.forms.FormValues; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.*; + +public class MicrodataDocument implements Document { + private org.jsoup.nodes.Document jsoupDocument; + private HtmlClient client; + + public MicrodataDocument(String html, HtmlClient client, String baseUrl) { + this.jsoupDocument = Jsoup.parse(html, baseUrl); + this.client = client; + } + + + @Override + public Items link(String rel) throws ClientException { + Item me = toItem(jsoupDocument); + return me.link(rel); + } + + @Override + public Items form(String rel) throws ClientException { + Item me = toItem(jsoupDocument); + return me.form(rel); + } + + @Override + public Items item(String rel) { + List<Item> items = new ArrayList<Item>(); + + for (Item i : items()) { + if (((ItemImpl) i).el.hasClass(rel)) { + items.add(i); + } + } + + return new ItemsImpl(items); + } + + @Override + public Items items() { + return new ItemsImpl(selectItems(jsoupDocument, new ArrayList<Item>())); + } + + @Override + public String toString() { + return this.jsoupDocument.toString(); + } + + private String toClass(String name) { + return "." + name; + } + + private List<Item> toItems(Elements el) { + List<Item> items = new ArrayList<Item>(); + + for (Element e : el) { + items.add(toItem(e)); + } + + return items; + } + + private Item toItem(Element el) { + return new ItemImpl(el, this); + } + + private List<Item> selectItems(Element e, List<Item> items) { + if (e.hasAttr("itemscope") && !e.hasAttr("itemprop")) { + items.add(new ItemImpl(e, this)); + return items; + } + + for (Element c : e.children()) { + selectItems(c, items); + } + + return items; + } + + + private class ItemImpl implements Item { + + private Element el; + private MicrodataDocument document; + private ItemImpl followed; + + public ItemImpl(Element element, MicrodataDocument document) { + if (element == null) throw new NullPointerException("element is mandatory"); + this.el = element; + this.document = document; + } + + @Override + public Items prop(String name) throws ClientException { + return new ItemsImpl(selectProps(getProxy().el, name, new ArrayList<Item>())); + } + + @Override + public Set<String> props() throws ClientException { + return selectAllPropNames(getProxy().el, new HashSet<String>()); + } + + @Override + public Items link(String rel) throws ClientException { + return new ItemsImpl(toItems(getProxy().el.select("link[rel=" + rel + "], a[rel=" + rel + "]"))); + } + + @Override + public Items form(String rel) throws ClientException { + return new ItemsImpl(toItems(getProxy().el.select("form[data-rel=" + rel + "]"))); + } + + + /* Private methods */ + + private List<Item> selectProps(Element e, String name, List<Item> items) { + for (Element c : e.children()) { + if (c.hasAttr("itemprop")) { + if (c.attr("itemprop").equals(name)) { + items.add(new ItemImpl(c, document)); + } + + if (c.hasAttr("itemscope")) { + continue; + } + } + + selectProps(c, name, items); + } + + return items; + } + + private Set<String> selectAllPropNames(Element e, Set<String> items) { + for (Element c : e.children()) { + if (c.hasAttr("itemprop")) { + items.add(c.attr("itemprop")); + } + + if (c.hasAttr("itemscope")) { + continue; + } + + selectAllPropNames(c, items); + } + return items; + } + + private ItemImpl getProxy() throws ClientException { + if (el.tagName().equalsIgnoreCase("a") && el.hasAttr("href")) { + if (followed == null) { + followed = follow(el, document); + } + return followed; + } + + return this; + } + + private ItemImpl follow(Element el, MicrodataDocument doc) throws ClientException { + if (el.hasAttr("itemscope")) { + return new ItemImpl(el, doc); + } + + if (el.tagName().equalsIgnoreCase("a") && el.hasAttr("href")) { + String href = el.attr("href"); + + if (href.startsWith("#")) { + Element first = el.ownerDocument().select(href).first(); + return first == null ? null : follow(first, doc); + } + + String absHref = el.attr("abs:href"); + MicrodataDocument d = (MicrodataDocument) doc.client.get(absHref); + + try { + URI uri = new URI(absHref); + + String fragment = uri.getRawFragment(); + if (fragment != null) { + Element e = d.jsoupDocument.getElementById(fragment); + return e == null ? null : follow(e, d); + } + } catch (URISyntaxException ex) { + throw new ClientException("Error parsing URI: " + absHref, ex); + } + + ItemsImpl items = (ItemsImpl) d.items(); + + if (items.length() == 1) { + return (ItemImpl) items.at(0); + } + + throw new ClientException("Unable determine item: " + absHref); + } + + return new ItemImpl(el, doc); + } + + @Override + public String text() throws ClientException { + // resolve element + Element el = getProxy().el; + + // if it's a meta, get the value of the content attr + if (el.tagName().equalsIgnoreCase("meta") && el.hasAttr("content")) { + return el.attr("content"); + } + + // else, get the text value using jsoup + return getProxy().el.text(); + } + + @Override + public boolean bool() throws ClientException { + return Boolean.parseBoolean(text()); + } + + @Override + public int number() throws ClientException { + return Integer.parseInt(text()); + } + + @Override + public String href() { + return el.attr("href"); + } + + @Override + public String src() { + return el.attr("src"); + } + + @Override + public Document follow() throws ClientException { + if ((el.tagName().equalsIgnoreCase("a") || el.tagName().equalsIgnoreCase("link")) && el.hasAttr("href")) { + String href = el.attr("href"); + + if (href.startsWith("#")) { + return document; + } + + return document.client.enter(el.attr("abs:href")); + } + + throw new ClientException("Unable to follow: " + el.toString()); + } + + @Override + public Document submit(Iterable<NameValuePair> values) throws ClientException { + if (el.tagName().equalsIgnoreCase("form")) { + String action = el.attr("abs:action"); + if (action.length() == 0) { + action = el.baseUri(); + } + + String method = el.attr("method"); + + if (method.length() == 0 || method.equalsIgnoreCase("get")) { + FormValues query = new FormValues(el, values); + String url = action + (action.contains("?") ? "?" : "&") + query.toString(); + return document.client.enter(url); + } + + if (method.equalsIgnoreCase("post")) { + String enctype = el.attr("enctype"); + + FormValues v = new FormValues(el, values); + if (enctype.length() == 0 || enctype.equalsIgnoreCase("application/x-www-form-urlencoded")) { + return document.client.post(action, v.toUrlEncodedEntity()); + } else if (enctype.equalsIgnoreCase("multipart/form-data")) { + return document.client.post(action, v.toMultipartEntity()); + } + + throw new ClientException("Unsupported form enctype: " + enctype); + } + + throw new ClientException("Unsupported form method: " + method); + } + + throw new ClientException("The item is not a form"); + } + + + } + + /** + * Items impl for microdata + */ + private class ItemsImpl implements Items { + private List<Item> items; + + public ItemsImpl(List<Item> items) { + this.items = items; + } + + @Override + public Iterator<Item> iterator() { + return items.iterator(); + } + + @Override + public Item at(int index) { + return items.get(index); + } + + @Override + public int length() { + return items.size(); + } + + @Override + public Items prop(String name) throws ClientException { + return items.get(0).prop(name); + } + + @Override + public Set<String> props() throws ClientException { + return items.get(0).props(); + } + + @Override + public Items link(String rel) throws ClientException { + return items.get(0).link(rel); + } + + @Override + public Items form(String rel) throws ClientException { + return items.get(0).form(rel); + } + + @Override + public String text() throws ClientException { + return items.get(0).text(); + } + + @Override + public boolean bool() throws ClientException { + return items.get(0).bool(); + } + + @Override + public int number() throws ClientException { + return items.get(0).number(); + } + + @Override + public String href() { + return items.get(0).href(); + } + + @Override + public String src() { + return items.get(0).src(); + } + + @Override + public Document follow() throws ClientException { + return items.get(0).follow(); + } + + @Override + public Document submit(Iterable<NameValuePair> values) throws ClientException { + return items.get(0).submit(values); + } + } + +} \ No newline at end of file diff --git a/src/main/java/org/apache/sling/hapi/client/microdata/MicrodataHtmlClient.java b/src/main/java/org/apache/sling/hapi/client/microdata/MicrodataHtmlClient.java new file mode 100644 index 0000000..807067a --- /dev/null +++ b/src/main/java/org/apache/sling/hapi/client/microdata/MicrodataHtmlClient.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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.apache.sling.hapi.client.microdata; + +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.sling.hapi.client.impl.AbstractHtmlClientImpl; + +import java.net.URISyntaxException; + +public class MicrodataHtmlClient extends AbstractHtmlClientImpl { + + public MicrodataHtmlClient(CloseableHttpClient client, String baseUrl) throws URISyntaxException { + super(client, baseUrl); + } + + public MicrodataHtmlClient(String baseUrl) throws URISyntaxException { + super(baseUrl); + } + + public MicrodataHtmlClient(String baseUrl, String user, String password) throws URISyntaxException { + super(baseUrl, user, password); + } + + @Override + public MicrodataDocument newDocument(String html) { + return new MicrodataDocument(html, this, this.baseUrl.toString()); + } +} diff --git a/src/test/java/FormTest.java b/src/test/java/FormTest.java new file mode 100644 index 0000000..3c9424d --- /dev/null +++ b/src/test/java/FormTest.java @@ -0,0 +1,114 @@ +/******************************************************************************* + * 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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. + ******************************************************************************/ + +import org.apache.commons.io.IOUtils; +import org.apache.http.*; +import org.apache.http.client.utils.URIUtils; +import org.apache.http.entity.StringEntity; +import org.apache.http.localserver.LocalServerTestBase; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.HttpRequestHandler; +import org.apache.sling.hapi.client.ClientException; +import org.apache.sling.hapi.client.Document; +import org.apache.sling.hapi.client.Items; +import org.apache.sling.hapi.client.microdata.MicrodataHtmlClient; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.core.IsEqual.equalTo; + + +public class FormTest extends LocalServerTestBase { + public static final String GET_URL = "/test1"; + public static final String POST_URL = "/testpost1"; + public static final String OK_RESPONSE = "TEST_OK"; + public static final String FAIL_RESPONSE = "TEST_FAIL"; + + public static String html; + + private HttpHost host; + private URI uri; + + @BeforeClass + public static void setUpClass() throws IOException { + FormTest.html = IOUtils.toString(ItemsTest.class.getResourceAsStream("items_forms.html"), "UTF-8"); + } + + @Before + public void setup() throws Exception { + super.setUp(); + this.serverBootstrap.registerHandler(GET_URL, new HttpRequestHandler() { + @Override + public void handle(HttpRequest httpRequest, HttpResponse httpResponse, HttpContext httpContext) + throws HttpException, IOException { + HttpEntity entity = new StringEntity(html, "UTF-8"); + httpResponse.setEntity(entity); + } + }).registerHandler(POST_URL, new HttpRequestHandler() { + @Override + public void handle(HttpRequest httpRequest, HttpResponse httpResponse, HttpContext httpContext) + throws HttpException, IOException { + if (!httpRequest.getRequestLine().getMethod().equals("POST")) { + httpResponse.setEntity(new StringEntity(FAIL_RESPONSE)); + } else { + httpResponse.setEntity(new StringEntity(OK_RESPONSE)); + } + httpResponse.setStatusCode(302); + httpResponse.setHeader("Location", GET_URL); + } + }); + + // start server + this.host = this.start(); + this.uri = URIUtils.rewriteURI(new URI("/"), host); + } + + @Test + public void testForm() throws ClientException, URISyntaxException { + MicrodataHtmlClient client = new MicrodataHtmlClient(uri.toString()); + Document doc = client.enter(GET_URL); + Items items = doc.items(); + Assert.assertThat(items.length(), equalTo(1)); + Items form = doc.form("test"); + Assert.assertThat(form.length(), equalTo(2)); + + List<NameValuePair> data = new ArrayList<NameValuePair>(); + data.add(new BasicNameValuePair("f1", "val1")); + + // url encode enctype + Document doc2 = form.at(0).submit(data); + Assert.assertThat(doc2.items().length(), equalTo(1)); + + // the multipart enctype + Document doc3 = form.at(1).submit(data); + Assert.assertThat(doc3.items().length(), equalTo(1)); + + + + } +} diff --git a/src/test/java/GetPostTest.java b/src/test/java/GetPostTest.java new file mode 100644 index 0000000..e45b8b1 --- /dev/null +++ b/src/test/java/GetPostTest.java @@ -0,0 +1,111 @@ + +/******************************************************************************* + * 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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. + ******************************************************************************/ + +import org.apache.http.*; +import org.apache.http.client.utils.URIUtils; +import org.apache.http.entity.StringEntity; +import org.apache.http.localserver.LocalServerTestBase; +import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.HttpRequestHandler; +import org.apache.sling.hapi.client.ClientException; +import org.apache.sling.hapi.client.Document; +import org.apache.sling.hapi.client.microdata.MicrodataHtmlClient; +import org.hamcrest.core.StringContains; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; + +import static org.hamcrest.core.StringContains.containsString; + +public class GetPostTest extends LocalServerTestBase { + public static final String GET_URL = "/test"; + public static final String GET_AUTH_URL = "/testauth"; + public static final String OK_RESPONSE = "TEST_OK"; + public static final String FAIL_RESPONSE = "TEST_FAIL"; + public static final String USER = "admin"; + private static final String PASSWORD = "admin"; + private static final String AUTH_STRING = "Basic YWRtaW46YWRtaW4="; + private static final String REDIRECT_URL = "/test_redirect"; + + + private HttpHost host; + private URI uri; + + @Before + public void setup() throws Exception { + super.setUp(); + this.serverBootstrap.registerHandler(GET_URL, new HttpRequestHandler() { + @Override + public void handle(HttpRequest httpRequest, HttpResponse httpResponse, HttpContext httpContext) + throws HttpException, IOException { + httpResponse.setEntity(new StringEntity(OK_RESPONSE)); + } + }).registerHandler(GET_AUTH_URL, new HttpRequestHandler() { + @Override + public void handle(HttpRequest httpRequest, HttpResponse httpResponse, HttpContext httpContext) + throws HttpException, IOException { + Header[] headers = httpRequest.getHeaders("Authorization"); + if (null == headers || headers.length == 0 || !headers[0].getValue().equals(AUTH_STRING)) { + httpResponse.setStatusCode(401); + httpResponse.setHeader("WWW-Authenticate", "Basic realm=\"TEST\""); + } else { + httpResponse.setEntity(new StringEntity(OK_RESPONSE)); + } + } + }).registerHandler(REDIRECT_URL, new HttpRequestHandler() { + @Override + public void handle(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, IOException { + response.setStatusCode(307); + response.setHeader("Location", GET_URL); + } + }); + + // start server + this.host = this.start(); + this.uri = URIUtils.rewriteURI(new URI("/"), host); + } + + @Test + public void testValidGet() throws ClientException, URISyntaxException { + MicrodataHtmlClient client = new MicrodataHtmlClient(uri.toString()); + Document doc = client.get(GET_URL); + Assert.assertThat("GET request failed", doc.toString(), new StringContains(OK_RESPONSE)); + } + + @Test + public void testValidAuthGet() throws ClientException, URISyntaxException { + MicrodataHtmlClient client = new MicrodataHtmlClient(uri.toString(), USER, PASSWORD); + Document doc = client.get(GET_AUTH_URL); + Assert.assertThat("GET request failed with basic auth", doc.toString(), containsString(OK_RESPONSE)); + } + + @Test + public void testRedirect() throws ClientException, URISyntaxException, UnsupportedEncodingException { + MicrodataHtmlClient client = new MicrodataHtmlClient(uri.toString(), USER, PASSWORD); + Document doc = client.post(REDIRECT_URL, new StringEntity("test")); + Assert.assertThat("POST request failed to redirect", doc.toString(), containsString(OK_RESPONSE)); + } + +} diff --git a/src/test/java/ItemsTest.java b/src/test/java/ItemsTest.java new file mode 100644 index 0000000..9e0b962 --- /dev/null +++ b/src/test/java/ItemsTest.java @@ -0,0 +1,121 @@ +/******************************************************************************* + * 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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. + ******************************************************************************/ + +import org.apache.commons.io.IOUtils; +import org.apache.http.*; +import org.apache.http.client.utils.URIUtils; +import org.apache.http.entity.StringEntity; +import org.apache.http.localserver.LocalServerTestBase; +import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.HttpRequestHandler; +import org.apache.sling.hapi.client.ClientException; +import org.apache.sling.hapi.client.Document; +import org.apache.sling.hapi.client.Items; +import org.apache.sling.hapi.client.microdata.MicrodataHtmlClient; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +import static org.hamcrest.core.IsEqual.equalTo; + + +public class ItemsTest extends LocalServerTestBase { + public static final String GET_URL = "/test"; + public static final String GET_LINKS_URL = "/testlinks"; + public static final String OK_RESPONSE = "TEST_OK"; + public static final String FAIL_RESPONSE = "TEST_FAIL"; + + public static String html; + public static String htmlLinks; + + private HttpHost host; + private URI uri; + + @BeforeClass + public static void setUpClass() throws IOException { + ItemsTest.html = IOUtils.toString(ItemsTest.class.getResourceAsStream("items.html"), "UTF-8"); + ItemsTest.htmlLinks = IOUtils.toString(ItemsTest.class.getResourceAsStream("items_links.html"), "UTF-8"); + } + + @Before + public void setup() throws Exception { + super.setUp(); + this.serverBootstrap.registerHandler(GET_URL, new HttpRequestHandler() { + @Override + public void handle(HttpRequest httpRequest, HttpResponse httpResponse, HttpContext httpContext) + throws HttpException, IOException { + HttpEntity entity = new StringEntity(html, "UTF-8"); + httpResponse.setEntity(entity); + } + }).registerHandler(GET_LINKS_URL, new HttpRequestHandler() { + @Override + public void handle(HttpRequest httpRequest, HttpResponse httpResponse, HttpContext httpContext) + throws HttpException, IOException { + HttpEntity entity = new StringEntity(htmlLinks, "UTF-8"); + httpResponse.setEntity(entity); + } + }); + + // start server + this.host = this.start(); + this.uri = URIUtils.rewriteURI(new URI("/"), host); + } + + @Test + public void testItems() throws ClientException, URISyntaxException { + MicrodataHtmlClient client = new MicrodataHtmlClient(uri.toString()); + Document doc = client.enter(GET_URL); + Items items = doc.items(); + Assert.assertThat(items.length(), equalTo(2)); + for (int i=0; i<2; i++) { + Assert.assertThat(items.at(i).prop("name").text(), equalTo("Avatar" + i)); + Assert.assertThat(items.at(i).prop("genre").text(), equalTo("Science fiction" + i)); + Assert.assertThat(items.at(i).prop("rank").number(), equalTo(i)); + Assert.assertThat(items.at(i).prop("director").prop("name").text(), equalTo("James Cameron" + i)); + Assert.assertThat(items.at(i).prop("director").prop("birthDate").text(), equalTo("August 16, 1954 - " + i)); + } + } + + @Test + public void testItemsLinks() throws ClientException, URISyntaxException { + MicrodataHtmlClient client = new MicrodataHtmlClient(uri.toString()); + Document doc = client.enter(GET_LINKS_URL); + Items items = doc.items(); + Assert.assertThat(items.length(), equalTo(1)); + Assert.assertThat(items.prop("name").text(), equalTo("Avatar")); + Assert.assertThat(items.prop("genre").text(), equalTo("Science fiction")); + Assert.assertThat(items.prop("rank").number(), equalTo(2)); + Assert.assertThat(items.prop("director").prop("name").text(), equalTo("James Cameron")); + Assert.assertThat(items.prop("director").prop("birthDate").text(), equalTo("August 16, 1954")); + + Assert.assertThat(doc.link("test").length(), equalTo(2)); + Assert.assertThat(doc.items().link("test").length(), equalTo(1)); + Assert.assertThat(doc.items().prop("director").link("test").length(), equalTo(1)); + + Items otherMovies = doc.link("test").follow().items(); + Assert.assertThat(otherMovies.length(), equalTo(2)); + + } + +} diff --git a/src/test/resources/items.html b/src/test/resources/items.html new file mode 100644 index 0000000..abe5d78 --- /dev/null +++ b/src/test/resources/items.html @@ -0,0 +1,24 @@ +<html> +<head> +</head> +</html> +<body> +<div itemscope itemtype ="http://schema.org/Movie"> + <h1 itemprop="name">Avatar0</h1> + <div itemprop="director" itemscope itemtype="http://schema.org/Person"> + Director: <span itemprop="name" itemscope itemtype="text">James Cameron0</span> + (born <span itemprop="birthDate" itemscope itemtype="text">August 16, 1954 - 0</span>) + </div> + <span itemprop="genre" itemscope itemtype="text">Science fiction0</span> + <span itemprop="rank" itemscope="true" itemtype="number">0</span> +</div> +<div itemscope itemtype ="http://schema.org/Movie"> + <h1 itemprop="name">Avatar1</h1> + <div itemprop="director" itemscope itemtype="http://schema.org/Person"> + Director: <span itemprop="name">James Cameron1</span> (born <span itemprop="birthDate">August 16, 1954 - 1</span>) + </div> + <span itemprop="genre">Science fiction1</span> + <span itemprop="rank">1</span> +</div> +</body> +</html> \ No newline at end of file diff --git a/src/test/resources/items_forms.html b/src/test/resources/items_forms.html new file mode 100644 index 0000000..fd14eb1 --- /dev/null +++ b/src/test/resources/items_forms.html @@ -0,0 +1,28 @@ +<html> +<head> +</head> +</html> +<body> +<div itemscope itemtype ="http://schema.org/Movie"> + <h1 itemprop="name">Avatar</h1> + <div itemprop="director" itemscope itemtype="http://schema.org/Person"> + Director: <span itemprop="name" itemscope itemtype="text">James Cameron</span> + (born <span itemprop="birthDate" itemscope itemtype="text">August 16, 1954</span>) + <p>Also directed: <a rel="test" href="/test">test</a></p> + </div> + <span itemprop="genre" itemscope itemtype="text">Science fiction</span> + <span itemprop="rank" itemscope="true" itemtype="number">2</span> +</div> +<div> + <form data-rel="test" action="/testpost1" method="post"> + <input type="text" name="f1" value="default1"> + <textarea name="f2">val2</textarea> + </form> + + <form data-rel="test" action="/testpost1" method="post" enctype="multipart/form-data"> + <input type="text" name="f1" value="default1"> + <textarea name="f2">val2</textarea> + </form> +</div> +</body> +</html> \ No newline at end of file diff --git a/src/test/resources/items_links.html b/src/test/resources/items_links.html new file mode 100644 index 0000000..1586116 --- /dev/null +++ b/src/test/resources/items_links.html @@ -0,0 +1,20 @@ +<html> +<head> +</head> +</html> +<body> +<div itemscope itemtype ="http://schema.org/Movie"> + <h1 itemprop="name">Avatar</h1> + <div itemprop="director" itemscope itemtype="http://schema.org/Person"> + Director: <span itemprop="name" itemscope itemtype="text">James Cameron</span> + (born <span itemprop="birthDate" itemscope itemtype="text">August 16, 1954</span>) + <p>Also directed: <a rel="test" href="/test">test</a></p> + </div> + <span itemprop="genre" itemscope itemtype="text">Science fiction</span> + <span itemprop="rank" itemscope="true" itemtype="number">2</span> +</div> +<div> + <a rel="test" href="/test">test</a> +</div> +</body> +</html> \ No newline at end of file -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
