You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by dk...@apache.org on 2018/10/08 03:00:46 UTC

[sling-org-apache-sling-app-cms] 01/01: Adding the initial code for displaying insights in Sling CMS

This is an automated email from the ASF dual-hosted git repository.

dklco pushed a commit to branch insights-feature
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-app-cms.git

commit 652210b67d11d2b2a91912b4e8853083869b56c8
Author: Dan Klco <dk...@apache.org>
AuthorDate: Sun Oct 7 23:00:32 2018 -0400

    Adding the initial code for displaying insights in Sling CMS
---
 api/pom.xml                                        |  23 +-
 api/src/main/java/org/apache/sling/cms/File.java   | 138 ++++++++++
 .../java/org/apache/sling/cms/FileManager.java     |  34 +++
 .../sling/cms/insights/FileInsightRequest.java     |  39 +++
 .../org/apache/sling/cms/insights/Insight.java     | 164 ++++++++++++
 .../apache/sling/cms/insights/InsightFactory.java  |  48 ++++
 .../apache/sling/cms/insights/InsightProvider.java |  56 ++++
 .../apache/sling/cms/insights/InsightRequest.java  |  45 ++++
 .../org/apache/sling/cms/insights/Message.java     |  67 +++++
 .../sling/cms/insights/PageInsightRequest.java     |  67 +++++
 .../apache/sling/cms/insights/package-info.java    |  25 ++
 core/pom.xml                                       |   4 +
 .../ipeirotis/readability/engine/Readability.java  | 231 ++++++++++++++++
 .../readability/engine/SentenceExtractor.java      |  48 ++++
 .../ipeirotis/readability/engine/Syllabify.java    |  73 +++++
 .../core/insights/impl/BaseInsightProvider.java    |  63 +++++
 .../cms/core/insights/impl/FakeHttpSession.java    | 101 +++++++
 .../sling/cms/core/insights/impl/FakeRequest.java  | 298 +++++++++++++++++++++
 .../sling/cms/core/insights/impl/FakeResponse.java | 189 +++++++++++++
 .../core/insights/impl/FileInsightRequestImpl.java |  46 ++++
 .../cms/core/insights/impl/InsightFactoryImpl.java |  98 +++++++
 .../core/insights/impl/PageInsightRequestImpl.java | 107 ++++++++
 .../insights/impl/ReadabilityInsightProvider.java  |   5 +
 .../models/{PageImpl.java => FileImpl.java}        | 102 +++----
 .../cms/core/internal/models/FileManagerImpl.java  |  47 ++++
 .../sling/cms/core/internal/models/PageImpl.java   |   8 +-
 pom.xml                                            |   6 +
 27 files changed, 2045 insertions(+), 87 deletions(-)

diff --git a/api/pom.xml b/api/pom.xml
index eb7c0c0..64efe15 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -1,15 +1,12 @@
 <?xml version="1.0" encoding="ISO-8859-1"?>
-<!-- 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/maven-v4_0_0.xsd">
+<!-- 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/maven-v4_0_0.xsd"
+>
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <artifactId>org.apache.sling.cms</artifactId>
@@ -95,5 +92,9 @@
             <groupId>org.jetbrains</groupId>
             <artifactId>annotations</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.jsoup</groupId>
+            <artifactId>jsoup</artifactId>
+        </dependency>
     </dependencies>
 </project>
