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