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:47 UTC

[sling-org-apache-sling-file-optimization] branch master created (now 81ba9ca)

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

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


      at 81ba9ca  Fixing typo

This branch includes the following new commits:

     new 18f771e  Adding a library for optimizing files within the Sling repository
     new 92100d1  Adding a new model and restore operation to support the needs from Sling CMS
     new d3c9194  Refactoring the code a bit to make the 'dry-run' not update the actual resource and adding a filter option
     new 9bed761  Fixing the test package and the artifact id
     new 5be51e3  Adding unit tests and cleaning up some issues found by unit tests
     new 50e3580  Moving fileoptim artifactId out of commons
     new 4fc02b5  Adding documentation
     new 81ba9ca  Fixing typo

The 8 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[sling-org-apache-sling-file-optimization] 04/08: Fixing the test package and the artifact id

Posted by dk...@apache.org.
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 9bed761f413c3aa0365334f00086e68bf8a57811
Author: Dan Klco <dk...@apache.org>
AuthorDate: Mon Jun 4 20:41:24 2018 -0400

    Fixing the test package and the artifact id
---
 pom.xml                                                               | 2 +-
 .../apache/sling/{ => fileoptim/optimizers}/TestJPGFileOptimizer.java | 2 +-
 .../apache/sling/{ => fileoptim/optimizers}/TestPngFileOptimizer.java | 4 ++--
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/pom.xml b/pom.xml
index 3d8382d..621d606 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
 		<relativePath />
 	</parent>
 
-	<artifactId>org.apache.sling.file.optim</artifactId>
+	<artifactId>org.apache.sling.commons.fileoptim</artifactId>
 	<version>1.0.0-SNAPSHOT</version>
 	<packaging>bundle</packaging>
 
diff --git a/src/test/java/org/apache/sling/TestJPGFileOptimizer.java b/src/test/java/org/apache/sling/fileoptim/optimizers/TestJPGFileOptimizer.java
similarity index 98%
rename from src/test/java/org/apache/sling/TestJPGFileOptimizer.java
rename to src/test/java/org/apache/sling/fileoptim/optimizers/TestJPGFileOptimizer.java
index 22ec2a9..0264093 100644
--- a/src/test/java/org/apache/sling/TestJPGFileOptimizer.java
+++ b/src/test/java/org/apache/sling/fileoptim/optimizers/TestJPGFileOptimizer.java
@@ -14,7 +14,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package org.apache.sling;
+package org.apache.sling.fileoptim.optimizers;
 
 import static org.junit.Assert.assertTrue;
 
diff --git a/src/test/java/org/apache/sling/TestPngFileOptimizer.java b/src/test/java/org/apache/sling/fileoptim/optimizers/TestPngFileOptimizer.java
similarity index 97%
rename from src/test/java/org/apache/sling/TestPngFileOptimizer.java
rename to src/test/java/org/apache/sling/fileoptim/optimizers/TestPngFileOptimizer.java
index 920eb76..44278c1 100644
--- a/src/test/java/org/apache/sling/TestPngFileOptimizer.java
+++ b/src/test/java/org/apache/sling/fileoptim/optimizers/TestPngFileOptimizer.java
@@ -14,7 +14,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package org.apache.sling;
+package org.apache.sling.fileoptim.optimizers;
 
 import static org.junit.Assert.assertTrue;
 
@@ -47,7 +47,7 @@ public class TestPngFileOptimizer {
 
 		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"));
 	}
 }


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

Posted by dk...@apache.org.
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


[sling-org-apache-sling-file-optimization] 03/08: Refactoring the code a bit to make the 'dry-run' not update the actual resource and adding a filter option

Posted by dk...@apache.org.
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 d3c9194263a35b336a680787055b8afd51b19659
Author: Dan Klco <dk...@apache.org>
AuthorDate: Mon Jun 4 18:01:23 2018 -0400

    Refactoring the code a bit to make the 'dry-run' not update the actual resource and adding a filter option
---
 pom.xml                                            |   6 ++
 .../sling/fileoptim/FileOptimizerService.java      |  34 +++----
 .../apache/sling/fileoptim/OptimizationResult.java |  42 +++++++-
 .../fileoptim/impl/FileOptimizerEventHandler.java  |  26 ++---
 .../fileoptim/impl/FileOptimizerServiceImpl.java   | 106 ++++++++++----------
 .../impl/filters/FileOptimizerFilter.java          | 109 +++++++++++++++++++++
 .../fileoptim/impl/servlets/FileOptimizerData.java |   3 +-
 .../impl/servlets/FileOptimizerPreview.java        |   2 +-
 .../sling/fileoptim/models/OptimizeResource.java   |  20 +++-
 .../sling/fileoptim/models/OptimizedFile.java      |   9 +-
 src/main/resources/OSGI-INF/l10n/bundle.properties |  14 ++-
 .../resources/SLING-INF/nodetypes/nodetypes.cnd    |   1 +
 12 files changed, 271 insertions(+), 101 deletions(-)

diff --git a/pom.xml b/pom.xml
index d628c44..3d8382d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -112,6 +112,12 @@
 			<version>2.3.22</version>
 			<scope>provided</scope>
 		</dependency>
+		<dependency>
+			<groupId>org.apache.sling</groupId>
+			<artifactId>org.apache.sling.engine</artifactId>
+			<version>2.6.12</version>
+			<scope>provided</scope>
+		</dependency>
 
 		<!-- Image Optimization Dependencies -->
 		<dependency>
diff --git a/src/main/java/org/apache/sling/fileoptim/FileOptimizerService.java b/src/main/java/org/apache/sling/fileoptim/FileOptimizerService.java
index c63b43a..9739eb9 100644
--- a/src/main/java/org/apache/sling/fileoptim/FileOptimizerService.java
+++ b/src/main/java/org/apache/sling/fileoptim/FileOptimizerService.java
@@ -17,8 +17,6 @@
 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;
