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/07/03 16:44:48 UTC

[sling-org-apache-sling-file-optimization] 01/08: Adding a library for optimizing files within the Sling repository

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

dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-file-optimization.git

commit 18f771e24ac72f02c819fd2433904ed07b75ac29
Author: Dan Klco <dk...@apache.org>
AuthorDate: Wed May 30 14:45:30 2018 -0400

    Adding a library for optimizing files within the Sling repository
---
 .gitignore                                         |  17 ++
 LICENSE                                            | 202 ++++++++++++++++
 NOTICE                                             |   8 +
 README.md                                          |   9 +
 pom.xml                                            | 187 +++++++++++++++
 .../org/apache/sling/fileoptim/FileOptimizer.java  |  44 ++++
 .../sling/fileoptim/FileOptimizerService.java      |  75 ++++++
 .../apache/sling/fileoptim/OptimizationResult.java | 118 ++++++++++
 .../fileoptim/impl/FileOptimizerEventHandler.java  | 110 +++++++++
 .../fileoptim/impl/FileOptimizerOperation.java     |  98 ++++++++
 .../fileoptim/impl/FileOptimizerServiceImpl.java   | 258 +++++++++++++++++++++
 .../fileoptim/impl/FileOptimizerWebConsole.java    |  96 ++++++++
 .../fileoptim/impl/servlets/FileOptimizerData.java |  76 ++++++
 .../impl/servlets/FileOptimizerPreview.java        |  67 ++++++
 .../sling/fileoptim/models/OptimizedFile.java      |  60 +++++
 .../fileoptim/optimizers/JpegFileOptimizer.java    |  90 +++++++
 .../fileoptim/optimizers/PngFileOptimizer.java     |  63 +++++
 .../sling/fileoptim/optimizers/package-info.java   |  18 ++
 .../org/apache/sling/fileoptim/package-info.java   |  18 ++
 src/main/resources/OSGI-INF/l10n/bundle.properties |  52 +++++
 .../resources/SLING-INF/nodetypes/nodetypes.cnd    |  32 +++
 .../org/apache/sling/TestJPGFileOptimizer.java     |  76 ++++++
 .../org/apache/sling/TestPngFileOptimizer.java     |  53 +++++
 .../Screen Shot 2018-05-29 at 4.17.20 PM.png       | Bin 0 -> 455695 bytes
 .../resources/valentino-funghi-41239-unsplash.jpg  | Bin 0 -> 5165767 bytes
 25 files changed, 1827 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5b783ed
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,17 @@
+/target
+.idea
+.classpath
+.metadata
+.project
+.settings
+.externalToolBuilders
+maven-eclipse.xml
+*.swp
+*.iml
+*.ipr
+*.iws
+*.bak
+.vlt
+.DS_Store
+jcr.log
+atlassian-ide-plugin.xml
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed 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.
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..862652e
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,8 @@
+Apache Sling File Optimizer
+Copyright 2018 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+
+This product includes software developed at
+https://github.com/depsypher/pngtastic
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..9ceb056
--- /dev/null
+++ b/README.md
@@ -0,0 +1,9 @@
+[<img src="http://sling.apache.org/res/logos/sling.png" align="center"/>](http://sling.apache.org)
+
+[![Build Status](https://img.shields.io/jenkins/s/https/builds.apache.org/view/S-Z/view/Sling/job/sling-org-apache-sling-file-optim-1.8.svg)](https://builds.apache.org/view/S-Z/view/Sling/job/sling-org-apache-sling-file-optim-1.8) [![Test Status](https://img.shields.io/jenkins/t/https/builds.apache.org/view/S-Z/view/Sling/job/sling-sling-org-apache-sling-file-optim-1.8.svg)](https://builds.apache.org/view/S-Z/view/Sling/job/sling-sling-org-apache-sling-file-optim-1.8) [![License](https:/ [...]
+
+# Apache Sling File Optimization
+
+Bundle for optimizing files stored in the Apache Sling repository. Includes a plugin architecture for providing file optimizers and hooks to automatically and manually optimize files.
+
+This module is part of the [Apache Sling](https://sling.apache.org) project.
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..d628c44
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,187 @@
+<?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">
+
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>org.apache.sling</groupId>
+		<artifactId>sling</artifactId>
+		<version>33</version>
+		<relativePath />
+	</parent>
+
+	<artifactId>org.apache.sling.file.optim</artifactId>
+	<version>1.0.0-SNAPSHOT</version>
+	<packaging>bundle</packaging>
+
+	<name>Apache Sling File Optimization</name>
+	<description>
+        Bundle for optimizing files stored in the Apache Sling repository. Includes a plugin architecture for providing file optimizers and hooks to automatically and manually optimize files.
+    </description>
+
+
+	<scm>
+		<connection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-file-optim.git</connection>
+		<developerConnection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-file-optim.git</developerConnection>
+		<url>https://gitbox.apache.org/repos/asf?p=sling-org-apache-sling-file-optim.git</url>
+		<tag>HEAD</tag>
+	</scm>
+
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.felix</groupId>
+				<artifactId>maven-bundle-plugin</artifactId>
+				<extensions>true</extensions>
+				<configuration>
+					<instructions>
+						<Sling-Nodetypes>SLING-INF/nodetypes/nodetypes.cnd</Sling-Nodetypes>
+						<Sling-Model-Packages>org.apache.sling.fileoptim.models</Sling-Model-Packages>
+						<Import-Package>!org.apache.tools.ant,!org.apache.tools.ant.types,*</Import-Package>
+						<Embed-Dependency>
+							*;scope=compile;inline=true
+						</Embed-Dependency>
+					</instructions>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+
+	<dependencies>
+
+		<!-- OSGi -->
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>org.osgi.service.component.annotations</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>org.osgi.service.metatype.annotations</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>org.osgi.annotation.versioning</artifactId>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>osgi.core</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>osgi.cmpn</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.felix</groupId>
+			<artifactId>org.apache.felix.webconsole</artifactId>
+			<version>4.2.0</version>
+			<scope>provided</scope>
+		</dependency>
+
+		<!-- JCR / Sling Commons -->
+		<dependency>
+			<groupId>javax.jcr</groupId>
+			<artifactId>jcr</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.jackrabbit</groupId>
+			<artifactId>jackrabbit-jcr-commons</artifactId>
+			<version>2.7.5</version>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.sling</groupId>
+			<artifactId>org.apache.sling.api</artifactId>
+			<version>2.8.0</version>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.sling</groupId>
+			<artifactId>org.apache.sling.servlets.post</artifactId>
+			<version>2.3.22</version>
+			<scope>provided</scope>
+		</dependency>
+
+		<!-- Image Optimization Dependencies -->
+		<dependency>
+			<groupId>com.github.depsypher</groupId>
+			<artifactId>pngtastic</artifactId>
+			<version>1.5</version>
+			<scope>compile</scope>
+		</dependency>
+
+
+		<!-- Base Dependencies -->
+		<dependency>
+			<groupId>javax.servlet</groupId>
+			<artifactId>javax.servlet-api</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-api</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>commons-lang</groupId>
+			<artifactId>commons-lang</artifactId>
+			<version>2.4</version>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>commons-io</groupId>
+			<artifactId>commons-io</artifactId>
+			<version>2.4</version>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<artifactId>commons-codec</artifactId>
+			<version>1.11</version>
+			<groupId>commons-codec</groupId>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<artifactId>org.apache.sling.commons.johnzon</artifactId>
+			<version>1.1.0</version>
+			<groupId>org.apache.sling</groupId>
+			<scope>provided</scope>
+		</dependency>
+
+		<!-- Sling Models -->
+		<dependency>
+			<groupId>org.apache.sling</groupId>
+			<artifactId>org.apache.sling.models.api</artifactId>
+			<version>1.3.6</version>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.geronimo.specs</groupId>
+			<artifactId>geronimo-atinject_1.0_spec</artifactId>
+			<version>1.0</version>
+			<scope>provided</scope>
+		</dependency>
+
+		<!-- Test Dependencies -->
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.jmock</groupId>
+			<artifactId>jmock-junit4</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-simple</artifactId>
+		</dependency>
+	</dependencies>
+</project>
diff --git a/src/main/java/org/apache/sling/fileoptim/FileOptimizer.java b/src/main/java/org/apache/sling/fileoptim/FileOptimizer.java
new file mode 100644
index 0000000..7196aba
--- /dev/null
+++ b/src/main/java/org/apache/sling/fileoptim/FileOptimizer.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
+ *
+ *      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.fileoptim;
+
+/**
+ * Interface for File Optimizers to extend to implement
+ */
+public interface FileOptimizer {
+
+	public static final String MIME_TYPE = "mime.type";
+
+	/**
+	 * Optimize a single file.
+	 * 
+	 * @param original
+	 *            the original file to optimize
+	 * @param metaType
+	 *            the type of the file to optimize
+	 * @return the optimized file
+	 */
+	byte[] optimizeFile(byte[] original, String metaType);
+
+	/**
+	 * Gets the name of the optimizer
+	 * 
+	 * @return the name of the optimizer
+	 */
+	String getName();
+
+}
diff --git a/src/main/java/org/apache/sling/fileoptim/FileOptimizerService.java b/src/main/java/org/apache/sling/fileoptim/FileOptimizerService.java
new file mode 100644
index 0000000..6d88fbc
--- /dev/null
+++ b/src/main/java/org/apache/sling/fileoptim/FileOptimizerService.java
@@ -0,0 +1,75 @@
+/*
+ * 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.fileoptim;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+
+/**
+ * A service for optimizing files stored in Sling Resources.
+ */
+public interface FileOptimizerService {
+
+	/**
+	 * Returns true if the resource is a registered type, the CA Config for the
+	 * resource is enabled and if an optimizer is registered for the file's meta
+	 * type
+	 * 
+	 * @param fileResource
+	 *            the resource to check if the optimizer is available
+	 * @return true if the file optimizer can optimize the file, false otherwise
+	 */
+	boolean canOptimize(Resource fileResource);
+
+	/**
+	 * Optimizes a file resource.
+	 * 
+	 * @param fileResource
+	 *            the resource to optimize
+	 * @param autoCommit
+	 *            if true, the results will automatically be committed to the Sling
+	 *            Repo
+	 * @return the results of the optimization
+	 * @throws PersistenceException
+	 *             an exception occurs saving the optimized resource
+	 * @throws IOException 
+	 *             an exception occurs reading the original resource
+	 */
+	OptimizationResult optimizeFile(Resource fileResource, boolean autoCommit) throws PersistenceException, IOException;
+
+	/**
+	 * Optimizes a collection of file resources.
+	 * 
+	 * @param fileResources
+	 *            the resources to optimize
+	 * @param autoCommit
+	 *            if true, the results will automatically be committed to the Sling
+	 *            Repo
+	 * @return the results of the optimization
+	 * @throws PersistenceException
+	 *             an exception occurs saving the optimized resources
+	 * @throws IOException 
+	 *             an exception occurs reading the original resources
+	 */
+	Map<String, OptimizationResult> optimizeFiles(Collection<Resource> fileResources, boolean autoCommit)
+			throws PersistenceException, IOException;
+
+}
diff --git a/src/main/java/org/apache/sling/fileoptim/OptimizationResult.java b/src/main/java/org/apache/sling/fileoptim/OptimizationResult.java
new file mode 100644
index 0000000..b9ca48b
--- /dev/null
+++ b/src/main/java/org/apache/sling/fileoptim/OptimizationResult.java
@@ -0,0 +1,118 @@
+/*
+ * 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.fileoptim;
+
+import org.apache.sling.api.resource.Resource;
+
+/**
+ * Result of file optimization
+ */
+public class OptimizationResult {
+
+	private String algorithm;
+	private boolean optimized = false;
+	private long optimizedSize;
+	private long originalSize;
+	private final Resource resource;
+	private double savings = 0;
+
+	public OptimizationResult(Resource resource) {
+		this.resource = resource;
+	}
+
+	/**
+	 * @return the algorithm
+	 */
+	public String getAlgorithm() {
+		return algorithm;
+	}
+
+	/**
+	 * @return the optimizedSize
+	 */
+	public long getOptimizedSize() {
+		return optimizedSize;
+	}
+
+	/**
+	 * @return the originalSize
+	 */
+	public long getOriginalSize() {
+		return originalSize;
+	}
+
+	/**
+	 * @return the resource
+	 */
+	public Resource getResource() {
+		return resource;
+	}
+
+	/**
+	 * @return the savings
+	 */
+	public double getSavings() {
+		return savings;
+	}
+
+	/**
+	 * @return the optimized
+	 */
+	public boolean isOptimized() {
+		return optimized;
+	}
+
+	/**
+	 * @param algorithm
+	 *            the algorithm to set
+	 */
+	public void setAlgorithm(String algorithm) {
+		this.algorithm = algorithm;
+	}
+
+	/**
+	 * @param optimized
+	 *            the optimized to set
+	 */
+	public void setOptimized(boolean optimized) {
+		this.optimized = optimized;
+	}
+
+	/**
+	 * @param optimizedSize
+	 *            the optimizedSize to set
+	 */
+	public void setOptimizedSize(long optimizedSize) {
+		this.optimizedSize = optimizedSize;
+	}
+
+	/**
+	 * @param originalSize
+	 *            the originalSize to set
+	 */
+	public void setOriginalSize(long originalSize) {
+		this.originalSize = originalSize;
+	}
+
+	/**
+	 * @param savings
+	 *            the savings to set
+	 */
+	public void setSavings(double savings) {
+		this.savings = savings;
+	}
+}
diff --git a/src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerEventHandler.java b/src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerEventHandler.java
new file mode 100644
index 0000000..4803818
--- /dev/null
+++ b/src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerEventHandler.java
@@ -0,0 +1,110 @@
+/*
+ * 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.fileoptim.impl;
+
+import java.io.IOException;
+
+import org.apache.sling.api.SlingConstants;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.fileoptim.FileOptimizerService;
+import org.apache.sling.fileoptim.impl.FileOptimizerEventHandler.Config;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Modified;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventHandler;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An event filter to trigger to optimize images when they have been saved by
+ * compressing.
+ */
+@Component(service = EventHandler.class, immediate = true)
+@Designate(ocd = Config.class)
+public class FileOptimizerEventHandler implements EventHandler {
+
+	@ObjectClassDefinition(name = "%event.handler.name", description = "%event.handler.description", localization = "OSGI-INF/l10n/bundle")
+	public @interface Config {
+
+		@AttributeDefinition(name = "%event.handler.enabled.name", description = "%event.handler.enabled.description")
+		boolean enabled() default false;
+
+		@AttributeDefinition(name = "%event.handler.filter.name", description = "%event.handler.filter.description")
+		String event_filter() default "(&(resourceType=sling:File)(|(path=*.png)(path=*.jpg)))";
+
+		@AttributeDefinition(name = "%event.handler.topics.name", description = "%event.handler.topics.description")
+		String[] event_topics() default { SlingConstants.TOPIC_RESOURCE_ADDED, SlingConstants.TOPIC_RESOURCE_CHANGED };
+	}
+
+	private static final Logger log = LoggerFactory.getLogger(FileOptimizerEventHandler.class);
+
+	@Reference
+	private FileOptimizerService fileOptimizer;
+
+	private ResourceResolver resourceResolver;
+
+	@Reference
+	private ResourceResolverFactory rrf;
+
+	private Config config;
+
+	@Activate
+	@Modified
+	public void activate(Config config) throws LoginException {
+		deactivate();
+		resourceResolver = rrf.getServiceResourceResolver(null);
+		this.config = config;
+	}
+
+	@Deactivate
+	public void deactivate() {
+		if (resourceResolver != null) {
+			resourceResolver.close();
+		}
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see
+	 * org.osgi.service.event.EventHandler#handleEvent(org.osgi.service.event.Event)
+	 */
+	@Override
+	public void handleEvent(Event event) {
+		if (config.enabled()) {
+			String path = (String) event.getProperty(SlingConstants.PROPERTY_PATH);
+			Resource fileResource = resourceResolver.getResource(path);
+			if (fileResource != null && fileOptimizer.canOptimize(fileResource)) {
+				try {
+					fileOptimizer.optimizeFile(fileResource, true);
+				} catch (IOException e) {
+					log.error("Exception saving optimized file", e);
+				}
+			}
+		}
+	}
+
+}
diff --git a/src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerOperation.java b/src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerOperation.java
new file mode 100644
index 0000000..5b47fb4
--- /dev/null
+++ b/src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerOperation.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.fileoptim.impl;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.fileoptim.FileOptimizerService;
+import org.apache.sling.servlets.post.Modification;
+import org.apache.sling.servlets.post.PostOperation;
+import org.apache.sling.servlets.post.PostResponse;
+import org.apache.sling.servlets.post.SlingPostProcessor;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The File Optimization operation will optimize a file
+ */
+@Component(immediate = true, service = { PostOperation.class }, property = PostOperation.PROP_OPERATION_NAME
+		+ "=fileoptim")
+public class FileOptimizerOperation implements PostOperation {
+
+	private static final Logger log = LoggerFactory.getLogger(FileOptimizerOperation.class);
+
+	@Reference
+	private FileOptimizerService fileOptimizer;
+
+	protected void doRun(SlingHttpServletRequest request, PostResponse response, List<Modification> changes)
+			throws IOException {
+		Resource resource = request.getResource();
+		if (fileOptimizer.canOptimize(resource)) {
+			fileOptimizer.optimizeFile(resource, true);
+			changes.add(Modification.onModified(resource.getPath()));
+		}
+	}
+
+	public void run(final SlingHttpServletRequest request, final PostResponse response,
+			final SlingPostProcessor[] processors) {
+
+		try {
+			// calculate the paths
+			String path = request.getResource().getPath();
+			response.setPath(path);
+
+			final List<Modification> changes = new ArrayList<>();
+
+			doRun(request, response, changes);
+
+			// invoke processors
+			if (processors != null) {
+				for (SlingPostProcessor processor : processors) {
+					processor.process(request, changes);
+				}
+			}
+
+			// check modifications for remaining postfix and store the base path
+			final Map<String, String> modificationSourcesContainingPostfix = new HashMap<>();
+			final Set<String> allModificationSources = new HashSet<>(changes.size());
+			for (final Modification modification : changes) {
+				final String source = modification.getSource();
+				if (source != null) {
+					allModificationSources.add(source);
+					final int atIndex = source.indexOf('@');
+					if (atIndex > 0) {
+						modificationSourcesContainingPostfix.put(source.substring(0, atIndex), source);
+					}
+				}
+			}
+		} catch (Exception e) {
+			log.error("Exception during response processing.", e);
+			response.setError(e);
+		}
+	}
+
+}
diff --git a/src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerServiceImpl.java b/src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerServiceImpl.java
new file mode 100644
index 0000000..1483e4e
--- /dev/null
+++ b/src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerServiceImpl.java
@@ -0,0 +1,258 @@
+/*
+ * 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.fileoptim.impl;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.fileoptim.FileOptimizer;
+import org.apache.sling.fileoptim.FileOptimizerService;
+import org.apache.sling.fileoptim.OptimizationResult;
+import org.apache.sling.fileoptim.impl.FileOptimizerServiceImpl.Config;
+import org.apache.sling.fileoptim.models.OptimizedFile;
+import org.osgi.framework.BundleContext;
+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.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Modified;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implementation of the FileOptimizerService
+ */
+@Component(service = FileOptimizerService.class, immediate = true)
+@Designate(ocd = Config.class)
+public class FileOptimizerServiceImpl implements FileOptimizerService, ServiceListener {
+
+	@ObjectClassDefinition(name = "%file.optimizer.name", description = "%file.optimizer.description", localization = "OSGI-INF/l10n/bundle")
+	public @interface Config {
+
+		@AttributeDefinition(name = "%file.optimizer.hash.algorithm.name", description = "%file.optimizer.hash.algorithm.description")
+		String hashAlgorithm() default "MD5";
+	}
+
+	private static final Logger log = LoggerFactory.getLogger(FileOptimizerServiceImpl.class);
+
+	private BundleContext bundleContext;
+
+	private Config config;
+
+	private Map<String, List<ServiceReference<FileOptimizer>>> fileOptimizers = new HashMap<String, List<ServiceReference<FileOptimizer>>>();
+
+	@Activate
+	@Modified
+	public void activate(ComponentContext context, Config config) throws InvalidSyntaxException {
+		bundleContext = context.getBundleContext();
+		bundleContext.addServiceListener(this, "(" + Constants.OBJECTCLASS + "=" + FileOptimizer.class.getName() + ")");
+		this.config = config;
+	}
+
+	private void addOptimizer(Map<String, List<ServiceReference<FileOptimizer>>> tempCache, String metaType,
+			ServiceReference<FileOptimizer> ref) {
+		if (!tempCache.containsKey(metaType)) {
+			tempCache.put(metaType, new ArrayList<ServiceReference<FileOptimizer>>());
+		}
+		tempCache.get(metaType).add(ref);
+	}
+
+	private String calculateHash(byte[] bytes) {
+		MessageDigest messageDigest;
+		try {
+			messageDigest = MessageDigest.getInstance(config.hashAlgorithm());
+			messageDigest.reset();
+			messageDigest.update(bytes);
+			return Base64.encodeBase64String(messageDigest.digest());
+		} catch (NoSuchAlgorithmException e) {
+			log.error("Exception generating hash", e);
+		}
+		return null;
+	}
+
+	@Override
+	public boolean canOptimize(Resource fileResource) {
+		if (!fileResource.getName().equals(JcrConstants.JCR_CONTENT)) {
+			fileResource = fileResource.getChild(JcrConstants.JCR_CONTENT);
+		}
+		OptimizedFile of = fileResource.adaptTo(OptimizedFile.class);
+		return of != null && fileOptimizers.containsKey(of.getMimeType())
+				&& fileOptimizers.get(of.getMimeType()).size() > 0;
+	}
+
+	@Deactivate
+	public void deactivate(ComponentContext context) {
+		context.getBundleContext().removeServiceListener(this);
+	}
+
+	/**
+	 * @return the fileOptimizers
+	 */
+	public Map<String, List<ServiceReference<FileOptimizer>>> getFileOptimizers() {
+		return fileOptimizers;
+	}
+
+	@Override
+	public OptimizationResult optimizeFile(Resource fileResource, boolean autoCommit) throws IOException {
+
+		if (!fileResource.getName().equals(JcrConstants.JCR_CONTENT)) {
+			fileResource = fileResource.getChild(JcrConstants.JCR_CONTENT);
+		}
+		OptimizationResult result = new OptimizationResult(fileResource);
+
+		OptimizedFile optim = fileResource.adaptTo(OptimizedFile.class);
+
+		boolean optimize = true;
+		byte[] original = IOUtils.toByteArray(optim.getContent());
+		if (StringUtils.isNotBlank(optim.getHash()) && optim.getHash().equals(calculateHash(original))) {
+			optimize = false;
+		}
+
+		if (optimize) {
+			log.debug("Optimizing file resource {}", fileResource);
+			List<ServiceReference<FileOptimizer>> optimizers = fileOptimizers.get(optim.getMimeType());
+			for (ServiceReference<FileOptimizer> ref : optimizers) {
+				FileOptimizer optimizer = bundleContext.getService(ref);
+				if (optimizer != null) {
+					byte[] optimized = optimizer.optimizeFile(original, optim.getMimeType());
+					if (optimized != null && optimized.length < original.length) {
+
+						double savings = 1.0 - ((double) optimized.length / (double) original.length);
+
+						log.debug("Optimized file with {} saving {}%", optimizer.getName(), Math.round(savings * 100));
+
+						ModifiableValueMap mvm = fileResource.adaptTo(ModifiableValueMap.class);
+
+						Set<String> mixins = new HashSet<String>(
+								Arrays.asList(mvm.get(JcrConstants.JCR_MIXINTYPES, new String[0])));
+						mixins.add(OptimizedFile.MT_OPTIMIZED);
+						mvm.put(JcrConstants.JCR_MIXINTYPES, mixins.toArray(new String[] {}));
+
+						mvm.put(OptimizedFile.PN_ALGORITHM, optimizer.getName());
+						mvm.put(OptimizedFile.PN_HASH, calculateHash(optimized));
+						mvm.put(OptimizedFile.PN_ORIGINAL, new ByteArrayInputStream(original));
+						mvm.put(OptimizedFile.PN_SAVINGS, savings);
+						mvm.put(JcrConstants.JCR_DATA, new ByteArrayInputStream(optimized));
+
+						if (autoCommit) {
+							log.debug("Persisting changes...");
+							fileResource.getResourceResolver().commit();
+						}
+
+						result.setAlgorithm(optimizer.getName());
+						result.setSavings(savings);
+						result.setOptimized(true);
+						result.setOptimizedSize(optimized.length);
+						result.setOriginalSize(original.length);
+
+						break;
+					} else {
+						log.debug("Optimizer {} was not able to optimize the file", optimizer.getName());
+					}
+				} else {
+					log.warn("No service retrieved for service reference {}", ref);
+				}
+			}
+		} else {
+			log.trace("Resource {} is already optimized", fileResource);
+		}
+		return result;
+	}
+
+	@Override
+	public Map<String, OptimizationResult> optimizeFiles(Collection<Resource> fileResources, boolean autoCommit)
+			throws IOException {
+		Map<String, OptimizationResult> results = new HashMap<String, OptimizationResult>();
+		boolean dirty = false;
+		for (Resource fileResource : fileResources) {
+			OptimizationResult res = optimizeFile(fileResource, false);
+			results.put(fileResource.getName(), res);
+			if (res.isOptimized()) {
+				dirty = true;
+			}
+		}
+		if (autoCommit && dirty) {
+			log.debug("Persisting changes...");
+			fileResources.iterator().next().getResourceResolver().commit();
+		}
+		return results;
+	}
+
+	private void rebuildOptimizerCache() {
+		log.debug("rebuildOptimizerCache");
+		Map<String, List<ServiceReference<FileOptimizer>>> tempCache = new HashMap<String, List<ServiceReference<FileOptimizer>>>();
+		Collection<ServiceReference<FileOptimizer>> references = null;
+		try {
+			references = bundleContext.getServiceReferences(FileOptimizer.class, null);
+		} catch (Exception e) {
+			log.error("Exception retrieving service references", e);
+		}
+		for (ServiceReference<FileOptimizer> ref : references) {
+			Object mimeType = ref.getProperty(FileOptimizer.MIME_TYPE);
+			if (mimeType != null && mimeType instanceof String[]) {
+				for (String mt : (String[]) mimeType) {
+					addOptimizer(tempCache, mt, ref);
+				}
+			} else if (mimeType != null) {
+				addOptimizer(tempCache, (String) mimeType, ref);
+			}
+		}
+		for (List<ServiceReference<FileOptimizer>> optList : tempCache.values()) {
+			Collections.sort(optList);
+		}
+		this.fileOptimizers = tempCache;
+	}
+
+	@Override
+	public void serviceChanged(ServiceEvent event) {
+		rebuildOptimizerCache();
+	}
+
+	/**
+	 * @param fileOptimizers
+	 *            the fileOptimizers to set
+	 */
+	public void setFileOptimizers(Map<String, List<ServiceReference<FileOptimizer>>> fileOptimizers) {
+		this.fileOptimizers = fileOptimizers;
+	}
+
+}
diff --git a/src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerWebConsole.java b/src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerWebConsole.java
new file mode 100644
index 0000000..ad62359
--- /dev/null
+++ b/src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerWebConsole.java
@@ -0,0 +1,96 @@
+/*
+ * 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.fileoptim.impl;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.servlet.Servlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.felix.webconsole.AbstractWebConsolePlugin;
+import org.apache.felix.webconsole.WebConsoleConstants;
+import org.apache.sling.fileoptim.FileOptimizer;
+import org.apache.sling.fileoptim.FileOptimizerService;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+/**
+ * Simple web console plugin for listing out the available optimizers
+ */
+@Component(property = { Constants.SERVICE_DESCRIPTION + "=Web Console Plugin for Apache Sling File Optimizer",
+		Constants.SERVICE_VENDOR + "=The Apache Software Foundation",
+		WebConsoleConstants.PLUGIN_LABEL + "=" + FileOptimizerWebConsole.CONSOLE_LABEL,
+		WebConsoleConstants.PLUGIN_TITLE + "=" + FileOptimizerWebConsole.CONSOLE_TITLE,
+		WebConsoleConstants.CONFIG_PRINTER_MODES + "=always",
+		WebConsoleConstants.PLUGIN_CATEGORY + "=Status" }, service = { Servlet.class })
+public class FileOptimizerWebConsole extends AbstractWebConsolePlugin {
+
+	private static final long serialVersionUID = 7086113364871387281L;
+	public static final String CONSOLE_LABEL = "fileoptim";
+	public static final String CONSOLE_TITLE = "File Optimizer";
+
+	@Reference
+	private FileOptimizerService fileOptimizer;
+
+	@Override
+	public String getTitle() {
+		return CONSOLE_TITLE;
+	}
+
+	@Override
+	public String getLabel() {
+		return CONSOLE_LABEL;
+	}
+
+	@Override
+	protected void renderContent(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
+			throws IOException {
+		PrintWriter pw = httpServletResponse.getWriter();
+		pw.println("<div id='content' class='ui-widget'><br>");
+		pw.println("<pre>");
+		pw.println("Available Optimizers");
+		pw.println("========================");
+
+		Map<String, List<ServiceReference<FileOptimizer>>> optimizerCache = ((FileOptimizerServiceImpl) fileOptimizer)
+				.getFileOptimizers();
+
+		for (Entry<String, List<ServiceReference<FileOptimizer>>> to : optimizerCache.entrySet()) {
+
+			pw.println();
+			pw.println(to.getKey());
+			pw.println("-------------------------------------");
+			for (ServiceReference<FileOptimizer> fo : to.getValue()) {
+
+				FileOptimizer o = this.getBundleContext().getService(fo);
+
+				pw.println("- " + o.getName() + " (" + o.getClass().getName() + ")");
+			}
+		}
+		pw.println("</pre>");
+		pw.println("</div>");
+	}
+
+}
diff --git a/src/main/java/org/apache/sling/fileoptim/impl/servlets/FileOptimizerData.java b/src/main/java/org/apache/sling/fileoptim/impl/servlets/FileOptimizerData.java
new file mode 100644
index 0000000..639be70
--- /dev/null
+++ b/src/main/java/org/apache/sling/fileoptim/impl/servlets/FileOptimizerData.java
@@ -0,0 +1,76 @@
+/*
+ * 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.fileoptim.impl.servlets;
+
+import java.io.IOException;
+
+import javax.json.Json;
+import javax.json.stream.JsonGenerator;
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
+import org.apache.sling.fileoptim.FileOptimizerService;
+import org.apache.sling.fileoptim.OptimizationResult;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+/**
+ * Servlet for displaying a preview of an optimized image.
+ */
+@Component(service = { Servlet.class }, property = { "sling.servlet.paths=/system/fileoptim.json",
+		"sling.servlet.methods=GET" })
+public class FileOptimizerData extends SlingSafeMethodsServlet {
+
+	@Reference
+	private FileOptimizerService fileOptimizer;
+
+	private static final long serialVersionUID = 8635343288414416865L;
+
+	protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
+			throws ServletException, IOException {
+		String path = request.getParameter("path");
+
+		Resource resource = request.getResourceResolver().getResource(path);
+
+		if (resource == null) {
+			response.sendError(404, "No Resource found at path " + path);
+		} else if (fileOptimizer.canOptimize(resource)) {
+
+			OptimizationResult res = fileOptimizer.optimizeFile(resource.getChild(JcrConstants.JCR_CONTENT), false);
+			response.setContentType("application/json");
+
+			JsonGenerator json = Json.createGenerator(response.getWriter());
+			json.writeStartObject();
+			json.write("algorithm", res.getAlgorithm());
+			json.write("originalSize", res.getOriginalSize());
+			json.write("optimizedSize", res.getOptimizedSize());
+			json.write("optimized", res.isOptimized());
+			json.write("preview", "/system/fileoptim/preview?path=" + path);
+			json.write("savings", res.getSavings());
+			json.writeEnd();
+			json.close();
+			response.flushBuffer();
+		} else {
+			response.sendError(400, "Resource at path " + path + " is not a file or cannot be optimized");
+		}
+	}
+}
diff --git a/src/main/java/org/apache/sling/fileoptim/impl/servlets/FileOptimizerPreview.java b/src/main/java/org/apache/sling/fileoptim/impl/servlets/FileOptimizerPreview.java
new file mode 100644
index 0000000..53bf7e5
--- /dev/null
+++ b/src/main/java/org/apache/sling/fileoptim/impl/servlets/FileOptimizerPreview.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.fileoptim.impl.servlets;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
+import org.apache.sling.fileoptim.FileOptimizerService;
+import org.apache.sling.fileoptim.OptimizationResult;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+/**
+ * Servlet for displaying a preview of an optimized image.
+ */
+@Component(service = Servlet.class, property = { "sling.servlet.paths=/system/fileoptim/preview",
+		"sling.servlet.methods=GET" }, immediate = true)
+public class FileOptimizerPreview extends SlingSafeMethodsServlet {
+
+	@Reference
+	private FileOptimizerService fileOptimizer;
+
+	private static final long serialVersionUID = 8635343288414416865L;
+
+	protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
+			throws ServletException, IOException {
+		String path = request.getParameter("path");
+
+		Resource resource = request.getResourceResolver().getResource(path);
+
+		if (resource == null) {
+			response.sendError(404, "No Resource found at path " + path);
+		} else if (fileOptimizer.canOptimize(resource)) {
+			OptimizationResult res = fileOptimizer.optimizeFile(resource.getChild(JcrConstants.JCR_CONTENT), false);
+			ValueMap vm = res.getResource().getValueMap();
+			response.setContentType(vm.get(JcrConstants.JCR_MIMETYPE, String.class));
+			response.setHeader("Content-disposition", "inline; filename=" + resource.getName());
+			IOUtils.copy(vm.get(JcrConstants.JCR_DATA, InputStream.class), response.getOutputStream());
+		} else {
+			response.sendError(400, "Resource at path " + path + " is not a file or cannot be optimized");
+		}
+	}
+}
diff --git a/src/main/java/org/apache/sling/fileoptim/models/OptimizedFile.java b/src/main/java/org/apache/sling/fileoptim/models/OptimizedFile.java
new file mode 100644
index 0000000..e531a56
--- /dev/null
+++ b/src/main/java/org/apache/sling/fileoptim/models/OptimizedFile.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
+ *
+ * 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.fileoptim.models;
+
+import java.io.InputStream;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.models.annotations.DefaultInjectionStrategy;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.annotations.Required;
+
+/**
+ * Sling Model representing an optimized file
+ */
+@Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
+public interface OptimizedFile {
+
+	static final String PREFIX = "optim:";
+	public static final String MT_OPTIMIZED = PREFIX + "optimized";
+	public static final String PN_ALGORITHM = PREFIX + "algrithm";
+	public static final String PN_HASH = PREFIX + "hash";
+	public static final String PN_ORIGINAL = PREFIX + "original";
+	public static final String PN_SAVINGS = PREFIX + "savings";
+
+	@Named(JcrConstants.JCR_DATA)
+	@Inject
+	@Required
+	InputStream getContent();
+
+	@Named(PN_HASH)
+	@Inject
+	String getHash();
+
+	@Named(JcrConstants.JCR_MIMETYPE)
+	@Inject
+	@Required
+	String getMimeType();
+
+	@Named(PN_ORIGINAL)
+	@Inject
+	InputStream getOriginal();
+}
diff --git a/src/main/java/org/apache/sling/fileoptim/optimizers/JpegFileOptimizer.java b/src/main/java/org/apache/sling/fileoptim/optimizers/JpegFileOptimizer.java
new file mode 100644
index 0000000..9b641d9
--- /dev/null
+++ b/src/main/java/org/apache/sling/fileoptim/optimizers/JpegFileOptimizer.java
@@ -0,0 +1,90 @@
+/*
+ * 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.fileoptim.optimizers;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import javax.imageio.IIOImage;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageWriteParam;
+import javax.imageio.ImageWriter;
+import javax.imageio.stream.ImageOutputStream;
+import javax.imageio.stream.MemoryCacheImageOutputStream;
+
+import org.apache.sling.fileoptim.FileOptimizer;
+import org.apache.sling.fileoptim.optimizers.JpegFileOptimizer.Config;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Modified;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Default optimizer which lossily compresses a JPEG image.
+ */
+@Component(service = FileOptimizer.class, property = { FileOptimizer.MIME_TYPE + "=image/jpeg" })
+@Designate(ocd = Config.class)
+public class JpegFileOptimizer implements FileOptimizer {
+
+	private static final Logger log = LoggerFactory.getLogger(JpegFileOptimizer.class);
+
+	@ObjectClassDefinition(name = "%jpeg.optimizer.name", description = "%jpeg.optimizer.description", localization = "OSGI-INF/l10n/bundle")
+	public @interface Config {
+		@AttributeDefinition(name = "%jpeg.optimizer.compression.level.name", description = "%jpeg.optimizer.compression.level.description")
+		float compressionLevel() default 0.8f;
+	}
+
+	private Config config;
+
+	@Activate
+	@Modified
+	public void activate(Config config) {
+		this.config = config;
+	}
+
+	@Override
+	public byte[] optimizeFile(byte[] original, String metaType) {
+		ImageWriter jpgWriter = ImageIO.getImageWritersByFormatName("jpg").next();
+		ImageWriteParam jpgWriteParam = jpgWriter.getDefaultWriteParam();
+		jpgWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+		jpgWriteParam.setCompressionQuality(config.compressionLevel());
+
+		ByteArrayOutputStream baos = new ByteArrayOutputStream();
+		ImageOutputStream outputStream = new MemoryCacheImageOutputStream(baos);
+		jpgWriter.setOutput(outputStream);
+		try {
+			IIOImage outputImage = new IIOImage(ImageIO.read(new ByteArrayInputStream(original)), null, null);
+			jpgWriter.write(null, outputImage, jpgWriteParam);
+			jpgWriter.dispose();
+			return baos.toByteArray();
+		} catch (IOException e) {
+			log.warn("Exception optimizing image", e);
+		}
+		return null;
+	}
+
+	@Override
+	public String getName() {
+		return "Apache Sling JPEG File Optimizer";
+	}
+
+}
diff --git a/src/main/java/org/apache/sling/fileoptim/optimizers/PngFileOptimizer.java b/src/main/java/org/apache/sling/fileoptim/optimizers/PngFileOptimizer.java
new file mode 100644
index 0000000..e5fe63f
--- /dev/null
+++ b/src/main/java/org/apache/sling/fileoptim/optimizers/PngFileOptimizer.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.fileoptim.optimizers;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.apache.sling.fileoptim.FileOptimizer;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.googlecode.pngtastic.core.PngImage;
+import com.googlecode.pngtastic.core.PngOptimizer;
+
+/**
+ * Default optimizer which lossly compresses a PNG image.
+ */
+@Component(service = FileOptimizer.class, property = { FileOptimizer.MIME_TYPE + "=image/png" })
+public class PngFileOptimizer implements FileOptimizer {
+
+	private static final Logger log = LoggerFactory.getLogger(PngFileOptimizer.class);
+
+	@Override
+	public byte[] optimizeFile(byte[] original, String metaType) {
+
+		PngOptimizer optimizer = new PngOptimizer();
+
+		PngImage image = new PngImage(new ByteArrayInputStream(original));
+		try {
+			PngImage optimized = optimizer.optimize(image);
+
+			ByteArrayOutputStream baos = new ByteArrayOutputStream();
+			optimized.writeDataOutputStream(baos);
+
+			return baos.toByteArray();
+		} catch (IOException e) {
+			log.warn("Exception optimizing PNG image", e);
+		}
+		return null;
+	}
+
+	@Override
+	public String getName() {
+		return "PNGTastic PNG Optimizer";
+	}
+
+}
diff --git a/src/main/java/org/apache/sling/fileoptim/optimizers/package-info.java b/src/main/java/org/apache/sling/fileoptim/optimizers/package-info.java
new file mode 100644
index 0000000..976de2c
--- /dev/null
+++ b/src/main/java/org/apache/sling/fileoptim/optimizers/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+@org.osgi.annotation.versioning.Version("1.0.0")
+package org.apache.sling.fileoptim.optimizers;
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/fileoptim/package-info.java b/src/main/java/org/apache/sling/fileoptim/package-info.java
new file mode 100644
index 0000000..9b2977b
--- /dev/null
+++ b/src/main/java/org/apache/sling/fileoptim/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+@org.osgi.annotation.versioning.Version("1.0.0")
+package org.apache.sling.fileoptim;
\ No newline at end of file
diff --git a/src/main/resources/OSGI-INF/l10n/bundle.properties b/src/main/resources/OSGI-INF/l10n/bundle.properties
new file mode 100644
index 0000000..66e75e6
--- /dev/null
+++ b/src/main/resources/OSGI-INF/l10n/bundle.properties
@@ -0,0 +1,52 @@
+#
+#  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.
+#
+
+#
+# This file contains localization strings for configuration labels and
+# descriptions as used in the metatype.xml descriptor generated by the
+# the Sling SCR plugin
+
+# Event Handler Keys
+event.handler.name=Apache Sling File Optimizer Event Handler
+event.handler.description=An event handler for automatically invoking the Apache Sling File Optimizer based on OSGi Events
+
+event.handler.enabled.name=Enabled
+event.handler.enabled.description=If true, the Event Handler will execute, if not the event handler will not execute
+
+event.handler.filter.name=Filter
+event.handler.filter.description=A LDAP Filter to control which OSGi Events will trigger this handler will execute
+
+event.handler.topics.name=Filter
+event.handler.topic.description=The OSGI Event topics which will trigger this event
+
+# File Optimizer Keys
+file.optimizer.name=Apache Sling File Optimizer
+file.optimizer.description=A service to optimize files. Allows developers to register FileOptimizer instances against \
+file types to be able to optimize files of that type
+
+file.optimizer.hash.algorithm.name=Hash Algorithm
+file.optimizer.hash.algorithm.description=The algorithm with which to hash the contents of the file, used to determine \
+if a file has already been optimized or not
+
+# Jpeg Optimizer Keys
+jpeg.optimizer.name=Apache Sling JPEG Optimizer
+jpeg.optimizer.description=An optimizer for lossily compressing JPEG images
+
+jpeg.optimizer.compression.level.name=Compression Level
+jpeg.optimizer.compression.level.description=The compression level for compressing the Jpeg (between 0.0 - 1.0)
\ No newline at end of file
diff --git a/src/main/resources/SLING-INF/nodetypes/nodetypes.cnd b/src/main/resources/SLING-INF/nodetypes/nodetypes.cnd
new file mode 100644
index 0000000..204b4b6
--- /dev/null
+++ b/src/main/resources/SLING-INF/nodetypes/nodetypes.cnd
@@ -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
+//  
+//  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.
+//  
+//  You can find out more documentation on this topic 
+//  by following these links:
+//
+//	-  http://sling.apache.org/site/content-loading.html
+//	-  http://jackrabbit.apache.org/node-type-notation.html
+
+<optim = 'http://www.sling.apache.org/optim/1.0'>
+	
+
+[optim:optimized] 
+ mixin  
+ - optim:algorithm (string)
+ - optim:hash (string)
+ - optim:original (binary)
+ - optim:savings (double)
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/TestJPGFileOptimizer.java b/src/test/java/org/apache/sling/TestJPGFileOptimizer.java
new file mode 100644
index 0000000..22ec2a9
--- /dev/null
+++ b/src/test/java/org/apache/sling/TestJPGFileOptimizer.java
@@ -0,0 +1,76 @@
+/*
+ * 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;
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.sling.fileoptim.optimizers.JpegFileOptimizer;
+import org.apache.sling.fileoptim.optimizers.JpegFileOptimizer.Config;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TestJPGFileOptimizer {
+
+	private JpegFileOptimizer optimizer = new JpegFileOptimizer();
+
+	private static final Logger log = LoggerFactory.getLogger(TestJPGFileOptimizer.class);
+
+	@Before
+	public void init() {
+
+		Config config = new Config() {
+			{
+			}
+
+			@Override
+			public Class<? extends Annotation> annotationType() {
+				return null;
+			}
+
+			@Override
+			public float compressionLevel() {
+				return 0.75f;
+			}
+		};
+		optimizer.activate(config);
+	}
+
+	@Test
+	public void testOptimizer() throws IOException {
+		ByteArrayOutputStream baos = new ByteArrayOutputStream();
+		IOUtils.copy(getClass().getClassLoader().getResourceAsStream("valentino-funghi-41239-unsplash.jpg"), baos);
+		byte[] optimized = optimizer.optimizeFile(baos.toByteArray(), "image/jpeg");
+
+		assertTrue(baos.toByteArray().length > optimized.length);
+
+		log.info("Original size: {}", baos.toByteArray().length);
+		log.info("Optimized size: {}", optimized.length);
+
+		double savings = 1.0 - ((double) optimized.length / (double) baos.toByteArray().length);
+		log.info("Compressed by {}%", Math.round(savings * 100.0));
+		
+		IOUtils.write(optimized, new FileOutputStream("target/optimized.jpg"));
+	}
+}
diff --git a/src/test/java/org/apache/sling/TestPngFileOptimizer.java b/src/test/java/org/apache/sling/TestPngFileOptimizer.java
new file mode 100644
index 0000000..920eb76
--- /dev/null
+++ b/src/test/java/org/apache/sling/TestPngFileOptimizer.java
@@ -0,0 +1,53 @@
+/*
+ * 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;
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.sling.fileoptim.optimizers.PngFileOptimizer;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TestPngFileOptimizer {
+
+	private PngFileOptimizer optimizer = new PngFileOptimizer();
+
+	private static final Logger log = LoggerFactory.getLogger(TestPngFileOptimizer.class);
+
+	@Test
+	public void testOptimizer() throws IOException {
+		ByteArrayOutputStream baos = new ByteArrayOutputStream();
+		IOUtils.copy(getClass().getClassLoader().getResourceAsStream("Screen Shot 2018-05-29 at 4.17.20 PM.png"), baos);
+		byte[] optimized = optimizer.optimizeFile(baos.toByteArray(), "image/png");
+
+		assertTrue(baos.toByteArray().length > optimized.length);
+
+		log.info("Original size: {}", baos.toByteArray().length);
+		log.info("Optimized size: {}", optimized.length);
+
+		double savings = 1.0 - ((double) optimized.length / (double) baos.toByteArray().length);
+		log.info("Compressed by {}%", Math.round(savings * 100.0));
+		
+		IOUtils.write(optimized, new FileOutputStream("target/optimized.png"));
+	}
+}
diff --git a/src/test/resources/Screen Shot 2018-05-29 at 4.17.20 PM.png b/src/test/resources/Screen Shot 2018-05-29 at 4.17.20 PM.png
new file mode 100644
index 0000000..88564c9
Binary files /dev/null and b/src/test/resources/Screen Shot 2018-05-29 at 4.17.20 PM.png differ
diff --git a/src/test/resources/valentino-funghi-41239-unsplash.jpg b/src/test/resources/valentino-funghi-41239-unsplash.jpg
new file mode 100644
index 0000000..08e22fe
Binary files /dev/null and b/src/test/resources/valentino-funghi-41239-unsplash.jpg differ