\ No newline at end of file
diff --git a/api/src/main/java/org/apache/sling/cms/File.java b/api/src/main/java/org/apache/sling/cms/File.java
new file mode 100644
index 0000000..d08c331
--- /dev/null
+++ b/api/src/main/java/org/apache/sling/cms/File.java
@@ -0,0 +1,138 @@
+/*
+ * 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.cms;
+
+import java.util.Calendar;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ValueMap;
+import org.osgi.annotation.versioning.ProviderType;
+
+/**
+ * An interface representing a sling:File resource. Adaptable from a sling:File
+ * Resource.
+ */
+@ProviderType
+public interface File {
+
+    /**
+     * Gets the path of this file after it is published on the site's primary domain
+     * with the the html extension
+     * 
+     * @return the published path of the file
+     */
+    String getPublishedPath();
+
+    /**
+     * Gets the full url (including domain) of this file after it is published on
+     * the site's primary domain
+     * 
+     * @return the published url of the file
+     */
+    String getPublishedUrl();
+
+    /**
+     * Gets the site this file is contained within
+     * 
+     * @return the site containing the file
+     */
+    Site getSite();
+
+    /**
+     * Retrieves the content Resource of the file
+     * 
+     * @return the jcr:content child resource of the file
+     */
+    Resource getContentResource();
+
+    /**
+     * Gets the date the file was created
+     * 
+     * @return the date on which the file was created
+     */
+    Calendar getCreated();
+
+    /**
+     * Gets the name of the user whom created this file
+     * 
+     * @return the name of the user whom created the file
+     */
+    String getCreatedBy();
+
+    /**
+     * Gets the last time this file was modified
+     * 
+     * @return the date the file was last modified
+     */
+    Calendar getLastModified();
+
+    /**
+     * Gets the username of the user who last modified the file
+     * 
+     * @return the name of the user who last modified the file
+     */
+    String getLastModifiedBy();
+
+    /**
+     * Gets the name of the Sling Resource backing the file
+     * 
+     * @return the name of the file resource
+     */
+    String getName();
+
+    /**
+     * Gets the parent of the current file
+     * 
+     * @return the parent of the file
+     */
+    Resource getParent();
+
+    /**
+     * Get the path this file resides within the repository
+     * 
+     * @return the path of the file
+     */
+    String getPath();
+
+    /**
+     * Gets a ValueMap of the properties of the content resource for this page
+     * 
+     * @return the properties of the content resource
+     */
+    ValueMap getProperties();
+
+    /**
+     * Returns true if the file is published, false otherwise
+     * 
+     * @return whether or not the file is published
+     */
+    boolean isPublished();
+
+    /**
+     * Gets the resource backing this file
+     * 
+     * @return the resource backing the file
+     */
+    Resource getResource();
+
+    /**
+     * Gets the content type of this file
+     * 
+     * @return the content type of the file
+     */
+    String getContentType();
+}
diff --git a/api/src/main/java/org/apache/sling/cms/FileManager.java b/api/src/main/java/org/apache/sling/cms/FileManager.java
new file mode 100644
index 0000000..f322dd7
--- /dev/null
+++ b/api/src/main/java/org/apache/sling/cms/FileManager.java
@@ -0,0 +1,34 @@
+/*
+ * 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.cms;
+
+import org.osgi.annotation.versioning.ProviderType;
+
+/**
+ * A interface used to retrieve sling:File Resources. Adaptable from a child
+ * Resource of a sling:File.
+ */
+@ProviderType
+public interface FileManager {
+
+    /**
+     * Get the file containing the adapted resource
+     * 
+     * @return the file
+     */
+    File getFile();
+}
diff --git a/api/src/main/java/org/apache/sling/cms/insights/FileInsightRequest.java b/api/src/main/java/org/apache/sling/cms/insights/FileInsightRequest.java
new file mode 100644
index 0000000..f45a8f7
--- /dev/null
+++ b/api/src/main/java/org/apache/sling/cms/insights/FileInsightRequest.java
@@ -0,0 +1,39 @@
+/*
+ * 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.cms.insights;
+
+import org.apache.sling.cms.File;
+
+/**
+ * Represents an insight request for a file
+ */
+public interface FileInsightRequest extends InsightRequest {
+
+    public default TYPE getType() {
+        return TYPE.FILE;
+    }
+
+    /**
+     * Returns the file used to create this request
+     * 
+     * @return the file
+     */
+    public File getFile();
+
+}
diff --git a/api/src/main/java/org/apache/sling/cms/insights/Insight.java b/api/src/main/java/org/apache/sling/cms/insights/Insight.java
new file mode 100644
index 0000000..eefbb8d
--- /dev/null
+++ b/api/src/main/java/org/apache/sling/cms/insights/Insight.java
@@ -0,0 +1,164 @@
+/*
+ * 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.cms.insights;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Simple POJO Model for holding an insight provider's results.
+ */
+public class Insight {
+
+    private Object data;
+    private boolean display = true;
+    private InsightProvider provider;
+    private InsightRequest request;
+    private double score;
+    private boolean scored = false;
+    private List<Message> scoreDetails = new ArrayList<>();
+    private boolean skip = false;
+    private boolean succeeded = true;
+
+    public Insight(InsightProvider provider, InsightRequest request) {
+        this.request = request;
+        this.provider = provider;
+    }
+
+    /**
+     * @return the data
+     */
+    public Object getData() {
+        return data;
+    }
+
+    /**
+     * @return the provider
+     */
+    public InsightProvider getProvider() {
+        return provider;
+    }
+
+    /**
+     * @return the request
+     */
+    public InsightRequest getRequest() {
+        return request;
+    }
+
+    /**
+     * @return the score
+     */
+    public double getScore() {
+        return score;
+    }
+
+    /**
+     * @return the scoreDetails
+     */
+    public List<Message> getScoreDetails() {
+        return scoreDetails;
+    }
+
+    /**
+     * @return the display
+     */
+    public boolean isDisplay() {
+        return display;
+    }
+
+    public boolean isScored() {
+        return scored;
+    }
+
+    /**
+     * @return the skip
+     */
+    public boolean isSkip() {
+        return skip;
+    }
+
+    /**
+     * @return the succeeded
+     */
+    public boolean isSucceeded() {
+        return succeeded;
+    }
+
+    /**
+     * @param data the data to set
+     */
+    public void setData(Object data) {
+        this.data = data;
+    }
+
+    /**
+     * @param display the display to set
+     */
+    public void setDisplay(boolean display) {
+        this.display = display;
+    }
+
+    /**
+     * @param provider the provider to set
+     */
+    public void setProvider(InsightProvider provider) {
+        this.provider = provider;
+    }
+
+    /**
+     * @param request the request to set
+     */
+    public void setRequest(InsightRequest request) {
+        this.request = request;
+    }
+
+    /**
+     * @param score the score to set
+     */
+    public void setScore(double score) {
+        this.score = score;
+    }
+
+    public void setScored(boolean scored) {
+        this.scored = scored;
+    }
+
+    /**
+     * @param scoreDetails the scoreDetails to set
+     */
+    public void setScoreDetails(List<Message> scoreDetails) {
+        this.scoreDetails = scoreDetails;
+    }
+
+    /**
+     * @param skip the skip to set
+     */
+    public void setSkip(boolean skip) {
+        this.skip = skip;
+    }
+
+    /**
+     * @param succeeded the succeeded to set
+     */
+    public void setSucceeded(boolean succeeded) {
+        this.succeeded = succeeded;
+    }
+
+}
diff --git a/api/src/main/java/org/apache/sling/cms/insights/InsightFactory.java b/api/src/main/java/org/apache/sling/cms/insights/InsightFactory.java
new file mode 100644
index 0000000..f7d2e9b
--- /dev/null
+++ b/api/src/main/java/org/apache/sling/cms/insights/InsightFactory.java
@@ -0,0 +1,48 @@
+/*
+ * 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.cms.insights;
+
+import java.util.Collection;
+
+import org.apache.sling.cms.File;
+import org.apache.sling.cms.Page;
+
+/**
+ * Service Factory for retrieving insight instances.
+ */
+public interface InsightFactory {
+
+    /**
+     * Get the insights which are enabled for a particular file.
+     * 
+     * @param file the file for which to retrieve the insights
+     * @return the insights
+     */
+    Collection<Insight> getInsights(File file);
+    
+
+    /**
+     * Get the insights which are enabled for a particular page.
+     * 
+     * @param page the page for which to retrieve the insights
+     * @return the insights
+     */
+    Collection<Insight> getInsights(Page page);
+
+}
diff --git a/api/src/main/java/org/apache/sling/cms/insights/InsightProvider.java b/api/src/main/java/org/apache/sling/cms/insights/InsightProvider.java
new file mode 100644
index 0000000..baa5f4e
--- /dev/null
+++ b/api/src/main/java/org/apache/sling/cms/insights/InsightProvider.java
@@ -0,0 +1,56 @@
+/*
+ * 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.cms.insights;
+
+/**
+ * Interface for all of the Insight Provider services to implement.
+ */
+public interface InsightProvider {
+
+    /**
+     * Evaluate the request and return a response with the details.
+     * 
+     * @param request the request to evaluate
+     * @return the insights gathered
+     */
+    Insight evaluateRequest(InsightRequest request);
+
+    /**
+     * Get the ID for a particular Insight Provider. This should be human readable
+     * and URL-safe.
+     * 
+     * @return the check ID
+     */
+    String getId();
+
+    /**
+     * The title of the Insight Provider
+     * 
+     * @return the user-displayed title
+     */
+    String getTitle();
+
+    /**
+     * Returns true if the provider is enabled for this request and false otherwise.
+     * 
+     * @param request the request to evaluate
+     * @return true if enabled, false otherwise
+     */
+    boolean isEnabled(InsightRequest request);
+}
diff --git a/api/src/main/java/org/apache/sling/cms/insights/InsightRequest.java b/api/src/main/java/org/apache/sling/cms/insights/InsightRequest.java
new file mode 100644
index 0000000..bd310a9
--- /dev/null
+++ b/api/src/main/java/org/apache/sling/cms/insights/InsightRequest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.cms.insights;
+
+import org.apache.sling.api.resource.Resource;
+
+/**
+ * Represents an insight request
+ */
+public interface InsightRequest {
+
+    public enum TYPE {
+        FILE, PAGE
+    }
+
+    /**
+     * Gets the resource used to create this request.
+     * 
+     * @return
+     */
+    Resource getResource();
+
+    /**
+     * Get the type of this request.
+     * 
+     * @return the type
+     */
+    TYPE getType();
+}
diff --git a/api/src/main/java/org/apache/sling/cms/insights/Message.java b/api/src/main/java/org/apache/sling/cms/insights/Message.java
new file mode 100644
index 0000000..ff51cbb
--- /dev/null
+++ b/api/src/main/java/org/apache/sling/cms/insights/Message.java
@@ -0,0 +1,67 @@
+/*
+ * 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.cms.insights;
+
+/**
+ * Represents a message which may be assigned a status and text
+ */
+public class Message {
+
+    public enum STYLE {
+        DEFAULT, DANGER, WARN, SUCCESS
+    }
+
+    private STYLE style;
+
+    private String text;
+
+    public Message(String text, STYLE style) {
+        this.text = text;
+        this.style = style;
+    }
+
+    public STYLE getStyle() {
+        return style;
+    }
+
+    public String getStyleClass() {
+        String styleClass = "";
+        if (style != STYLE.DEFAULT) {
+            styleClass = "text-" + style.toString().toLowerCase();
+        }
+        return styleClass;
+    }
+
+    public String getText() {
+        return text;
+    }
+
+    public void setStyle(STYLE style) {
+        this.style = style;
+    }
+
+    public void setText(String text) {
+        this.text = text;
+    }
+
+    @Override
+    public String toString() {
+        return "Message [style=" + style + ", text=" + text + "]";
+    }
+}
diff --git a/api/src/main/java/org/apache/sling/cms/insights/PageInsightRequest.java b/api/src/main/java/org/apache/sling/cms/insights/PageInsightRequest.java
new file mode 100644
index 0000000..6a8054c
--- /dev/null
+++ b/api/src/main/java/org/apache/sling/cms/insights/PageInsightRequest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.cms.insights;
+
+import java.io.IOException;
+
+import org.apache.sling.cms.Page;
+import org.jsoup.nodes.Document;
+
+/**
+ * Represents an insight request
+ */
+public interface PageInsightRequest extends InsightRequest {
+
+    /**
+     * Gets the page backing this request
+     * 
+     * @return the page
+     */
+    public Page getPage();
+
+    /**
+     * Gets the HTML of the body of the page, not the full page (e.g. no header).
+     * 
+     * @return the HTML of the body
+     * @throws IOException an exception occurs retrieving the content
+     */
+    public String getPageBodyHtml() throws IOException;
+
+    /**
+     * Gets the page HTML as a JSoup Document.
+     * 
+     * @return the page HTML
+     * @throws IOException an exception occurs retrieving the content
+     */
+    public Document getPageDocument() throws IOException;
+
+    /**
+     * Gets the page HTML. Loads the HTML lazily so the initial request may take a
+     * bit but others will be quick.
+     * 
+     * @return a string representation of the HTML for a page
+     * @throws ServletException
+     * @throws IOException
+     */
+    public String getPageHtml() throws IOException;
+
+    public default TYPE getType() {
+        return TYPE.PAGE;
+    }
+}
diff --git a/api/src/main/java/org/apache/sling/cms/insights/package-info.java b/api/src/main/java/org/apache/sling/cms/insights/package-info.java
new file mode 100644
index 0000000..07fd7d0
--- /dev/null
+++ b/api/src/main/java/org/apache/sling/cms/insights/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+/**
+ * Support for Content Insights
+ *
+ * @version 0.10.2
+ */
+package org.apache.sling.cms.insights;
\ No newline at end of file
diff --git a/core/pom.xml b/core/pom.xml
index 640e9f2..5e04613 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -186,5 +186,9 @@
             <groupId>org.apache.jackrabbit</groupId>
             <artifactId>oak-core</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.jsoup</groupId>