@@ -40,6 +38,19 @@ public interface FileOptimizerService {
 	boolean canOptimize(Resource fileResource);
 
 	/**
+	 * Gets the optimized contents of a file resource. This will not update the
+	 * underlying resource, but instead just returns the results of optimizing the
+	 * resource.
+	 * 
+	 * @param fileResource
+	 *            the resource to optimize
+	 * @return the results of the optimization
+	 * @throws IOException
+	 *             an exception occurs reading the original resource
+	 */
+	OptimizationResult getOptimizedContents(Resource fileResource) throws IOException;
+
+	/**
 	 * Returns true if the specified resource has already been optimized by the
 	 * FileOptimizer.
 	 * 
@@ -50,7 +61,7 @@ public interface FileOptimizerService {
 	boolean isOptimized(Resource fileResource);
 
 	/**
-	 * Optimizes a file resource.
+	 * Optimizes a file resource. This method will modify the underlying resource.
 	 * 
 	 * @param fileResource
 	 *            the resource to optimize
@@ -65,21 +76,4 @@ public interface FileOptimizerService {
 	 */
 	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
index b9ca48b..21de34b 100644
--- a/src/main/java/org/apache/sling/fileoptim/OptimizationResult.java
+++ b/src/main/java/org/apache/sling/fileoptim/OptimizationResult.java
@@ -16,6 +16,9 @@
  */
 package org.apache.sling.fileoptim;
 
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
 import org.apache.sling.api.resource.Resource;
 
 /**
@@ -25,6 +28,7 @@ public class OptimizationResult {
 
 	private String algorithm;
 	private boolean optimized = false;
+	private byte[] optimizedContents;
 	private long optimizedSize;
 	private long originalSize;
 	private final Resource resource;
@@ -35,6 +39,8 @@ public class OptimizationResult {
 	}
 
 	/**
+	 * Returns the algorithm by which the file was optimized
+	 * 
 	 * @return the algorithm
 	 */
 	public String getAlgorithm() {
@@ -42,6 +48,26 @@ public class OptimizationResult {
 	}
 
 	/**
+	 * Returns the raw optimized contents as a byte array
+	 * 
+	 * @return the optimized contents
+	 */
+	public byte[] getOptimizedContents() {
+		return optimizedContents;
+	}
+
+	/**
+	 * Returns the optimized contents as an InputStream
+	 * 
+	 * @return the optimized content stream
+	 */
+	public InputStream getOptimizedContentStream() {
+		return new ByteArrayInputStream(optimizedContents);
+	}
+
+	/**
+	 * Returns the optimized size in bytes
+	 * 
 	 * @return the optimizedSize
 	 */
 	public long getOptimizedSize() {
@@ -49,6 +75,8 @@ public class OptimizationResult {
 	}
 
 	/**
+	 * Return the original size in bytes
+	 * 
 	 * @return the originalSize
 	 */
 	public long getOriginalSize() {
@@ -56,6 +84,8 @@ public class OptimizationResult {
 	}
 
 	/**
+	 * Returns the resource that was optimized
+	 * 
 	 * @return the resource
 	 */
 	public Resource getResource() {
@@ -63,6 +93,8 @@ public class OptimizationResult {
 	}
 
 	/**
+	 * Return the percent savings as a 1-based double value
+	 * 
 	 * @return the savings
 	 */
 	public double getSavings() {
@@ -70,7 +102,11 @@ public class OptimizationResult {
 	}
 
 	/**
-	 * @return the optimized
+	 * Returns true if the result is actually optimized, if the optimization did not
+	 * provide a smaller result or the file was not optimized for any other reason,
+	 * this will be false.
+	 * 
+	 * @return the optimized flag
 	 */
 	public boolean isOptimized() {
 		return optimized;
@@ -92,6 +128,10 @@ public class OptimizationResult {
 		this.optimized = optimized;
 	}
 
+	public void setOptimizedContents(byte[] optimizedContents) {
+		this.optimizedContents = optimizedContents;
+	}
+
 	/**
 	 * @param optimizedSize
 	 *            the optimizedSize to set
diff --git a/src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerEventHandler.java b/src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerEventHandler.java
index 4803818..5d0364e 100644
--- a/src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerEventHandler.java
+++ b/src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerEventHandler.java
@@ -27,6 +27,7 @@ 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.ConfigurationPolicy;
 import org.osgi.service.component.annotations.Deactivate;
 import org.osgi.service.component.annotations.Modified;
 import org.osgi.service.component.annotations.Reference;
@@ -42,16 +43,12 @@ 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)
+@Component(service = EventHandler.class, immediate = true, configurationPolicy = ConfigurationPolicy.REQUIRE)
 @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)))";
 
@@ -69,14 +66,11 @@ public class FileOptimizerEventHandler implements EventHandler {
 	@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
@@ -94,15 +88,13 @@ public class FileOptimizerEventHandler implements EventHandler {
 	 */
 	@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);
-				}
+		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/FileOptimizerServiceImpl.java b/src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerServiceImpl.java
index a1a7807..165d521 100644
--- a/src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerServiceImpl.java
+++ b/src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerServiceImpl.java
@@ -16,7 +16,6 @@
  */
 package org.apache.sling.fileoptim.impl;
 
-import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
@@ -116,7 +115,7 @@ public class FileOptimizerServiceImpl implements FileOptimizerService, ServiceLi
 			fileResource = fileResource.getChild(JcrConstants.JCR_CONTENT);
 		}
 		OptimizedFile of = fileResource.adaptTo(OptimizedFile.class);
-		return of != null && fileOptimizers.containsKey(of.getMimeType())
+		return of != null && of.getDisabled() != true && fileOptimizers.containsKey(of.getMimeType())
 				&& fileOptimizers.get(of.getMimeType()).size() > 0;
 	}
 
@@ -132,27 +131,15 @@ public class FileOptimizerServiceImpl implements FileOptimizerService, ServiceLi
 		return fileOptimizers;
 	}
 
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see
+	 * org.apache.sling.fileoptim.FileOptimizerService#getOptimizedContents(org.
+	 * apache.sling.api.resource.Resource)
+	 */
 	@Override
-	public boolean isOptimized(Resource fileResource) {
-
-		if (!fileResource.getName().equals(JcrConstants.JCR_CONTENT)) {
-			fileResource = fileResource.getChild(JcrConstants.JCR_CONTENT);
-		}
-
-		OptimizedFile of = fileResource.adaptTo(OptimizedFile.class);
-		try {
-			String calculatedHash = calculateHash(IOUtils.toByteArray(of.getContent()));
-			log.debug("Comparing stored {} and calculated {} hashes", of.getHash(), calculatedHash);
-			return ObjectUtils.equals(of.getHash(), calculatedHash);
-		} catch (IOException e) {
-			log.error("Exception checking if file optimized, assuming false", e);
-			return false;
-		}
-	}
-
-	@Override
-	public OptimizationResult optimizeFile(Resource fileResource, boolean autoCommit) throws IOException {
-
+	public OptimizationResult getOptimizedContents(Resource fileResource) throws IOException {
 		if (!fileResource.getName().equals(JcrConstants.JCR_CONTENT)) {
 			fileResource = fileResource.getChild(JcrConstants.JCR_CONTENT);
 		}
@@ -178,32 +165,12 @@ public class FileOptimizerServiceImpl implements FileOptimizerService, ServiceLi
 						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;
+						result.setOptimizedContents(optimized);
 					} else {
 						log.debug("Optimizer {} was not able to optimize the file", optimizer.getName());
 					}
@@ -218,22 +185,49 @@ public class FileOptimizerServiceImpl implements FileOptimizerService, ServiceLi
 	}
 
 	@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;
-			}
+	public boolean isOptimized(Resource fileResource) {
+
+		if (!fileResource.getName().equals(JcrConstants.JCR_CONTENT)) {
+			fileResource = fileResource.getChild(JcrConstants.JCR_CONTENT);
+		}
+
+		OptimizedFile of = fileResource.adaptTo(OptimizedFile.class);
+		try {
+			String calculatedHash = calculateHash(IOUtils.toByteArray(of.getContent()));
+			log.debug("Comparing stored {} and calculated {} hashes", of.getHash(), calculatedHash);
+			return ObjectUtils.equals(of.getHash(), calculatedHash);
+		} catch (IOException e) {
+			log.error("Exception checking if file optimized, assuming false", e);
+			return false;
 		}
-		if (autoCommit && dirty) {
+	}
+
+	@Override
+	public OptimizationResult optimizeFile(Resource fileResource, boolean autoCommit) throws IOException {
+
+		OptimizationResult result = getOptimizedContents(fileResource);
+
+		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, result.getAlgorithm());
+		mvm.put(OptimizedFile.PN_HASH, calculateHash(result.getOptimizedContents()));
+		mvm.put(OptimizedFile.PN_ORIGINAL, result.getOptimizedContents());
+		mvm.put(OptimizedFile.PN_SAVINGS, result.getSavings());
+
+		OptimizedFile optim = fileResource.adaptTo(OptimizedFile.class);
+		mvm.put(JcrConstants.JCR_DATA, IOUtils.toByteArray(optim.getContent()));
+
+		if (autoCommit) {
 			log.debug("Persisting changes...");
-			fileResources.iterator().next().getResourceResolver().commit();
+			fileResource.getResourceResolver().commit();
 		}
-		return results;
+
+		return result;
+
 	}
 
 	private void rebuildOptimizerCache() {
diff --git a/src/main/java/org/apache/sling/fileoptim/impl/filters/FileOptimizerFilter.java b/src/main/java/org/apache/sling/fileoptim/impl/filters/FileOptimizerFilter.java
new file mode 100644
index 0000000..466c212
--- /dev/null
+++ b/src/main/java/org/apache/sling/fileoptim/impl/filters/FileOptimizerFilter.java
@@ -0,0 +1,109 @@
+/*
+ * 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.filters;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.engine.EngineConstants;
+import org.apache.sling.fileoptim.FileOptimizerService;
+import org.apache.sling.fileoptim.OptimizationResult;
+import org.apache.sling.fileoptim.impl.filters.FileOptimizerFilter.Config;
+import org.apache.sling.fileoptim.models.OptimizedFile;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.ConfigurationPolicy;
+import org.osgi.service.component.annotations.Reference;
+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;
+
+/**
+ * Sling Servlet Filter for optimizing a file response.
+ */
+@Component(service = { Filter.class }, property = { "sling.filter.scope="
+		+ EngineConstants.FILTER_SCOPE_REQUEST }, configurationPolicy = ConfigurationPolicy.REQUIRE)
+@Designate(ocd = Config.class)
+public class FileOptimizerFilter implements Filter {
+
+	@ObjectClassDefinition(name = "%filter.name", description = "%filter.description", localization = "OSGI-INF/l10n/bundle")
+	public @interface Config {
+
+		@AttributeDefinition(name = "%filter.pattern.name", description = "%filter.pattern.description")
+		String sling_filter_pattern() default "-";
+
+		@AttributeDefinition(name = "%filter.service.ranking.name", description = "%filter.service.ranking.name")
+		int service_ranking() default 0;
+
+	}
+
+	private static final Logger log = LoggerFactory.getLogger(FileOptimizerFilter.class);
+
+	@Reference
+	private FileOptimizerService fileOptimizer;
+
+	@Override
+	public void init(FilterConfig filterConfig) throws ServletException {
+	}
+
+	@Override
+	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+			throws IOException, ServletException {
+		if (request instanceof SlingHttpServletRequest) {
+			Resource resource = ((SlingHttpServletRequest) request).getResource();
+			try {
+				if (fileOptimizer.canOptimize(resource)) {
+					log.debug("Returning optimized file");
+					OptimizationResult res = fileOptimizer.getOptimizedContents(resource);
+					if (res.isOptimized()) {
+						OptimizedFile of = null;
+						if (!resource.getName().equals(JcrConstants.JCR_CONTENT)) {
+							of = resource.getChild(JcrConstants.JCR_CONTENT).adaptTo(OptimizedFile.class);
+						} else {
+							of = resource.adaptTo(OptimizedFile.class);
+						}
+						response.setContentType(of.getMimeType());
+						response.setContentLengthLong(res.getOptimizedSize());
+						((HttpServletResponse) response).setHeader("Optimized-With", res.getAlgorithm());
+						IOUtils.copy(res.getOptimizedContentStream(), response.getOutputStream());
+						return;
+					}
+				}
+			} catch (Exception e) {
+				log.warn("Unexpected exception attempting to optimize file response", e);
+			}
+		}
+		chain.doFilter(request, response);
+	}
+
+	@Override
+	public void destroy() {
+	}
+
+}
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
index 639be70..ff7b986 100644
--- a/src/main/java/org/apache/sling/fileoptim/impl/servlets/FileOptimizerData.java
+++ b/src/main/java/org/apache/sling/fileoptim/impl/servlets/FileOptimizerData.java
@@ -23,7 +23,6 @@ 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;
@@ -55,7 +54,7 @@ public class FileOptimizerData extends SlingSafeMethodsServlet {
 			response.sendError(404, "No Resource found at path " + path);
 		} else if (fileOptimizer.canOptimize(resource)) {
 
-			OptimizationResult res = fileOptimizer.optimizeFile(resource.getChild(JcrConstants.JCR_CONTENT), false);
+			OptimizationResult res = fileOptimizer.getOptimizedContents(resource);
 			response.setContentType("application/json");
 
 			JsonGenerator json = Json.createGenerator(response.getWriter());
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
index 53bf7e5..3786097 100644
--- a/src/main/java/org/apache/sling/fileoptim/impl/servlets/FileOptimizerPreview.java
+++ b/src/main/java/org/apache/sling/fileoptim/impl/servlets/FileOptimizerPreview.java
@@ -55,7 +55,7 @@ public class FileOptimizerPreview extends SlingSafeMethodsServlet {
 		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);
+			OptimizationResult res = fileOptimizer.getOptimizedContents(resource);
 			ValueMap vm = res.getResource().getValueMap();
 			response.setContentType(vm.get(JcrConstants.JCR_MIMETYPE, String.class));
 			response.setHeader("Content-disposition", "inline; filename=" + resource.getName());
diff --git a/src/main/java/org/apache/sling/fileoptim/models/OptimizeResource.java b/src/main/java/org/apache/sling/fileoptim/models/OptimizeResource.java
index 2960d1a..218929a 100644
--- a/src/main/java/org/apache/sling/fileoptim/models/OptimizeResource.java
+++ b/src/main/java/org/apache/sling/fileoptim/models/OptimizeResource.java
@@ -30,7 +30,8 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * Sling model for executing the optimizer, will not commit the result
+ * Sling model for executing the optimizer on a resource, will not commit the
+ * result
  */
 @Model(adaptables = Resource.class)
 public class OptimizeResource {
@@ -55,21 +56,36 @@ public class OptimizeResource {
 		log.debug("initializing with resource {}", resource);
 		if (fileOptimizer.canOptimize(resource)) {
 			this.canOptimize = true;
-			this.result = fileOptimizer.optimizeFile(resource, false);
+			this.result = fileOptimizer.getOptimizedContents(resource);
 		} else {
 			this.canOptimize = false;
 			this.result = null;
 		}
 	}
 
+	/**
+	 * Returns true if the file is optimized, false otherwise
+	 * 
+	 * @return
+	 */
 	public boolean isOptimized() {
 		return fileOptimizer.isOptimized(resource);
 	}
 
+	/**
+	 * Gets the optimization result.
+	 * 
+	 * @return
+	 */
 	public OptimizationResult getResult() {
 		return result;
 	}
 
+	/**
+	 * Return true if the file can be optimized
+	 * 
+	 * @return
+	 */
 	public boolean isCanOptimize() {
 		return canOptimize;
 	}
diff --git a/src/main/java/org/apache/sling/fileoptim/models/OptimizedFile.java b/src/main/java/org/apache/sling/fileoptim/models/OptimizedFile.java
index 12a0e66..061701b 100644
--- a/src/main/java/org/apache/sling/fileoptim/models/OptimizedFile.java
+++ b/src/main/java/org/apache/sling/fileoptim/models/OptimizedFile.java
@@ -23,12 +23,13 @@ import javax.inject.Named;
 
 import org.apache.jackrabbit.JcrConstants;
 import org.apache.sling.api.resource.Resource;
+import org.apache.sling.models.annotations.Default;
 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
+ * Sling Model representing a file which can be or has been optimized.
  */
 @Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
 public interface OptimizedFile {
@@ -36,6 +37,7 @@ 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_DISABLED = PREFIX + "disabled";
 	public static final String PN_HASH = PREFIX + "hash";
 	public static final String PN_ORIGINAL = PREFIX + "original";
 	public static final String PN_SAVINGS = PREFIX + "savings";
@@ -44,6 +46,11 @@ public interface OptimizedFile {
 	@Inject
 	String getAlgorithm();
 
+	@Named(PN_DISABLED)
+	@Inject
+	@Default(booleanValues = false)
+	boolean getDisabled();
+
 	@Named(JcrConstants.JCR_DATA)
 	@Inject
 	@Required
diff --git a/src/main/resources/OSGI-INF/l10n/bundle.properties b/src/main/resources/OSGI-INF/l10n/bundle.properties
index 66e75e6..5c5849a 100644
--- a/src/main/resources/OSGI-INF/l10n/bundle.properties
+++ b/src/main/resources/OSGI-INF/l10n/bundle.properties
@@ -49,4 +49,16 @@ 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
+jpeg.optimizer.compression.level.description=The compression level for compressing the Jpeg (between 0.0 - 1.0)
+
+# Filter Keys
+
+filter.name=Apache Sling File Optimizer Filter
+filter.description=Sling Servlet Filter for optimizing a file response
+
+filter.pattern.name=Filter Pattern
+filter.pattern.description=Restrict the filter to paths that match the supplied regular expression
+
+filter.service.ranking.name=Service Ranking
+filter.service.ranking.description=Indication of where to place the filter in the filter chain. The higher the number the \
+earlier in the filter chain.
\ 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
index 204b4b6..f572c95 100644
--- a/src/main/resources/SLING-INF/nodetypes/nodetypes.cnd
+++ b/src/main/resources/SLING-INF/nodetypes/nodetypes.cnd
@@ -27,6 +27,7 @@
 [optim:optimized] 
  mixin  
  - optim:algorithm (string)
+ - optim:disabled (boolean)
  - optim:hash (string)
  - optim:original (binary)
  - optim:savings (double)
\ No newline at end of file


[sling-org-apache-sling-file-optimization] 05/08: Adding unit tests and cleaning up some issues found by unit tests

Posted by dk...@apache.org.
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 5be51e3ab8cbfe74858957723554ea509b67b186
Author: Dan Klco <dk...@apache.org>
AuthorDate: Tue Jun 5 00:17:20 2018 -0400

    Adding unit tests and cleaning up some issues found by unit tests
---
 pom.xml                                            |   6 +-
 .../fileoptim/impl/FileOptimizerServiceImpl.java   |  12 +-
 .../sling/fileoptim/BaseFileOptimizerTest.java     | 156 +++++++++++++++++++++
 .../sling/fileoptim/impl/TestFileOptimizer.java    |  77 ++++++++++
 .../impl/servlets/TestFileOptimizerData.java       |  85 +++++++++++
 src/test/resources/simplelogger.properties         |  19 +++
 6 files changed, 349 insertions(+), 6 deletions(-)

diff --git a/pom.xml b/pom.xml
index 621d606..7998a6f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -182,8 +182,10 @@
 			<artifactId>junit</artifactId>
 		</dependency>
 		<dependency>
-			<groupId>org.jmock</groupId>
-			<artifactId>jmock-junit4</artifactId>
+			<groupId>org.mockito</groupId>
+			<artifactId>mockito-core</artifactId>
+			<version>2.7.16</version>
+			<scope>test</scope>
 		</dependency>
 		<dependency>
 			<groupId>org.slf4j</groupId>
diff --git a/src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerServiceImpl.java b/src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerServiceImpl.java
index 165d521..5e5b9eb 100644
--- a/src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerServiceImpl.java
+++ b/src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerServiceImpl.java
@@ -84,8 +84,9 @@ public class FileOptimizerServiceImpl implements FileOptimizerService, ServiceLi
 	@Modified
 	public void activate(ComponentContext context, Config config) throws InvalidSyntaxException {
 		bundleContext = context.getBundleContext();
-		bundleContext.addServiceListener(this, "(" + Constants.OBJECTCLASS + "=" + FileOptimizer.class.getName() + ")");
 		this.config = config;
+		this.rebuildOptimizerCache();
+		bundleContext.addServiceListener(this, "(" + Constants.OBJECTCLASS + "=" + FileOptimizer.class.getName() + ")");
 	}
 
 	private void addOptimizer(Map<String, List<ServiceReference<FileOptimizer>>> tempCache, String metaType,
@@ -111,7 +112,8 @@ public class FileOptimizerServiceImpl implements FileOptimizerService, ServiceLi
 
 	@Override
 	public boolean canOptimize(Resource fileResource) {
-		if (!fileResource.getName().equals(JcrConstants.JCR_CONTENT)) {
+		if (!fileResource.getName().equals(JcrConstants.JCR_CONTENT)
+				&& fileResource.getChild(JcrConstants.JCR_CONTENT) != null) {
 			fileResource = fileResource.getChild(JcrConstants.JCR_CONTENT);
 		}
 		OptimizedFile of = fileResource.adaptTo(OptimizedFile.class);
@@ -140,7 +142,8 @@ public class FileOptimizerServiceImpl implements FileOptimizerService, ServiceLi
 	 */
 	@Override
 	public OptimizationResult getOptimizedContents(Resource fileResource) throws IOException {
-		if (!fileResource.getName().equals(JcrConstants.JCR_CONTENT)) {
+		if (!fileResource.getName().equals(JcrConstants.JCR_CONTENT)
+				&& fileResource.getChild(JcrConstants.JCR_CONTENT) != null) {
 			fileResource = fileResource.getChild(JcrConstants.JCR_CONTENT);
 		}
 		OptimizationResult result = new OptimizationResult(fileResource);
@@ -187,7 +190,8 @@ public class FileOptimizerServiceImpl implements FileOptimizerService, ServiceLi
 	@Override
 	public boolean isOptimized(Resource fileResource) {
 
-		if (!fileResource.getName().equals(JcrConstants.JCR_CONTENT)) {
+		if (!fileResource.getName().equals(JcrConstants.JCR_CONTENT)
+				&& fileResource.getChild(JcrConstants.JCR_CONTENT) != null) {
 			fileResource = fileResource.getChild(JcrConstants.JCR_CONTENT);
 		}
 
diff --git a/src/test/java/org/apache/sling/fileoptim/BaseFileOptimizerTest.java b/src/test/java/org/apache/sling/fileoptim/BaseFileOptimizerTest.java
new file mode 100644
index 0000000..df1d667
--- /dev/null
+++ b/src/test/java/org/apache/sling/fileoptim/BaseFileOptimizerTest.java
@@ -0,0 +1,156 @@
+/*
+ * 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.InputStream;
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.fileoptim.impl.FileOptimizerServiceImpl;
+import org.apache.sling.fileoptim.models.OptimizedFile;
+import org.apache.sling.fileoptim.optimizers.JpegFileOptimizer;
+import org.mockito.Mockito;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class BaseFileOptimizerTest {
+
+	private static final Logger log = LoggerFactory.getLogger(BaseFileOptimizerTest.class);
+	public FileOptimizerServiceImpl fileOptimizerService;
+
+	private final Map<String, InputStream> FILES = new HashMap<String, InputStream>() {
+		private static final long serialVersionUID = 1L;
+		{
+			put("jpeg", BaseFileOptimizerTest.class.getClassLoader()
+					.getResourceAsStream("valentino-funghi-41239-unsplash.jpg"));
+			put("png", BaseFileOptimizerTest.class.getClassLoader()
+					.getResourceAsStream("Screen Shot 2018-05-29 at 4.17.20 PM.png"));
+		}
+	};
+
+	public BaseFileOptimizerTest() throws InvalidSyntaxException {
+		log.trace("BaseFileOptimizerTest()");
+		fileOptimizerService = new FileOptimizerServiceImpl();
+
+		// mock up the basic OSGi stuff
+		ComponentContext context = Mockito.mock(ComponentContext.class);
+		BundleContext bundleContext = Mockito.mock(BundleContext.class);
+		Mockito.when(context.getBundleContext()).thenReturn(bundleContext);
+
+		// allow for populating the service cache
+		@SuppressWarnings("unchecked")
+		ServiceReference<FileOptimizer> ref = Mockito.mock(ServiceReference.class);
+		Mockito.when(ref.getProperty(FileOptimizer.MIME_TYPE)).thenReturn("image/jpeg");
+		Collection<ServiceReference<FileOptimizer>> references = new ArrayList<ServiceReference<FileOptimizer>>();
+		references.add(ref);
+		Mockito.when(bundleContext.getServiceReferences(FileOptimizer.class, null)).thenReturn(references);
+
+		// allow for retrieving the service
+		JpegFileOptimizer jpegFileOptimizer = new JpegFileOptimizer();
+		jpegFileOptimizer.activate(new org.apache.sling.fileoptim.optimizers.JpegFileOptimizer.Config() {
+			{
+			}
+
+			@Override
+			public Class<? extends Annotation> annotationType() {
+				// TODO Auto-generated method stub
+				return null;
+			}
+
+			@Override
+			public float compressionLevel() {
+				return 0.7f;
+			}
+		});
+		Mockito.when(bundleContext.getService(ref)).thenReturn(jpegFileOptimizer);
+
+		fileOptimizerService.activate(context, new org.apache.sling.fileoptim.impl.FileOptimizerServiceImpl.Config() {
+			public Class<? extends Annotation> annotationType() {
+				return null;
+			}
+
+			public String hashAlgorithm() {
+				return "MD5";
+			}
+		});
+
+	}
+
+	private Resource getValidFile(String type) {
+		Resource fileResource = Mockito.mock(Resource.class);
+		Mockito.when(fileResource.getName()).thenReturn("file." + type);
+
+		Resource contentResource = Mockito.mock(Resource.class);
+		Mockito.when(fileResource.getChild(JcrConstants.JCR_CONTENT)).thenReturn(contentResource);
+
+		OptimizedFile of = Mockito.mock(OptimizedFile.class);
+		Mockito.when(contentResource.adaptTo(OptimizedFile.class)).thenReturn(of);
+		Mockito.when(of.getHash()).thenReturn(null);
+		Mockito.when(of.getContent()).thenReturn(FILES.get(type));
+		Mockito.when(of.getDisabled()).thenReturn(false);
+		Mockito.when(of.getMimeType()).thenReturn("image/" + type);
+		return fileResource;
+	}
+
+	public Resource getOptimizedFile() {
+		Resource fileResource = Mockito.mock(Resource.class);
+		Mockito.when(fileResource.getName()).thenReturn("file.png");
+
+		Resource contentResource = Mockito.mock(Resource.class);
+		Mockito.when(fileResource.getChild(JcrConstants.JCR_CONTENT)).thenReturn(contentResource);
+
+		OptimizedFile of = Mockito.mock(OptimizedFile.class);
+		Mockito.when(contentResource.adaptTo(OptimizedFile.class)).thenReturn(of);
+		Mockito.when(of.getHash()).thenReturn(null);
+		Mockito.when(of.getContent()).thenReturn(FILES.get("png"));
+		Mockito.when(of.getDisabled()).thenReturn(false);
+		Mockito.when(of.getMimeType()).thenReturn("image/png");
+		Mockito.when(of.getHash()).thenReturn("M8SdtVLx2VXMDtteUPLVkQ==");
+		return fileResource;
+	}
+
+	public Resource getDisabled() {
+		Resource fileResource = Mockito.mock(Resource.class);
+		Mockito.when(fileResource.getName()).thenReturn("file.jpg");
+
+		Resource contentResource = Mockito.mock(Resource.class);
+		Mockito.when(fileResource.getChild(JcrConstants.JCR_CONTENT)).thenReturn(contentResource);
+
+		OptimizedFile of = Mockito.mock(OptimizedFile.class);
+		Mockito.when(contentResource.adaptTo(OptimizedFile.class)).thenReturn(of);
+		Mockito.when(of.getDisabled()).thenReturn(true);
+		Mockito.when(of.getMimeType()).thenReturn("image/jpeg");
+		return fileResource;
+	}
+
+	public Resource getValidJpegFile() {
+		return getValidFile("jpeg");
+	}
+
+	public Resource getValidPngFile() {
+		return getValidFile("png");
+	}
+}
diff --git a/src/test/java/org/apache/sling/fileoptim/impl/TestFileOptimizer.java b/src/test/java/org/apache/sling/fileoptim/impl/TestFileOptimizer.java
new file mode 100644
index 0000000..92098b2
--- /dev/null
+++ b/src/test/java/org/apache/sling/fileoptim/impl/TestFileOptimizer.java
@@ -0,0 +1,77 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+
+import org.apache.sling.fileoptim.BaseFileOptimizerTest;
+import org.apache.sling.fileoptim.OptimizationResult;
+import org.junit.Test;
+import org.osgi.framework.InvalidSyntaxException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TestFileOptimizer extends BaseFileOptimizerTest {
+
+	public TestFileOptimizer() throws InvalidSyntaxException {
+		super();
+	}
+
+	private static Logger log = LoggerFactory.getLogger(TestFileOptimizer.class);
+
+	@Test
+	public void testCanOptimize() {
+		log.info("testCanOptimize");
+		assertTrue(fileOptimizerService.canOptimize(getValidJpegFile()));
+	}
+
+	@Test
+	public void testCantOptimize() {
+		log.info("testCantOptimize");
+		assertFalse(fileOptimizerService.canOptimize(getValidPngFile()));
+	}
+
+	@Test
+	public void testDisabled() {
+		log.info("testDisabled");
+		assertFalse(fileOptimizerService.canOptimize(getDisabled()));
+	}
+	
+
+	@Test
+	public void isOptimized() {
+		log.info("isOptimized");
+		assertTrue(fileOptimizerService.isOptimized(super.getOptimizedFile()));
+	}
+
+	@Test
+	public void testGetOptimizedContents() throws IOException {
+		log.info("testGetOptimizedContents");
+		OptimizationResult res = fileOptimizerService.getOptimizedContents(getValidJpegFile());
+		assertNotNull(res);
+		assertTrue(res.isOptimized());
+		assertNotNull(res.getOptimizedContents());
+		assertEquals(res.getOptimizedContents().length, res.getOptimizedSize());
+		assertTrue(res.getSavings() > 0.0 && res.getSavings() < 1.0);
+	}
+
+}
diff --git a/src/test/java/org/apache/sling/fileoptim/impl/servlets/TestFileOptimizerData.java b/src/test/java/org/apache/sling/fileoptim/impl/servlets/TestFileOptimizerData.java
new file mode 100644
index 0000000..c39a22a
--- /dev/null
+++ b/src/test/java/org/apache/sling/fileoptim/impl/servlets/TestFileOptimizerData.java
@@ -0,0 +1,85 @@
+/*
+ * 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 static org.junit.Assert.assertNotNull;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.Field;
+
+import javax.servlet.ServletException;
+
+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.ResourceResolver;
+import org.apache.sling.fileoptim.BaseFileOptimizerTest;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.osgi.framework.InvalidSyntaxException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TestFileOptimizerData extends BaseFileOptimizerTest {
+
+	public TestFileOptimizerData() throws InvalidSyntaxException {
+		super();
+		log.trace("TestFileOptimizerData()");
+	}
+
+	private static final Logger log = LoggerFactory.getLogger(TestFileOptimizerData.class);
+	private static final String PATH = "/content/file.jpg";
+	private StringWriter writer;
+	private SlingHttpServletRequest request;
+	private SlingHttpServletResponse response;
+	private FileOptimizerData servlet;
+
+	@Before
+	public void initTest() throws IOException, NoSuchFieldException, SecurityException, IllegalArgumentException,
+			IllegalAccessException {
+		log.trace("initTest");
+		request = Mockito.mock(SlingHttpServletRequest.class);
+		Mockito.when(request.getParameter("path")).thenReturn(PATH);
+		ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
+		Mockito.when(request.getResourceResolver()).thenReturn(resolver);
+		Resource jpegResource = getValidJpegFile();
+		Mockito.when(resolver.getResource(PATH)).thenReturn(jpegResource);
+
+		this.writer = new StringWriter();
+		response = Mockito.mock(SlingHttpServletResponse.class);
+		Mockito.when(response.getWriter()).thenReturn(new PrintWriter(writer));
+
+		servlet = new FileOptimizerData();
+		Field fo = FileOptimizerData.class.getDeclaredField("fileOptimizer");
+		fo.setAccessible(true);
+		fo.set(servlet, this.fileOptimizerService);
+	}
+
+	@Test
+	public void testServlet() throws ServletException, IOException {
+
+		servlet.doGet(request, response);
+
+		String response = writer.toString();
+		assertNotNull(response);
+		log.info(response);
+		
+	}
+}
diff --git a/src/test/resources/simplelogger.properties b/src/test/resources/simplelogger.properties
new file mode 100644
index 0000000..e6cb9e8
--- /dev/null
+++ b/src/test/resources/simplelogger.properties
@@ -0,0 +1,19 @@
+#
+#  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.slf4j.simpleLogger.defaultLogLevel=trace
\ No newline at end of file


[sling-org-apache-sling-file-optimization] 06/08: Moving fileoptim artifactId out of commons

Posted by dk...@apache.org.
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 50e35801f89dadc96a9c8d4899c2dff485978137
Author: Dan Klco <dk...@apache.org>
AuthorDate: Thu Jun 7 00:17:51 2018 -0400

    Moving fileoptim artifactId out of commons
---
 pom.xml | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/pom.xml b/pom.xml
index 7998a6f..8376e1b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
 		<relativePath />
 	</parent>
 
-	<artifactId>org.apache.sling.commons.fileoptim</artifactId>
+	<artifactId>org.apache.sling.fileoptim</artifactId>
 	<version>1.0.0-SNAPSHOT</version>
 	<packaging>bundle</packaging>
 
@@ -30,7 +30,6 @@
         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>


[sling-org-apache-sling-file-optimization] 08/08: Fixing typo

Posted by dk...@apache.org.
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 81ba9ca221ce6ddfa7a6a6e962e88fa6eabbfdc1
Author: Dan Klco <dk...@apache.org>
AuthorDate: Fri Jun 29 12:11:20 2018 -0400

    Fixing typo
---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 3ccd232..f423f07 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ The file optimizer includes the ability to revert the optimized file either usin
 
 This module is part of the [Apache Sling](https://sling.apache.org) project.
 
-## Using the File Opimization Library
+## Using the File Optimization Library
 
 There are four different methods for interacting with the File Optimizer: using the Event Handler, interacting with the Servlets / Post Operations, using the Filter or invoking the API directly. 
 


[sling-org-apache-sling-file-optimization] 07/08: Adding documentation

Posted by dk...@apache.org.
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 4fc02b58ffe2e9c029e70ecfaa9a51f57869f21e
Author: Dan Klco <dk...@apache.org>
AuthorDate: Fri Jun 29 12:03:35 2018 -0400

    Adding documentation
---
 README.md | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 99 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 9ceb056..3ccd232 100644
--- a/README.md
+++ b/README.md
@@ -6,4 +6,102 @@
 
 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
+The file optimizer includes the ability to revert the optimized file either using the Sling Post Operation or via the API. Resources can be excluded from optimization by setting the `optim:disabled` attribute to `true`.
+
+This module is part of the [Apache Sling](https://sling.apache.org) project.
+
+## Using the File Opimization Library
+
+There are four different methods for interacting with the File Optimizer: using the Event Handler, interacting with the Servlets / Post Operations, using the Filter or invoking the API directly. 
+
+### Event Handler
+
+The File Optimizer Event Handler can be used to automatically optimize file resources when Sling Events occur. The File Optimizer Event Handler is not enabled by default, to enable it, you will need to enable the Event Handler with an OSGi Config like:
+
+    # Example Event Filter
+    event.filter=(&(resourceType=sling:File)(|(path=*.png)(path=*.jpg)))
+    # Example Event Topic
+    event.topic=org/apache/sling/api/resource/Resource/CHANGED
+    
+### Servlet / Post Operations
+
+There are four servlets / Sling Post Servlet operations for interacting with the File Optimization API.
+
+#### OptimizeFileOperation
+
+This operation will optimize a File resource. Example usage:
+
+    curl -d ":operation=fileoptim:optimize" -X POST http://localhost:8080/content/afile.jpg
+
+#### RestoreOriginalOperation
+
+This operation will restore the original contents of an optimized File resource. Example usage:
+
+    curl -d ":operation=fileoptim:restore" -X POST http://localhost:8080/content/afile.jpg
+
+#### FileOptimizerData
+
+This servlet will return the JSON data for the results of an optimization operation  if it were run against the specified resource. Example usage:
+
+    curl http://localhost:8080/system/fileoptim.json?path=/content/afile.jpg
+    
+Example response:
+
+    {
+        "algorithm": "Apache Sling JPEG File Optimizer",
+        "originalSize": 1000,
+        "optimizedSize", 500,
+        "optimized", false,
+        "preview", "/system/fileoptim/preview?path=/content/afile.jpg",
+        "savings", 0.5
+    }
+
+#### FileOptimizerPreview
+
+This servlet will return the optimized binary as if the File Optimizer were run against the specified resource. Example usage:
+
+    curl http://localhost:8080/system/fileoptim/preview?path=/content/afile.jpg
+
+### Filter
+
+The File Optimizer Filter can be used to automatically optimize file resources when serving the content. The File Optimizer Filter is not enabled by default, to enable it, you will need to enable the Filter with an OSGi Config like:
+
+    # Example Filter Scope
+    sling.filter.scope=REQUEST
+
+### API
+
+The File Optimizer service can be retrieved by reference, for example:
+
+    @Reference
+    private FileOptimizerService fileOptimSvc;
+    
+    public void optimizeFile(Resource fileResource) {
+        fileOptimSvc.optimizeFile(fileResource, true);
+    }
+    
+Additionally, there are two Sling mdoels for discovering the optimization information of resources.
+
+ - *org.apache.sling.fileoptim.models.OptimizedFile* - Allows for retrieval of the data from a file which has been optimized
+ - *org.apache.sling.fileoptim.models.OptimizeResource* - Allows for determining if a resource can be optimized and what the results would be if it were
+
+## Defining a File Optimizer
+
+File optimizers are used by the library to optimize resources based on the file mime type. Each File Optimizer should implement the [FileOptimizer](src/main/java/org/apache/sling/fileoptim/FileOptimizer.java) interface, setting the *mime.type* property to the MimeTypes for which the optimizer is applicable. The [Service Rankin](https://osgi.org/javadoc/r2/org/osgi/framework/Constants.html#SERVICE_RANKING) property can be used to override the default File Optimizers.
+
+    @Component(service = FileOptimizer.class, property = { FileOptimizer.MIME_TYPE + "=image/jpeg" })
+    public class DevNullFileOptimizer implements FileOptimizer {
+
+        private static final Logger log = LoggerFactory.getLogger(DevNullFileOptimizer.class);
+
+        @Override
+        public byte[] optimizeFile(byte[] original, String metaType) {
+            // TODO: Actually do something with the file contents here and return the optimized file
+            return new byte[0];
+        }
+
+        @Override
+        public String getName() {
+            return "/dev/null File Optimizer";
+        }
+    }


[sling-org-apache-sling-file-optimization] 02/08: Adding a new model and restore operation to support the needs from Sling CMS

Posted by dk...@apache.org.
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 92100d1a845791fa4412b2bded11987d09b5f938
Author: Dan Klco <dk...@apache.org>
AuthorDate: Thu May 31 08:00:50 2018 -0400

    Adding a new model and restore operation to support the needs from Sling
    CMS
---
 .../sling/fileoptim/FileOptimizerService.java      | 14 +++-
 .../fileoptim/impl/FileOptimizerServiceImpl.java   | 19 ++++++
 ...erOperation.java => OptimizeFileOperation.java} |  6 +-
 ...peration.java => RestoreOriginalOperation.java} | 24 +++++--
 .../sling/fileoptim/models/OptimizeResource.java   | 76 ++++++++++++++++++++++
 .../sling/fileoptim/models/OptimizedFile.java      |  9 +++
 6 files changed, 137 insertions(+), 11 deletions(-)

diff --git a/src/main/java/org/apache/sling/fileoptim/FileOptimizerService.java b/src/main/java/org/apache/sling/fileoptim/FileOptimizerService.java
index 6d88fbc..c63b43a 100644
--- a/src/main/java/org/apache/sling/fileoptim/FileOptimizerService.java
+++ b/src/main/java/org/apache/sling/fileoptim/FileOptimizerService.java
@@ -40,6 +40,16 @@ public interface FileOptimizerService {
 	boolean canOptimize(Resource fileResource);
 
 	/**
+	 * Returns true if the specified resource has already been optimized by the
+	 * FileOptimizer.
+	 * 
+	 * @param fileResource
+	 *            the resource to check
+	 * @return true if optimized by the file optimizer, false otherwise
+	 */
+	boolean isOptimized(Resource fileResource);
+
+	/**
 	 * Optimizes a file resource.
 	 * 
 	 * @param fileResource
@@ -50,7 +60,7 @@ public interface FileOptimizerService {
 	 * @return the results of the optimization
 	 * @throws PersistenceException
 	 *             an exception occurs saving the optimized resource
-	 * @throws IOException 
+	 * @throws IOException
 	 *             an exception occurs reading the original resource
 	 */
 	OptimizationResult optimizeFile(Resource fileResource, boolean autoCommit) throws PersistenceException, IOException;
@@ -66,7 +76,7 @@ public interface FileOptimizerService {
 	 * @return the results of the optimization
 	 * @throws PersistenceException
 	 *             an exception occurs saving the optimized resources
-	 * @throws IOException 
+	 * @throws IOException
 	 *             an exception occurs reading the original resources
 	 */
 	Map<String, OptimizationResult> optimizeFiles(Collection<Resource> fileResources, boolean autoCommit)
diff --git a/src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerServiceImpl.java b/src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerServiceImpl.java
index 1483e4e..a1a7807 100644
--- a/src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerServiceImpl.java
+++ b/src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerServiceImpl.java
@@ -32,6 +32,7 @@ import java.util.Set;
 
 import org.apache.commons.codec.binary.Base64;
 import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.ObjectUtils;
 import org.apache.commons.lang.StringUtils;
 import org.apache.jackrabbit.JcrConstants;
 import org.apache.sling.api.resource.ModifiableValueMap;
@@ -132,6 +133,24 @@ public class FileOptimizerServiceImpl implements FileOptimizerService, ServiceLi
 	}
 
 	@Override
+	public boolean isOptimized(Resource fileResource) {
+
+		if (!fileResource.getName().equals(JcrConstants.JCR_CONTENT)) {
+			fileResource = fileResource.getChild(JcrConstants.JCR_CONTENT);
+		}
+
+		OptimizedFile of = fileResource.adaptTo(OptimizedFile.class);
+		try {
+			String calculatedHash = calculateHash(IOUtils.toByteArray(of.getContent()));
+			log.debug("Comparing stored {} and calculated {} hashes", of.getHash(), calculatedHash);
+			return ObjectUtils.equals(of.getHash(), calculatedHash);
+		} catch (IOException e) {
+			log.error("Exception checking if file optimized, assuming false", e);
+			return false;
+		}
+	}
+
+	@Override
 	public OptimizationResult optimizeFile(Resource fileResource, boolean autoCommit) throws IOException {
 
 		if (!fileResource.getName().equals(JcrConstants.JCR_CONTENT)) {
diff --git a/src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerOperation.java b/src/main/java/org/apache/sling/fileoptim/impl/OptimizeFileOperation.java
similarity index 94%
copy from src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerOperation.java
copy to src/main/java/org/apache/sling/fileoptim/impl/OptimizeFileOperation.java
index 5b47fb4..89539a4 100644
--- a/src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerOperation.java
+++ b/src/main/java/org/apache/sling/fileoptim/impl/OptimizeFileOperation.java
@@ -40,10 +40,10 @@ 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 {
+		+ "=fileoptim:optimize")
+public class OptimizeFileOperation implements PostOperation {
 
-	private static final Logger log = LoggerFactory.getLogger(FileOptimizerOperation.class);
+	private static final Logger log = LoggerFactory.getLogger(OptimizeFileOperation.class);
 
 	@Reference
 	private FileOptimizerService fileOptimizer;
diff --git a/src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerOperation.java b/src/main/java/org/apache/sling/fileoptim/impl/RestoreOriginalOperation.java
similarity index 78%
rename from src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerOperation.java
rename to src/main/java/org/apache/sling/fileoptim/impl/RestoreOriginalOperation.java
index 5b47fb4..638f821 100644
--- a/src/main/java/org/apache/sling/fileoptim/impl/FileOptimizerOperation.java
+++ b/src/main/java/org/apache/sling/fileoptim/impl/RestoreOriginalOperation.java
@@ -17,6 +17,7 @@
 package org.apache.sling.fileoptim.impl;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -24,9 +25,12 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import org.apache.jackrabbit.JcrConstants;
 import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.ModifiableValueMap;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.fileoptim.FileOptimizerService;
+import org.apache.sling.fileoptim.models.OptimizedFile;
 import org.apache.sling.servlets.post.Modification;
 import org.apache.sling.servlets.post.PostOperation;
 import org.apache.sling.servlets.post.PostResponse;
@@ -37,13 +41,13 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * The File Optimization operation will optimize a file
+ * The File Optimization operation restore the original file
  */
 @Component(immediate = true, service = { PostOperation.class }, property = PostOperation.PROP_OPERATION_NAME
-		+ "=fileoptim")
-public class FileOptimizerOperation implements PostOperation {
+		+ "=fileoptim:restore")
+public class RestoreOriginalOperation implements PostOperation {
 
-	private static final Logger log = LoggerFactory.getLogger(FileOptimizerOperation.class);
+	private static final Logger log = LoggerFactory.getLogger(RestoreOriginalOperation.class);
 
 	@Reference
 	private FileOptimizerService fileOptimizer;
@@ -51,8 +55,16 @@ public class FileOptimizerOperation implements PostOperation {
 	protected void doRun(SlingHttpServletRequest request, PostResponse response, List<Modification> changes)
 			throws IOException {
 		Resource resource = request.getResource();
-		if (fileOptimizer.canOptimize(resource)) {
-			fileOptimizer.optimizeFile(resource, true);
+		if (fileOptimizer.isOptimized(resource)) {
+			ModifiableValueMap mvm = resource.getChild(JcrConstants.JCR_CONTENT).adaptTo(ModifiableValueMap.class);
+			mvm.put(JcrConstants.JCR_DATA, mvm.get(OptimizedFile.PN_ORIGINAL, InputStream.class));
+			mvm.remove(OptimizedFile.PN_ORIGINAL);
+			mvm.remove(OptimizedFile.PN_ALGORITHM);
+			mvm.remove(OptimizedFile.PN_HASH);
+			mvm.remove(OptimizedFile.PN_SAVINGS);
+			
+			resource.getResourceResolver().commit();
+			
 			changes.add(Modification.onModified(resource.getPath()));
 		}
 	}
diff --git a/src/main/java/org/apache/sling/fileoptim/models/OptimizeResource.java b/src/main/java/org/apache/sling/fileoptim/models/OptimizeResource.java
new file mode 100644
index 0000000..2960d1a
--- /dev/null
+++ b/src/main/java/org/apache/sling/fileoptim/models/OptimizeResource.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.models;
+
+import java.io.IOException;
+
+import javax.annotation.PostConstruct;
+
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.fileoptim.FileOptimizerService;
+import org.apache.sling.fileoptim.OptimizationResult;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.annotations.injectorspecific.OSGiService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Sling model for executing the optimizer, will not commit the result
+ */
+@Model(adaptables = Resource.class)
+public class OptimizeResource {
+
+	private static final Logger log = LoggerFactory.getLogger(OptimizeResource.class);
+
+	private boolean canOptimize;
+
+	@OSGiService
+	private FileOptimizerService fileOptimizer;
+
+	private OptimizationResult result;
+
+	private Resource resource;
+
+	public OptimizeResource(Resource resource) {
+		this.resource = resource;
+	}
+
+	@PostConstruct
+	public void init() throws PersistenceException, IOException {
+		log.debug("initializing with resource {}", resource);
+		if (fileOptimizer.canOptimize(resource)) {
+			this.canOptimize = true;
+			this.result = fileOptimizer.optimizeFile(resource, false);
+		} else {
+			this.canOptimize = false;
+			this.result = null;
+		}
+	}
+
+	public boolean isOptimized() {
+		return fileOptimizer.isOptimized(resource);
+	}
+
+	public OptimizationResult getResult() {
+		return result;
+	}
+
+	public boolean isCanOptimize() {
+		return canOptimize;
+	}
+}
diff --git a/src/main/java/org/apache/sling/fileoptim/models/OptimizedFile.java b/src/main/java/org/apache/sling/fileoptim/models/OptimizedFile.java
index e531a56..12a0e66 100644
--- a/src/main/java/org/apache/sling/fileoptim/models/OptimizedFile.java
+++ b/src/main/java/org/apache/sling/fileoptim/models/OptimizedFile.java
@@ -40,6 +40,10 @@ public interface OptimizedFile {
 	public static final String PN_ORIGINAL = PREFIX + "original";
 	public static final String PN_SAVINGS = PREFIX + "savings";
 
+	@Named(PN_ALGORITHM)
+	@Inject
+	String getAlgorithm();
+
 	@Named(JcrConstants.JCR_DATA)
 	@Inject
 	@Required
@@ -57,4 +61,9 @@ public interface OptimizedFile {
 	@Named(PN_ORIGINAL)
 	@Inject
 	InputStream getOriginal();
+
+	@Named(PN_SAVINGS)
+	@Inject
+	double getSavings();
+
 }