+            <artifactId>jsoup</artifactId>
+        </dependency>
     </dependencies>
 </project>
\ No newline at end of file
diff --git a/core/src/main/java/com/ipeirotis/readability/engine/Readability.java b/core/src/main/java/com/ipeirotis/readability/engine/Readability.java
new file mode 100644
index 0000000..9a639d9
--- /dev/null
+++ b/core/src/main/java/com/ipeirotis/readability/engine/Readability.java
@@ -0,0 +1,231 @@
+package com.ipeirotis.readability.engine;
+
+import java.math.BigDecimal;
+
+
+/**
+ * Implements various readability indexes
+ * 
+ * @author Panos Ipeirotis
+ * 
+ */
+public class Readability {
+
+	private static SentenceExtractor se = new SentenceExtractor();
+
+	Integer sentences;
+
+	Integer complex;
+
+	Integer words;
+
+	Integer syllables;
+
+	Integer characters;
+
+
+	public Readability(String text) {
+
+		// We add the "." for the sentence extractor to pick the last sentence,
+		// if it does not end with a punctuation mark.
+		this.sentences = getNumberOfSentences(text + ".");
+		this.complex = getNumberOfComplexWords(text);
+		this.words = getNumberOfWords(text);
+		this.syllables = getNumberOfSyllables(text);
+		this.characters = getNumberOfCharacters(text);
+
+	}
+
+	/**
+	 * Returns true is the word contains 3 or more syllables
+	 * 
+	 * @param w
+	 * @return
+	 */
+	public static boolean isComplex(String w) {
+		int syllables = Syllabify.syllable(w);
+		return (syllables > 2);
+	}
+
+	/**
+	 * Returns the number of letter characters in the text
+	 * 
+	 * @return
+	 */
+	public static int getNumberOfCharacters(String text) {
+		String cleanText = cleanLine(text);
+		String[] word = cleanText.split(" ");
+
+		int characters = 0;
+		for (String w : word) {
+			characters += w.length();
+		}
+		return characters;
+	}
+
+	/**
+	 * Returns the number of words with 3 or more syllables
+	 * 
+	 * @param text
+	 * @return the number of words in the text with 3 or more syllables
+	 */
+	public static int getNumberOfComplexWords(String text) {
+		String cleanText = cleanLine(text);
+		String[] words = cleanText.split(" ");
+		int complex = 0;
+		for (String w : words) {
+			if (isComplex(w))
+				complex++;
+		}
+		return complex;
+	}
+
+	public static int getNumberOfWords(String text) {
+		String cleanText = cleanLine(text);
+		String[] word = cleanText.split(" ");
+		int words = 0;
+		for (String w : word) {
+			if (w.length() > 0)
+				words++;
+		}
+		return words;
+	}
+
+	/**
+	 * Returns the total number of syllables in the words of the text
+	 * 
+	 * @param text
+	 * @return the total number of syllables in the words of the text
+	 */
+	public static int getNumberOfSyllables(String text) {
+		String cleanText = cleanLine(text);
+		String[] word = cleanText.split(" ");
+		int syllables = 0;
+		for (String w : word) {
+			if (w.length() > 0) {
+				syllables += Syllabify.syllable(w);
+			}
+		}
+		return syllables;
+	}
+
+	private static String cleanLine(String line) {
+		StringBuffer buffer = new StringBuffer();
+		for (int i = 0; i < line.length(); i++) {
+			char c = line.charAt(i);
+			if (c < 128 && Character.isLetter(c)) {
+				buffer.append(c);
+			} else {
+				buffer.append(' ');
+			}
+		}
+		return buffer.toString().toLowerCase();
+	}
+
+	public static int getNumberOfSentences(String text) {
+		int l = se.getSentences(text).length;
+		if (l > 0)
+			return l;
+		else if (text.length() > 0)
+			return 1;
+		else
+			return 0;
+	}
+
+	/**
+	 * 
+	 * http://en.wikipedia.org/wiki/SMOG_Index
+	 * 
+	 * @param text
+	 * @return The SMOG index of the text
+	 */
+	public double getSMOGIndex() {
+		double score = Math.sqrt(complex * (30.0 / sentences)) + 3;
+		return round(score, 3);
+	}
+
+	/**
+	 * 
+	 * http://en.wikipedia.org/wiki/SMOG
+	 * 
+	 * @param text
+	 * @return Retugns the SMOG value for the text
+	 */
+	public double getSMOG() {
+		double score = 1.043 * Math.sqrt(complex * (30.0 / sentences)) + 3.1291;
+		return round(score, 3);
+	}
+
+	/**
+	 * 
+	 * http://en.wikipedia.org/wiki/Flesch-Kincaid_Readability_Test
+	 * 
+	 * @param text
+	 * @return Returns the Flesch_Reading Ease value for the text
+	 */
+	public double getFleschReadingEase() {
+
+		double score = 206.835 - 1.015 * words / sentences - 84.6 * syllables
+				/ words;
+
+		return round(score, 3);
+	}
+
+	/**
+	 * 
+	 * http://en.wikipedia.org/wiki/Flesch-Kincaid_Readability_Test
+	 * 
+	 * @param text
+	 * @return Returns the Flesch-Kincaid_Readability_Test value for the text
+	 */
+	public double getFleschKincaidGradeLevel() {
+		double score = 0.39 * words / sentences + 11.8 * syllables / words
+				- 15.59;
+		return round(score, 3);
+	}
+
+	/**
+	 * 
+	 * http://en.wikipedia.org/wiki/Automated_Readability_Index
+	 * 
+	 * @param text
+	 * @return the Automated Readability Index for text
+	 */
+	public double getARI() {
+		double score = 4.71 * characters / words + 0.5 * words / sentences
+				- 21.43;
+		return round(score, 3);
+	}
+
+	/**
+	 * 
+	 * http://en.wikipedia.org/wiki/Gunning-Fog_Index
+	 * 
+	 * @param text
+	 * @return the Gunning-Fog Index for text
+	 */
+	public double getGunningFog() {
+		double score = 0.4 * (words / sentences + 100 * complex / words);
+		return round(score, 3);
+	}
+
+	/**
+	 * 
+	 * http://en.wikipedia.org/wiki/Coleman-Liau_Index
+	 * 
+	 * @return The Coleman-Liau_Index value for the text
+	 */
+	public double getColemanLiau() {
+		double score = (5.89 * characters / words) - (30 * sentences / words)
+				- 15.8;
+		return round(score, 3);
+	}
+
+	private static Double round(double d, int decimalPlace) {
+		// see the Javadoc about why we use a String in the constructor
+		// http://java.sun.com/j2se/1.5.0/docs/api/java/math/BigDecimal.html#BigDecimal(double)
+		BigDecimal bd = new BigDecimal(Double.toString(d));
+		bd = bd.setScale(decimalPlace, BigDecimal.ROUND_HALF_UP);
+		return bd.doubleValue();
+	}
+} 
\ No newline at end of file
diff --git a/core/src/main/java/com/ipeirotis/readability/engine/SentenceExtractor.java b/core/src/main/java/com/ipeirotis/readability/engine/SentenceExtractor.java
new file mode 100644
index 0000000..33fd722
--- /dev/null
+++ b/core/src/main/java/com/ipeirotis/readability/engine/SentenceExtractor.java
@@ -0,0 +1,48 @@
+package com.ipeirotis.readability.engine;
+
+import java.util.ArrayList;
+
+import com.aliasi.sentences.IndoEuropeanSentenceModel;
+import com.aliasi.sentences.SentenceModel;
+import com.aliasi.tokenizer.IndoEuropeanTokenizerFactory;
+import com.aliasi.tokenizer.Tokenizer;
+import com.aliasi.tokenizer.TokenizerFactory;
+
+/** Use SentenceModel to find sentence boundaries in text */
+public class SentenceExtractor {
+
+	final TokenizerFactory TOKENIZER_FACTORY = new IndoEuropeanTokenizerFactory();
+	final SentenceModel SENTENCE_MODEL = new IndoEuropeanSentenceModel();
+
+	public String[] getSentences(String text) {
+		ArrayList<String> tokenList = new ArrayList<String>();
+		ArrayList<String> whiteList = new ArrayList<String>();
+		Tokenizer tokenizer = TOKENIZER_FACTORY.tokenizer(text.toCharArray(), 0, text.length());
+		tokenizer.tokenize(tokenList, whiteList);
+
+		String[] tokens = new String[tokenList.size()];
+		String[] whites = new String[whiteList.size()];
+		tokenList.toArray(tokens);
+		whiteList.toArray(whites);
+		int[] sentenceBoundaries = SENTENCE_MODEL.boundaryIndices(tokens, whites);
+
+		if (sentenceBoundaries.length < 1) {
+			return new String[0];
+		}
+
+		String[] result = new String[sentenceBoundaries.length];
+
+		int sentStartTok = 0;
+		int sentEndTok = 0;
+		for (int i = 0; i < sentenceBoundaries.length; ++i) {
+			sentEndTok = sentenceBoundaries[i];
+			StringBuffer sb = new StringBuffer();
+			for (int j = sentStartTok; j <= sentEndTok; j++) {
+				sb.append(tokens[j] + whites[j + 1]);
+			}
+			result[i] = sb.toString();
+			sentStartTok = sentEndTok + 1;
+		}
+		return result;
+	}
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/ipeirotis/readability/engine/Syllabify.java b/core/src/main/java/com/ipeirotis/readability/engine/Syllabify.java
new file mode 100644
index 0000000..0cb89b8
--- /dev/null
+++ b/core/src/main/java/com/ipeirotis/readability/engine/Syllabify.java
@@ -0,0 +1,73 @@
+package com.ipeirotis.readability.engine;
+
+/*
+ * Created on Jun 5, 2007
+ *
+ */
+
+/**
+ * @author Panos Ipeirotis
+ * 
+ *         Java Code to estimate the number of syllables in a word.
+ * 
+ *         Translation of the Perl code by Greg Fast, found at:
+ *         http://search.cpan.org/author/GREGFAST/Lingua-EN-Syllable-0.251/
+ * 
+ *         For documentation and comments
+ *         http://search.cpan.org/src/GREGFAST/Lingua
+ *         -EN-Syllable-0.251/Syllable.pm
+ * 
+ */
+public class Syllabify {
+
+	static String[] SubSyl = { "cial", "tia", "cius", "cious", "giu", "ion", "iou", "sia$", ".ely$" };
+
+	static String[] AddSyl = { "ia", "riet", "dien", "iu", "io", "ii", "[aeiouym]bl$", "[aeiou]{3}", "^mc", "ism$",
+			"[^aeiouy][^aeiouy]l$", "[^l]lien", "^coa[dglx].", "[^gq]ua[^auieo]", "dnt$" };
+
+	public static int syllable(String word) {
+
+		word = word.toLowerCase();
+		word = word.replaceAll("'", " ");
+
+		if (word.equals("i"))
+			return 1;
+		if (word.equals("a"))
+			return 1;
+
+		if (word.endsWith("e")) {
+			word = word.substring(0, word.length() - 1);
+		}
+
+		String[] phonems = word.split("[^aeiouy]+");
+
+		int syl = 0;
+		for (int i = 0; i < SubSyl.length; i++) {
+			String syllabe = SubSyl[i];
+			if (word.matches(syllabe)) {
+				syl--;
+			}
+		}
+		for (int i = 0; i < AddSyl.length; i++) {
+			String syllabe = AddSyl[i];
+			if (word.matches(syllabe)) {
+				syl++;
+			}
+		}
+		if (word.length() == 1) {
+			syl++;
+		}
+
+		for (int i = 0; i < phonems.length; i++) {
+			if (phonems[i].length() > 0)
+				syl++;
+		}
+
+		if (syl == 0) {
+			syl = 1;
+		}
+
+		return syl;
+	}
+
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/apache/sling/cms/core/insights/impl/BaseInsightProvider.java b/core/src/main/java/org/apache/sling/cms/core/insights/impl/BaseInsightProvider.java
new file mode 100644
index 0000000..c8fd6d5
--- /dev/null
+++ b/core/src/main/java/org/apache/sling/cms/core/insights/impl/BaseInsightProvider.java
@@ -0,0 +1,63 @@
+/*
+ * 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.cms.core.insights.impl;
+
+import org.apache.sling.cms.insights.Insight;
+import org.apache.sling.cms.insights.InsightProvider;
+import org.apache.sling.cms.insights.InsightRequest;
+import org.apache.sling.cms.insights.Message;
+import org.apache.sling.cms.insights.Message.STYLE;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Simple base for the Insight Providers
+ */
+public abstract class BaseInsightProvider implements InsightProvider {
+
+    private static final Logger log = LoggerFactory.getLogger(BaseInsightProvider.class);
+
+    /**
+     * Method for the extending classes to implement, this can safely throw
+     * exceptions and this will trigger a failure result to be returned.
+     * 
+     * @param request the request to evaluate
+     * @return the result of evaluation
+     * @throws Exception any exception
+     */
+    protected abstract Insight doEvaluateRequest(InsightRequest request) throws Exception;
+
+    @Override
+    public Insight evaluateRequest(InsightRequest request) {
+        log.trace("evaluatePage");
+        Insight insight = null;
+        try {
+            insight = this.doEvaluateRequest(request);
+        } catch (Exception e) {
+            log.error("Unable to perform check " + getTitle() + " for request " + request, e);
+            insight = new Insight(this, request);
+            insight.getScoreDetails().add(new Message(
+                    "Unable to perform check " + getTitle() + " due to unexpected exception", STYLE.DANGER));
+            insight.setSucceeded(false);
+            insight.setScored(false);
+
+        }
+        return insight;
+    }
+}
diff --git a/core/src/main/java/org/apache/sling/cms/core/insights/impl/FakeHttpSession.java b/core/src/main/java/org/apache/sling/cms/core/insights/impl/FakeHttpSession.java
new file mode 100644
index 0000000..ec7a192
--- /dev/null
+++ b/core/src/main/java/org/apache/sling/cms/core/insights/impl/FakeHttpSession.java
@@ -0,0 +1,101 @@
+/*
+ * 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.cms.core.insights.impl;
+
+import java.util.Enumeration;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionContext;
+
+/**
+ * A fake session to use for making server-side requests.
+ */
+@SuppressWarnings("deprecation")
+public class FakeHttpSession implements HttpSession {
+
+    public long getCreationTime() {
+        throw new UnsupportedOperationException();
+    }
+
+    public String getId() {
+        throw new UnsupportedOperationException();
+    }
+
+    public long getLastAccessedTime() {
+        throw new UnsupportedOperationException();
+    }
+
+    public ServletContext getServletContext() {
+        throw new UnsupportedOperationException();
+    }
+
+    public void setMaxInactiveInterval(int i) {
+        throw new UnsupportedOperationException();
+    }
+
+    public int getMaxInactiveInterval() {
+        throw new UnsupportedOperationException();
+    }
+
+    public HttpSessionContext getSessionContext() {
+        throw new UnsupportedOperationException();
+    }
+
+    public Object getAttribute(String s) {
+        throw new UnsupportedOperationException();
+    }
+
+    public Object getValue(String s) {
+        throw new UnsupportedOperationException();
+    }
+
+    public Enumeration<?> getAttributeNames() {
+        throw new UnsupportedOperationException();
+    }
+
+    public String[] getValueNames() {
+        throw new UnsupportedOperationException();
+    }
+
+    public void setAttribute(String s, Object o) {
+        throw new UnsupportedOperationException();
+    }
+
+    public void putValue(String s, Object o) {
+        throw new UnsupportedOperationException();
+    }
+
+    public void removeAttribute(String s) {
+        throw new UnsupportedOperationException();
+    }
+
+    public void removeValue(String s) {
+        throw new UnsupportedOperationException();
+    }
+
+    public void invalidate() {
+        throw new UnsupportedOperationException();
+    }
+
+    public boolean isNew() {
+        throw new UnsupportedOperationException();
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/apache/sling/cms/core/insights/impl/FakeRequest.java b/core/src/main/java/org/apache/sling/cms/core/insights/impl/FakeRequest.java
new file mode 100644
index 0000000..32c3a4c
--- /dev/null
+++ b/core/src/main/java/org/apache/sling/cms/core/insights/impl/FakeRequest.java
@@ -0,0 +1,298 @@
+/*
+ * 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.cms.core.insights.impl;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.Principal;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+/**
+ * Simple class for faking a HttpServletRequest
+ */
+public class FakeRequest implements HttpServletRequest {
+
+    private final Map<String,Object> attributes;
+
+    private final String method;
+
+    private final Map<String,Object> parameters;
+
+    private final String path;
+
+    private final HttpSession session;
+
+    public FakeRequest(String method, String path) {
+        this.method = method;
+        this.path = path;
+        attributes = new HashMap<>();
+        parameters = new HashMap<>();
+        session = new FakeHttpSession();
+    }
+
+    public FakeRequest(String method, String path, Map<String,Object> params) {
+        this.method = method;
+        this.path = path;
+        attributes = new HashMap<>();
+        parameters = new HashMap<>();
+        session = new FakeHttpSession();
+        for (Iterator<String> iterator = params.keySet().iterator(); iterator.hasNext();) {
+            String key = iterator.next();
+            Object value = params.get(key);
+            if (params.get(key) instanceof String[])
+                parameters.put(key, (String[]) (String[]) value);
+            else
+                parameters.put(key, new String[] { value.toString() });
+        }
+
+    }
+
+    public Object getAttribute(String name) {
+        return attributes.get(name);
+    }
+
+    public Enumeration<?> getAttributeNames() {
+        return Collections.enumeration(attributes.keySet());
+    }
+
+    public String getAuthType() {
+        return null;
+    }
+
+    public String getCharacterEncoding() {
+        return "utf-8";
+    }
+
+    public int getContentLength() {
+        return 0;
+    }
+
+    public String getContentType() {
+        return null;
+    }
+
+    public String getContextPath() {
+        return "";
+    }
+
+    public Cookie[] getCookies() {
+        return new Cookie[0];
+    }
+
+    public long getDateHeader(String name) {
+        return -1L;
+    }
+
+    public String getHeader(String name) {
+        return null;
+    }
+
+    public Enumeration<?> getHeaderNames() {
+        return null;
+    }
+
+    public Enumeration<?> getHeaders(String name) {
+        return null;
+    }
+
+    public ServletInputStream getInputStream() throws IOException {
+        return null;
+    }
+
+    public int getIntHeader(String name) {
+        return -1;
+    }
+
+    public String getLocalAddr() {
+        return null;
+    }
+
+    public Locale getLocale() {
+        return Locale.getDefault();
+    }
+
+    public Enumeration<?> getLocales() {
+        return Collections.enumeration(Collections.singleton(Locale.getDefault()));
+    }
+
+    public String getLocalName() {
+        return null;
+    }
+
+    public int getLocalPort() {
+        return 0;
+    }
+
+    public String getMethod() {
+        return method;
+    }
+
+    public String getParameter(String name) {
+        Object value;
+        try {
+            value = parameters.get(name);
+            if (value instanceof String[]) {
+                return ((String[]) value)[0];
+            }
+        } catch (ClassCastException e) {
+            return null;
+        }
+        return (String) value;
+    }
+
+    public Map<String,Object> getParameterMap() {
+        return parameters;
+    }
+
+    public Enumeration<?> getParameterNames() {
+        return Collections.enumeration(parameters.keySet());
+    }
+
+    public String[] getParameterValues(String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    public String getPathInfo() {
+        return null;
+    }
+
+    public String getPathTranslated() {
+        return null;
+    }
+
+    public String getProtocol() {
+        return "HTTP/1.1";
+    }
+
+    public String getQueryString() {
+        return null;
+    }
+
+    public BufferedReader getReader() throws IOException {
+        return null;
+    }
+
+    public String getRealPath(String path) {
+        return null;
+    }
+
+    public String getRemoteAddr() {
+        return null;
+    }
+
+    public String getRemoteHost() {
+        return null;
+    }
+
+    public int getRemotePort() {
+        return 0;
+    }
+
+    public String getRemoteUser() {
+        return null;
+    }
+
+    public RequestDispatcher getRequestDispatcher(String path) {
+        return null;
+    }
+
+    public String getRequestedSessionId() {
+        return null;
+    }
+
+    public String getRequestURI() {
+        return path;
+    }
+
+    public StringBuffer getRequestURL() {
+        return new StringBuffer("http://localhost:8080" + path);
+    }
+
+    public String getScheme() {
+        return "http";
+    }
+
+    public String getServerName() {
+        return null;
+    }
+
+    public int getServerPort() {
+        return 0;
+    }
+
+    public String getServletPath() {
+        return path;
+    }
+
+    public HttpSession getSession() {
+        return session;
+    }
+
+    public HttpSession getSession(boolean create) {
+        return session;
+    }
+
+    public Principal getUserPrincipal() {
+        return null;
+    }
+
+    public boolean isRequestedSessionIdFromCookie() {
+        return false;
+    }
+
+    public boolean isRequestedSessionIdFromUrl() {
+        return false;
+    }
+
+    public boolean isRequestedSessionIdFromURL() {
+        return false;
+    }
+
+    public boolean isRequestedSessionIdValid() {
+        return false;
+    }
+
+    public boolean isSecure() {
+        return false;
+    }
+    public boolean isUserInRole(String role) {
+        return false;
+    }
+    public void removeAttribute(String name) {
+        attributes.remove(name);
+    }
+    public void setAttribute(String name, Object o) {
+        attributes.put(name, o);
+    }
+    public void setCharacterEncoding(String s) throws UnsupportedEncodingException {
+        // do nothing
+    }
+}
diff --git a/core/src/main/java/org/apache/sling/cms/core/insights/impl/FakeResponse.java b/core/src/main/java/org/apache/sling/cms/core/insights/impl/FakeResponse.java
new file mode 100644
index 0000000..9de5a02
--- /dev/null
+++ b/core/src/main/java/org/apache/sling/cms/core/insights/impl/FakeResponse.java
@@ -0,0 +1,189 @@
+/*
+ * 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.cms.core.insights.impl;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Locale;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * A fake response for making internal requests
+ */
+public class FakeResponse implements HttpServletResponse {
+
+    private String charset;
+
+    private String contentType;
+
+    private ServletOutputStream outputStream;
+
+    private PrintWriter printWriter;
+
+    private final MessageDigest md;
+
+    public FakeResponse(final OutputStream out) throws NoSuchAlgorithmException {
+
+        md = MessageDigest.getInstance("MD5");
+
+        outputStream = new ServletOutputStream() {
+
+            @Override
+            public void write(int b) throws IOException {
+                out.write(b);
+                md.update((byte) b);
+            }
+
+            @Override
+            public void flush() throws IOException {
+                out.flush();
+            }
+
+            @Override
+            public void close() throws IOException {
+                out.close();
+            }
+        };
+    }
+
+    public void addCookie(Cookie cookie1) {
+    }
+
+    public void addDateHeader(String s, long l) {
+    }
+
+    public void addHeader(String s, String s1) {
+    }
+
+    public void addIntHeader(String s, int i) {
+    }
+
+    public boolean containsHeader(String name) {
+        return false;
+    }
+
+    public String encodeRedirectUrl(String url) {
+        return null;
+    }
+
+    public String encodeRedirectURL(String url) {
+        return null;
+    }
+
+    public String encodeUrl(String url) {
+        return null;
+    }
+
+    public String encodeURL(String url) {
+        return null;
+    }
+
+    public void flushBuffer() throws IOException {
+    }
+
+    public int getBufferSize() {
+        return 0;
+    }
+
+    public String getCharacterEncoding() {
+        return charset;
+    }
+
+    public String getContentType() {
+        return contentType;
+    }
+
+    public Locale getLocale() {
+        return null;
+    }
+
+    public String getMD5() {
+        return new String(md.digest());
+    }
+
+    public ServletOutputStream getOutputStream() throws IOException {
+        return outputStream;
+    }
+
+    public PrintWriter getWriter() throws IOException {
+        if (printWriter == null) {
+            java.io.Writer osWriter = new OutputStreamWriter(getOutputStream(), "UTF-8");
+            printWriter = new PrintWriter(osWriter, true);
+        }
+        return printWriter;
+    }
+
+    public boolean isCommitted() {
+        return false;
+    }
+
+    public void reset() {
+    }
+
+    public void resetBuffer() {
+    }
+
+    public void sendError(int i) throws IOException {
+    }
+
+    public void sendError(int i, String s) throws IOException {
+    }
+
+    public void sendRedirect(String s) throws IOException {
+    }
+
+    public void setBufferSize(int i) {
+    }
+
+    public void setCharacterEncoding(String charset) {
+        this.charset = charset;
+    }
+
+    public void setContentLength(int i) {
+    }
+
+    public void setContentType(String type) {
+        contentType = type;
+    }
+
+    public void setDateHeader(String s, long l) {
+    }
+
+    public void setHeader(String s, String s1) {
+    }
+
+    public void setIntHeader(String s, int i) {
+    }
+
+    public void setLocale(Locale locale) {
+    }
+
+    public void setStatus(int i) {
+    }
+
+    public void setStatus(int i, String s) {
+    }
+}
diff --git a/core/src/main/java/org/apache/sling/cms/core/insights/impl/FileInsightRequestImpl.java b/core/src/main/java/org/apache/sling/cms/core/insights/impl/FileInsightRequestImpl.java
new file mode 100644
index 0000000..ce3bd85
--- /dev/null
+++ b/core/src/main/java/org/apache/sling/cms/core/insights/impl/FileInsightRequestImpl.java
@@ -0,0 +1,46 @@
+/*
+ * 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.cms.core.insights.impl;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.cms.File;
+import org.apache.sling.cms.insights.FileInsightRequest;
+
+/**
+ * Implementation of the FileInsightRequest interface
+ */
+public class FileInsightRequestImpl implements FileInsightRequest {
+
+    private File file;
+
+    public FileInsightRequestImpl(File file) {
+        this.file = file;
+    }
+
+    @Override
+    public Resource getResource() {
+        return file.getResource();
+    }
+
+    @Override
+    public File getFile() {
+        return file;
+    }
+
+}
diff --git a/core/src/main/java/org/apache/sling/cms/core/insights/impl/InsightFactoryImpl.java b/core/src/main/java/org/apache/sling/cms/core/insights/impl/InsightFactoryImpl.java
new file mode 100644
index 0000000..7f63bfc
--- /dev/null
+++ b/core/src/main/java/org/apache/sling/cms/core/insights/impl/InsightFactoryImpl.java
@@ -0,0 +1,98 @@
+/*
+ * 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.cms.core.insights.impl;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.sling.cms.File;
+import org.apache.sling.cms.Page;
+import org.apache.sling.cms.insights.Insight;
+import org.apache.sling.cms.insights.InsightFactory;
+import org.apache.sling.cms.insights.InsightProvider;
+import org.apache.sling.cms.insights.InsightRequest;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(immediate = true, service = { InsightFactory.class, ServiceListener.class })
+public class InsightFactoryImpl implements InsightFactory, ServiceListener {
+
+    private static final Logger log = LoggerFactory.getLogger(InsightFactoryImpl.class);
+    private static final Map<String, InsightProvider> insightProviders = new HashMap<>();
+
+    @Activate
+    public void activate(ComponentContext context) throws InvalidSyntaxException {
+        log.info("activate");
+        insightProviders.clear();
+        String filter = "(" + Constants.OBJECTCLASS + "=" + InsightProvider.class.getName() + ")";
+        context.getBundleContext().addServiceListener(this, filter);
+        @SuppressWarnings("unchecked")
+        ServiceReference<InsightProvider>[] serviceReferences = (ServiceReference<InsightProvider>[]) context
+                .getBundleContext().getServiceReferences(InsightProvider.class.getName(), null);
+        if (serviceReferences != null) {
+            Stream.of(serviceReferences).map(sr -> context.getBundleContext().getService(sr))
+                    .forEach(provider -> insightProviders.put(provider.getId(), provider));
+        }
+        log.info("Check Service Listener registered successfully!");
+    }
+
+    @Override
+    public void serviceChanged(ServiceEvent event) {
+        @SuppressWarnings("unchecked")
+        ServiceReference<InsightProvider> reference = (ServiceReference<InsightProvider>) event.getServiceReference();
+        InsightProvider provider = (InsightProvider) event.getServiceReference().getBundle().getBundleContext()
+                .getService(reference);
+        synchronized (this) {
+            log.info("Unregistering {}", provider.getId());
+            insightProviders.remove(provider.getId());
+            if (event.getType() == ServiceEvent.MODIFIED || event.getType() == ServiceEvent.REGISTERED) {
+                log.info("Registering {}", provider.getId());
+                insightProviders.put(provider.getId(), provider);
+            }
+        }
+    }
+
+    @Override
+    public Collection<Insight> getInsights(File file) {
+        return getInsights(new FileInsightRequestImpl(file));
+    }
+
+    @Override
+    public Collection<Insight> getInsights(Page page) {
+        return getInsights(new PageInsightRequestImpl(page));
+    }
+
+    private Collection<Insight> getInsights(InsightRequest request) {
+        return insightProviders.values().stream().filter(ip -> ip.isEnabled(request))
+                .map(ip -> ip.evaluateRequest(request)).collect(Collectors.toList());
+
+    }
+
+}
diff --git a/core/src/main/java/org/apache/sling/cms/core/insights/impl/PageInsightRequestImpl.java b/core/src/main/java/org/apache/sling/cms/core/insights/impl/PageInsightRequestImpl.java
new file mode 100644
index 0000000..59ca53b
--- /dev/null
+++ b/core/src/main/java/org/apache/sling/cms/core/insights/impl/PageInsightRequestImpl.java
@@ -0,0 +1,107 @@
+/*
+ * 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.cms.core.insights.impl;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.inject.Inject;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.cms.Page;
+import org.apache.sling.cms.insights.PageInsightRequest;
+import org.apache.sling.engine.SlingRequestProcessor;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implementation of the PageInsightRequest interface
+ */
+public class PageInsightRequestImpl implements PageInsightRequest {
+
+    private static final Logger log = LoggerFactory.getLogger(PageInsightRequestImpl.class);
+
+    private Map<String, String> markupCache = new HashMap<>();
+
+    private final Page page;
+
+    @Inject
+    private SlingRequestProcessor requestProcessor;
+
+    private final ResourceResolver resourceResolver;
+
+    public PageInsightRequestImpl(Page page) {
+        this.page = page;
+        this.resourceResolver = page.getResource().getResourceResolver();
+    }
+
+    private String getContents(String url) {
+        log.trace("getLocalPageHTML");
+        if (!markupCache.containsKey(url)) {
+            String requestPath = page.getPath() + ".html";
+            log.debug("Loading local page HTML from {}", requestPath);
+            HttpServletRequest req = new FakeRequest("GET", requestPath);
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+            HttpServletResponse resp;
+            try {
+                resp = new FakeResponse(out);
+                requestProcessor.processRequest(req, resp, resourceResolver);
+            } catch (ServletException | IOException | NoSuchAlgorithmException e) {
+                log.warn("Exception retrieving page contents for {}", url, e);
+            }
+            markupCache.put(url, out.toString());
+        }
+        return markupCache.get(url);
+    }
+
+    @Override
+    public Page getPage() {
+        return this.page;
+    }
+
+    @Override
+    public String getPageBodyHtml() throws IOException {
+        return getContents(page.getPath() + "/jcr:content/container.html");
+    }
+
+    @Override
+    public Document getPageDocument() throws IOException {
+        return Jsoup.parse(getPageHtml());
+    }
+
+    @Override
+    public String getPageHtml() throws IOException {
+        return getContents(page.getPath() + ".html");
+    }
+
+    @Override
+    public Resource getResource() {
+        return page.getResource();
+    }
+
+}
diff --git a/core/src/main/java/org/apache/sling/cms/core/insights/impl/ReadabilityInsightProvider.java b/core/src/main/java/org/apache/sling/cms/core/insights/impl/ReadabilityInsightProvider.java
new file mode 100644
index 0000000..ef7662f
--- /dev/null
+++ b/core/src/main/java/org/apache/sling/cms/core/insights/impl/ReadabilityInsightProvider.java
@@ -0,0 +1,5 @@
+package org.apache.sling.cms.core.insights.impl;
+
+public class ReadabilityInsightProvider {
+
+}
diff --git a/core/src/main/java/org/apache/sling/cms/core/internal/models/PageImpl.java b/core/src/main/java/org/apache/sling/cms/core/internal/models/FileImpl.java
similarity index 62%
copy from core/src/main/java/org/apache/sling/cms/core/internal/models/PageImpl.java
copy to core/src/main/java/org/apache/sling/cms/core/internal/models/FileImpl.java
index 1de2819..2e2e21f 100644
--- a/core/src/main/java/org/apache/sling/cms/core/internal/models/PageImpl.java
+++ b/core/src/main/java/org/apache/sling/cms/core/internal/models/FileImpl.java
@@ -16,21 +16,14 @@
  */
 package org.apache.sling.cms.core.internal.models;
 
-import java.util.ArrayList;
 import java.util.Calendar;
-import java.util.List;
 
 import javax.inject.Inject;
 import javax.inject.Named;
 
-import org.apache.commons.lang.StringUtils;
-import org.apache.jackrabbit.JcrConstants;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ValueMap;
-import org.apache.sling.cms.CMSConstants;
-import org.apache.sling.cms.CMSUtils;
-import org.apache.sling.cms.Page;
-import org.apache.sling.cms.PageTemplate;
+import org.apache.sling.cms.File;
 import org.apache.sling.cms.Site;
 import org.apache.sling.cms.SiteManager;
 import org.apache.sling.models.annotations.Default;
@@ -38,19 +31,10 @@ import org.apache.sling.models.annotations.Model;
 import org.apache.sling.models.annotations.Optional;
 
 /**
- * A model representing a page.
+ * A model representing a file
  */
-@Model(adaptables = Resource.class, adapters = Page.class)
-public class PageImpl implements Page {
-
-    public static final Page getContainingPage(Resource resource) {
-        Resource pageRsrc = CMSUtils.findParentResourceofType(resource, CMSConstants.NT_PAGE);
-        Page page = null;
-        if (pageRsrc != null) {
-            page = pageRsrc.adaptTo(Page.class);
-        }
-        return page;
-    }
+@Model(adaptables = Resource.class, adapters = File.class)
+public class FileImpl implements File {
 
     @Inject
     @Optional
@@ -58,6 +42,10 @@ public class PageImpl implements Page {
     private Resource contentResource;
 
     @Inject
+    @Named("jcr:content/jcr:mimeType")
+    private String contentType;
+
+    @Inject
     @Optional
     @Named("jcr:content/jcr:created")
     private Calendar created;
@@ -85,86 +73,74 @@ public class PageImpl implements Page {
     protected Resource resource;
 
     @Inject
-    @Optional
-    @Named(JcrConstants.JCR_CONTENT + "/" + CMSConstants.PN_TAXONOMY)
-    private String[] taxonomy;
-
-    @Inject
-    @Optional
-    @Named("jcr:content/sling:template")
-    private String template;
-
-    @Inject
-    @Named("jcr:content/jcr:title")
-    @Optional
-    private String title;
-
-    @Inject
     @Named("jcr:primaryType")
     private String type;
 
-    public PageImpl(Resource resource) {
+    public FileImpl(Resource resource) {
         this.resource = resource;
     }
 
+    @Override
     public Resource getContentResource() {
         return contentResource;
     }
 
+    @Override
+    public String getContentType() {
+        return contentType;
+    }
+
+    @Override
     public Calendar getCreated() {
         return created;
     }
 
+    @Override
     public String getCreatedBy() {
         return createdBy;
     }
 
-    public String[] getKeywords() {
-        List<String> keywords = new ArrayList<>();
-        if (taxonomy != null) {
-            for (String item : taxonomy) {
-                Resource resource = this.resource.getResourceResolver().getResource(item);
-                if (resource != null) {
-                    keywords.add(resource.getValueMap().get(CMSConstants.PN_TITLE, String.class));
-                }
-            }
-        }
-        return keywords.toArray(new String[keywords.size()]);
-    }
-
+    @Override
     public Calendar getLastModified() {
         return lastModified != null ? lastModified : created;
     }
 
+    @Override
     public String getLastModifiedBy() {
         return lastModifiedBy != null ? lastModifiedBy : createdBy;
     }
 
+    @Override
     public String getName() {
         return resource.getName();
     }
 
+    @Override
     public Resource getParent() {
         return resource.getParent();
     }
 
+    @Override
     public String getPath() {
         return resource.getPath();
     }
 
+    @Override
     public ValueMap getProperties() {
         return getContentResource().getValueMap();
     }
 
+    @Override
     public String getPublishedPath() {
         Site site = getSite();
         if (site != null) {
-            return resource.getPath().replace(site.getPath(), "") + ".html";
+            return resource.getPath().replace(site.getPath(), "");
         } else {
-            return resource.getPath() + ".html";
+            return resource.getPath();
         }
     }
 
+    @Override
     public String getPublishedUrl() {
         Site site = getSite();
         if (site != null) {
@@ -174,10 +150,12 @@ public class PageImpl implements Page {
         }
     }
 
+    @Override
     public Resource getResource() {
         return resource;
     }
 
+    @Override
     public Site getSite() {
         SiteManager siteMgr = resource.adaptTo(SiteManager.class);
         Site site = null;
@@ -187,27 +165,7 @@ public class PageImpl implements Page {
         return site;
     }
 
-    public PageTemplate getTemplate() {
-        Resource templateResource = this.resource.getResourceResolver().getResource(template);
-        if (templateResource != null) {
-            return templateResource.adaptTo(PageTemplate.class);
-        } else {
-            return null;
-        }
-    }
-
-    public String getTemplatePath() {
-        return this.template;
-    }
-
-    public String getTitle() {
-        if (StringUtils.isNotEmpty(title)) {
-            return title;
-        } else {
-            return resource.getName();
-        }
-    }
-
+    @Override
     public boolean isPublished() {
         return published;
     }
diff --git a/core/src/main/java/org/apache/sling/cms/core/internal/models/FileManagerImpl.java b/core/src/main/java/org/apache/sling/cms/core/internal/models/FileManagerImpl.java
new file mode 100644
index 0000000..f4ac120
--- /dev/null
+++ b/core/src/main/java/org/apache/sling/cms/core/internal/models/FileManagerImpl.java
@@ -0,0 +1,47 @@
+/*
+ * 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.cms.core.internal.models;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.cms.CMSConstants;
+import org.apache.sling.cms.CMSUtils;
+import org.apache.sling.cms.File;
+import org.apache.sling.cms.FileManager;
+import org.apache.sling.models.annotations.Model;
+
+/**
+ * A model used to retrieve files.
+ */
+@Model(adaptables = Resource.class, adapters = FileManager.class)
+public class FileManagerImpl implements FileManager {
+
+    private final File file;
+
+    public FileManagerImpl(Resource containingResource) {
+        Resource fileResource = CMSUtils.findParentResourceofType(containingResource, CMSConstants.NT_PAGE);
+        if (fileResource != null) {
+            file = fileResource.adaptTo(File.class);
+        } else {
+            file = null;
+        }
+    }
+
+    @Override
+    public File getFile() {
+        return file;
+    }
+}
diff --git a/core/src/main/java/org/apache/sling/cms/core/internal/models/PageImpl.java b/core/src/main/java/org/apache/sling/cms/core/internal/models/PageImpl.java
index 1de2819..7808f99 100644
--- a/core/src/main/java/org/apache/sling/cms/core/internal/models/PageImpl.java
+++ b/core/src/main/java/org/apache/sling/cms/core/internal/models/PageImpl.java
@@ -123,9 +123,9 @@ public class PageImpl implements Page {
         List<String> keywords = new ArrayList<>();
         if (taxonomy != null) {
             for (String item : taxonomy) {
-                Resource resource = this.resource.getResourceResolver().getResource(item);
-                if (resource != null) {
-                    keywords.add(resource.getValueMap().get(CMSConstants.PN_TITLE, String.class));
+                Resource rsrc = this.resource.getResourceResolver().getResource(item);
+                if (rsrc != null) {
+                    keywords.add(rsrc.getValueMap().get(CMSConstants.PN_TITLE, String.class));
                 }
             }
         }
@@ -178,7 +178,7 @@ public class PageImpl implements Page {
         return resource;
     }
 
-    public Site getSite() {
+    public Site getSite() { 
         SiteManager siteMgr = resource.adaptTo(SiteManager.class);
         Site site = null;
         if (siteMgr != null) {
diff --git a/pom.xml b/pom.xml
index f9e2671..94548b5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -170,6 +170,12 @@
                 <version>1.6.8</version>
                 <scope>provided</scope>
             </dependency>
+            <dependency>
+                <artifactId>jsoup</artifactId>
+                <version>1.11.3</version>
+                <groupId>org.jsoup</groupId>
+                <scope>provided</scope>
+            </dependency>
         </dependencies>
     </dependencyManagement>