├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src └── main ├── doc └── plain-include.png ├── java └── com │ └── cognifide │ └── cq │ └── includefilter │ ├── CacheControlFilter.java │ ├── Configuration.java │ ├── ConfigurationWhiteboard.java │ ├── IncludeTagWritingFilter.java │ ├── MutableUrl.java │ ├── SyntheticResourceIncludingFilter.java │ ├── SyntheticResourceIncludingServlet.java │ └── generator │ ├── IncludeGenerator.java │ ├── IncludeGeneratorWhiteboard.java │ └── types │ ├── EsiGenerator.java │ ├── JsiGenerator.java │ └── SsiGenerator.java └── resources └── generators └── jquery.html /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | .classpath 3 | .settings 4 | .project 5 | /target 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2012 Wunderman Thompson Technology 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sling Dynamic Include 2 | 3 | ___ 4 | **Now part of Apache Sling** 5 | 6 | Sling Dynamic Include (SDI) project has been [contributed to the Apache Sling project](https://github.com/apache/sling-org-apache-sling-dynamic-include). 7 | 8 | **SDI is no longer maintained here** - this Github project has been left as an information hub only. All tickets should be raised in the [Apache Sling jira](https://issues.apache.org/jira/projects/SLING). 9 | ___ 10 | 11 | 12 | 13 | ## Purpose 14 | 15 | The purpose of the module presented here is to replace dynamic generated components (eg. current time or foreign exchange rates) with server-side include tag (eg. [SSI](http://httpd.apache.org/docs/current/howto/ssi.html) or [ESI](http://www.w3.org/TR/esi-lang)). Therefore the dispatcher is able to cache the whole page but dynamic components are generated and included with every request. Components to include are chosen in filter configuration using `resourceType` attribute. 16 | 17 | When the filter intercepts request for a component with given `resourceType`, it'll return a server-side include tag (eg. `` for Apache server). However the path is extended by new selector (`nocache` by default). This is required because filter has to know when to return actual content. 18 | 19 | Components don't have to be modified in order to use this module (or even aware of its existence). It's servlet filter, installed as an OSGi bundle and it can be enabled, disabled or reconfigured without touching CQ installation. 20 | 21 | ## Prerequisites 22 | 23 | * CQ / Apache Sling 2 24 | * Maven 2.x, 3.x 25 | 26 | ## Installation 27 | 28 | Add following dependency to your project: 29 | 30 | 31 | com.cognifide.cq 32 | sling-dynamic-include 33 | 2.0.4 34 | 35 | 36 | ## Configuration 37 | 38 | Filter is delivered as a standard OSGi bundle. SDI is configured via the configuration factory called *SDI Configuration*. Following properties are available: 39 | 40 | * **Enabled** - enable SDI 41 | * **Base path** - given SDI configuration will be enabled only for this 42 | path 43 | * **Resource types** - which components should be replaced with tags 44 | * **Include type** - type of include tag (Apache SSI, ESI or Javascript) 45 | * **Add comment** - adds debug comment: `` to every replaced component 46 | * **Filter selector** - selector used to get actual content 47 | * **Component TTL** - time to live in seconds, set for rendered component (require Dispatcher 4.1.11+) 48 | * **Required header** - SDI will be enabled only if the configured header is present in the request. By default it's `Server-Agent=Communique-Dispatcher` header, added by the AEM dispatcher. You may enter just the header name only or the name and the value split with `=`. 49 | * **Ignore URL params** - SDI normally skips all requests containing any GET parameters. This option allows to set a list of parameters that should be ignored in the test. See the [Ignoring URL parameters](https://docs.adobe.com/docs/en/dispatcher/disp-config.html#Ignoring%20URL%20Parameters) section in the dispatcher documentation. 50 | * **Include path rewriting** -- enable rewriting link (according to sling mappings) that is used for dynamic content including. 51 | 52 | ## Compatibility with components 53 | 54 | Filter is incompatible with following types of component: 55 | 56 | * components which handles POST requests or GET parameters, 57 | * synthetic components which uses suffixes (because suffix is used to pass `requestType` of the synthetic resource). 58 | 59 | If component do not generate HTML but eg. JS or binary data then remember to turn off *Comment* option in configuration. 60 | 61 | ## Enabling SSI in Apache & dispatcher 62 | 63 | In order to enable SSI in Apache with dispatcher first enable `Include` mod (on Debian: `a2enmod include`). Then add `Includes` option to the `Options` directive in your virtual configuration host. After that find following lines in `dispatcher.conf` file: 64 | 65 | 66 | SetHandler dispatcher-handler 67 | 68 | 69 | and modify it: 70 | 71 | 72 | SetHandler dispatcher-handler 73 | 74 | SetOutputFilter INCLUDES 75 | 76 | After setting output filter open virtualhost configuration and add `Includes` option to `Options` directive: 77 | 78 | 79 | Options FollowSymLinks Includes 80 | AllowOverride None 81 | 82 | 83 | Options Indexes FollowSymLinks MultiViews Includes 84 | AllowOverride None 85 | Order allow,deny 86 | allow from all 87 | 88 | 89 | It's also a good idea to disable the caching for `.nocache.html` files in `dispatcher.any` config file. Just add: 90 | 91 | /disable-nocache 92 | { 93 | /glob "*.nocache.html*" 94 | /type "deny" 95 | } 96 | 97 | at the end of the `/rules` section. 98 | 99 | ## Enabling TTL in dispatcher 4.1.11+ 100 | In order to enable TTL on Apache with dispatcher just add: 101 | 102 | /enableTTL "1" 103 | 104 | to your dispatcher configuration. 105 | 106 | 107 | ## Enabling ESI in Varnish 108 | 109 | Just add following lines at the beginning of the `vcl_fetch` section in `/etc/varnish/default.vcl` file: 110 | 111 | if(req.url ~ "\.nocache.html") { 112 | set beresp.ttl = 0s; 113 | } else if (req.url ~ "\.html") { 114 | set beresp.do_esi = true; 115 | } 116 | 117 | It'll enable ESI includes in `.html` files and disable caching of the `.nocache.html` files. 118 | 119 | ## JavaScript Include 120 | 121 | Dynamic Include Filter can also replace dynamic components with AJAX tags, so they are loaded by the browser. It's called JSI. In the current version jQuery framework is used. More attention is required if included component has some Javascript code. Eg. Geometrixx Carousel component won't work because it's initialization is done in page `` section while the component itself is still not loaded. 122 | 123 | ## Plain and synthetic resources 124 | 125 | There are two cases: the first involves including a component which is available at some URL, eg. 126 | 127 | /content/geometrixx/en/jcr:content/carousel.html 128 | 129 | In this case, component is replaced with include tag, and `nocache` selector is added 130 | 131 | 132 | 133 | If the filter gets request with selector it'll pass it (using `doChain`) further without taking any action. 134 | 135 | ![Plain include](https://raw.github.com/wttech/Sling-Dynamic-Include/master/src/main/doc/plain-include.png) 136 | 137 | There are also components which are created from so-called synthetic resources. Synthetic resource have some resourceType and path, but they don't have any node is JCR repository. An example is 138 | 139 | /content/geometrixx/en/jcr:content/userinfo 140 | 141 | component with `foundation/components/userinfo` resource type. These components return 404 error if you try to make a HTTP request. SDI recognizes these components and forms a different include URL for them in which resource type is added as a suffix, eg.: 142 | 143 | /content/geometrixx/en/jcr:content/userinfo.nocache.html/foundation/components/userinfo 144 | 145 | If filter got such request, it'll try to emulate `` JSP tag and includes resource with given type and `nocache` selector: 146 | 147 | /content/geometrixx/en/jcr:content/userinfo.nocache.html 148 | 149 | Selector is necessary, because otherwise filter would again replace component with a SSI tag. 150 | 151 | # External resources 152 | 153 | * [SDI presentation](http://www.pro-vision.de/content/medialib/pro-vision/production/adaptto/2012/adaptto2012-sling-dynamic-include-tomasz-rekaweki-pdf/_jcr_content/renditions/rendition.file/adaptto2012-sling-dynamic-include-tomasz-rekaweki.pdf) on [adaptTo() 2012](http://www.pro-vision.de/de/adaptto/adaptto-2012.html) 154 | * [SDI blog](http://www.cognifide.com/blogs/cq/sling-dynamic-include/) post on the Cognifide website 155 | * See the [Apache Sling website](http://sling.apache.org/) for the Sling reference documentation. Apache Sling, Apache and Sling are trademarks of the [Apache Software Foundation](http://apache.org). 156 | 157 | # Release notes 158 | 159 | ## 2.2.0 160 | 161 | \#17 Support for time-based (TTL) caching, Dispatcher 4.1.11+ required 162 | 163 | # Integrated with Sling 164 | 165 | Sling Dynamic Include (SDI) project has been contributed to the Apache Sling project as [an extension](https://github.com/apache/sling/tree/trunk/contrib/extensions/sling-dynamic-include). 166 | See the official [announcement](https://www.cognifide.com/our-blogs/cq/contributing-sling-dynamic-include-to-apache-sling) on the Cognifide website. 167 | 168 | This Github project has been left as an information hub. 169 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | org.sonatype.oss 5 | oss-parent 6 | 7 7 | 8 | 9 | 4.0.0 10 | com.cognifide.cq 11 | sling-dynamic-include 12 | Sling Dynamic Include 13 | 2.2.1-SNAPSHOT 14 | bundle 15 | 16 | Wunderman Thompson Technology 17 | http://cognifide.com 18 | 19 | 20 | Dynamic include filter for Apache Sling 21 | https://github.com/wttech/Sling-Dynamic-Include 22 | 23 | 24 | The Apache Software License, Version 2.0 25 | http://www.apache.org/licenses/LICENSE-2.0.txt 26 | repo 27 | 28 | 29 | 30 | 31 | UTF-8 32 | http://localhost:4502/system/console 33 | admin 34 | admin 35 | 36 | 37 | 38 | scm:git:git@github.com:wttech/Sling-Dynamic-Include.git 39 | scm:git:git@github.com:wttech/Sling-Dynamic-Include.git 40 | scm:git:git@github.com:wttech/Sling-Dynamic-Include.git 41 | 42 | 43 | 44 | 45 | paco 46 | Przemyslaw Pakulski 47 | przemo.pakulski@cognifide.com 48 | 49 | 50 | trekawek 51 | Tomasz Rekawek 52 | tomasz.rekawek@cognifide.com 53 | 54 | 55 | 56 | 57 | 58 | 59 | org.apache.felix 60 | maven-scr-plugin 61 | 1.15.0 62 | 63 | 64 | generate-scr-scrdescriptor 65 | 66 | scr 67 | 68 | 69 | 70 | 71 | 72 | org.apache.maven.plugins 73 | maven-compiler-plugin 74 | 2.3.2 75 | 76 | true 77 | 1.6 78 | 1.6 79 | 1.6 80 | 81 | 82 | 83 | org.apache.felix 84 | maven-bundle-plugin 85 | 2.4.0 86 | true 87 | 88 | 89 | com.cognifide.cq.includefilter.* 90 | ${project.groupId}.${project.artifactId} 91 | ${project.name} 92 | Wunderman Thompson Technology 93 | false 94 | 95 | 96 | 97 | 98 | org.apache.sling 99 | maven-sling-plugin 100 | 2.1.0 101 | 102 | ${project.build.directory}/${project.build.finalName}.jar 103 | ${sling.username} 104 | ${sling.password} 105 | ${sling.url} 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | org.osgi 115 | org.osgi.core 116 | 4.2.0 117 | provided 118 | 119 | 120 | org.osgi 121 | org.osgi.compendium 122 | 4.2.0 123 | provided 124 | 125 | 126 | org.apache.felix 127 | org.apache.felix.scr.annotations 128 | 1.9.4 129 | provided 130 | 131 | 132 | 133 | 134 | javax.servlet 135 | servlet-api 136 | 2.4 137 | provided 138 | 139 | 140 | 141 | 142 | org.apache.sling 143 | org.apache.sling.api 144 | 2.2.0 145 | provided 146 | 147 | 148 | org.apache.sling 149 | org.apache.sling.engine 150 | 2.2.0 151 | provided 152 | 153 | 154 | org.apache.sling 155 | org.apache.sling.commons.osgi 156 | 2.2.0 157 | provided 158 | 159 | 160 | 161 | 162 | org.slf4j 163 | slf4j-api 164 | 1.7.0 165 | provided 166 | 167 | 168 | 169 | 170 | commons-lang 171 | commons-lang 172 | 2.4 173 | provided 174 | 175 | 176 | 177 | 178 | 179 | release-sign-artifacts 180 | 181 | 182 | performRelease 183 | true 184 | 185 | 186 | 187 | 188 | 189 | org.apache.maven.plugins 190 | maven-gpg-plugin 191 | 1.4 192 | 193 | 194 | sign-artifacts 195 | verify 196 | 197 | sign 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | -------------------------------------------------------------------------------- /src/main/doc/plain-include.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wttech/Sling-Dynamic-Include/616f2efe19dc85f6ded2bb2835c3e865b0775baa/src/main/doc/plain-include.png -------------------------------------------------------------------------------- /src/main/java/com/cognifide/cq/includefilter/CacheControlFilter.java: -------------------------------------------------------------------------------- 1 | package com.cognifide.cq.includefilter; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.Filter; 6 | import javax.servlet.FilterChain; 7 | import javax.servlet.FilterConfig; 8 | import javax.servlet.ServletException; 9 | import javax.servlet.ServletRequest; 10 | import javax.servlet.ServletResponse; 11 | 12 | import org.apache.felix.scr.annotations.Reference; 13 | import org.apache.felix.scr.annotations.sling.SlingFilter; 14 | import org.apache.felix.scr.annotations.sling.SlingFilterScope; 15 | import org.apache.sling.api.SlingHttpServletRequest; 16 | import org.apache.sling.api.SlingHttpServletResponse; 17 | import org.slf4j.Logger; 18 | import org.slf4j.LoggerFactory; 19 | 20 | @SlingFilter(scope = SlingFilterScope.REQUEST, order = 0) 21 | public class CacheControlFilter implements Filter { 22 | 23 | private static final String HEADER_CACHE_CONTROL = "Cache-Control"; 24 | 25 | private static final Logger LOG = LoggerFactory.getLogger(CacheControlFilter.class); 26 | 27 | @Reference 28 | private ConfigurationWhiteboard configurationWhiteboard; 29 | 30 | @Override 31 | public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 32 | throws IOException, ServletException { 33 | final SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) request; 34 | final String resourceType = slingRequest.getResource().getResourceType(); 35 | final Configuration config = configurationWhiteboard.getConfiguration(slingRequest, resourceType); 36 | 37 | if (config != null && config.hasTtlSet()) { 38 | SlingHttpServletResponse slingResponse = (SlingHttpServletResponse) response; 39 | slingResponse.setHeader(HEADER_CACHE_CONTROL, "max-age=" + config.getTtl()); 40 | LOG.debug("set \"{}: max-age={}\" to {}", HEADER_CACHE_CONTROL, config.getTtl(), resourceType); 41 | } 42 | 43 | chain.doFilter(request, response); 44 | } 45 | 46 | @Override 47 | public void init(FilterConfig filterConfig) throws ServletException { 48 | } 49 | 50 | @Override 51 | public void destroy() { 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/cognifide/cq/includefilter/Configuration.java: -------------------------------------------------------------------------------- 1 | package com.cognifide.cq.includefilter; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import org.apache.commons.lang.ArrayUtils; 8 | import org.apache.commons.lang.StringUtils; 9 | import org.apache.felix.scr.annotations.Activate; 10 | import org.apache.felix.scr.annotations.Component; 11 | import org.apache.felix.scr.annotations.ConfigurationPolicy; 12 | import org.apache.felix.scr.annotations.Properties; 13 | import org.apache.felix.scr.annotations.Property; 14 | import org.apache.felix.scr.annotations.PropertyOption; 15 | import org.apache.felix.scr.annotations.Service; 16 | import org.apache.sling.api.SlingHttpServletRequest; 17 | import org.apache.sling.commons.osgi.PropertiesUtil; 18 | import org.osgi.framework.Constants; 19 | import org.osgi.service.component.ComponentContext; 20 | 21 | /** 22 | * Include filter configuration. 23 | * 24 | * @author tomasz.rekawek 25 | * 26 | */ 27 | @Component(metatype = true, configurationFactory = true, 28 | label = "Cognifide : SDI : Configuration", immediate = true, policy = ConfigurationPolicy.REQUIRE) 29 | @Service(Configuration.class) 30 | @Properties({ 31 | @Property(name = Constants.SERVICE_VENDOR, value = "Cognifide"), 32 | @Property(name = Configuration.PROPERTY_FILTER_ENABLED, boolValue = Configuration.DEFAULT_FILTER_ENABLED, label = "Enabled", description = "Check to enable the filter"), 33 | @Property(name = Configuration.PROPERTY_FILTER_PATH, value = Configuration.DEFAULT_FILTER_PATH, label = "Base path", description = "This SDI configuration will work only for this path"), 34 | @Property(name = Configuration.PROPERTY_FILTER_RESOURCE_TYPES, cardinality = Integer.MAX_VALUE, label = "Resource types", description = "Filter will replace components with selected resource types"), 35 | @Property(name = Configuration.PROPERTY_INCLUDE_TYPE, value = Configuration.DEFAULT_INCLUDE_TYPE, label = "Include type", description = "Type of generated include tags", options = { 36 | @PropertyOption(name = "SSI", value = "Apache SSI"), 37 | @PropertyOption(name = "ESI", value = "ESI"), 38 | @PropertyOption(name = "JSI", value = "Javascript") }), 39 | @Property(name = Configuration.PROPERTY_ADD_COMMENT, boolValue = Configuration.DEFAULT_ADD_COMMENT, label = "Add comment", description = "Add comment to included components"), 40 | @Property(name = Configuration.PROPERTY_FILTER_SELECTOR, value = Configuration.DEFAULT_FILTER_SELECTOR, label = "Filter selector", description = "Selector used to mark included resources"), 41 | @Property(name = Configuration.PROPERTY_COMPONENT_TTL, label = "Component TTL", description = "\"Time to live\" cache header for rendered component (in seconds)"), 42 | @Property(name = Configuration.PROPERTY_REQUIRED_HEADER, value = Configuration.DEFAULT_REQUIRED_HEADER, label = "Required header", description = "SDI will work only for requests with given header"), 43 | @Property(name = Configuration.PROPERTY_IGNORE_URL_PARAMS, cardinality = Integer.MAX_VALUE, label = "Ignore URL params", description = "SDI will process the request even if it contains configured GET parameters"), 44 | @Property(name = Configuration.PROPERTY_REWRITE_PATH, boolValue = Configuration.DEFAULT_REWRITE_DISABLED, label = "Include path rewriting", description = "Check to enable include path rewriting") 45 | }) 46 | public class Configuration { 47 | 48 | static final String PROPERTY_FILTER_PATH = "include-filter.config.path"; 49 | 50 | static final String DEFAULT_FILTER_PATH = "/content"; 51 | 52 | static final String PROPERTY_FILTER_ENABLED = "include-filter.config.enabled"; 53 | 54 | static final boolean DEFAULT_FILTER_ENABLED = false; 55 | 56 | static final String PROPERTY_FILTER_RESOURCE_TYPES = "include-filter.config.resource-types"; 57 | 58 | static final String PROPERTY_FILTER_SELECTOR = "include-filter.config.selector"; 59 | 60 | static final String DEFAULT_FILTER_SELECTOR = "nocache"; 61 | 62 | static final String PROPERTY_COMPONENT_TTL = "include-filter.config.ttl"; 63 | 64 | static final String PROPERTY_INCLUDE_TYPE = "include-filter.config.include-type"; 65 | 66 | static final String DEFAULT_INCLUDE_TYPE = "SSI"; 67 | 68 | static final String PROPERTY_ADD_COMMENT = "include-filter.config.add_comment"; 69 | 70 | static final boolean DEFAULT_ADD_COMMENT = false; 71 | 72 | static final String PROPERTY_REQUIRED_HEADER = "include-filter.config.required_header"; 73 | 74 | static final String DEFAULT_REQUIRED_HEADER = "Server-Agent=Communique-Dispatcher"; 75 | 76 | static final String PROPERTY_IGNORE_URL_PARAMS = "include-filter.config.ignoreUrlParams"; 77 | 78 | static final String PROPERTY_REWRITE_PATH = "include-filter.config.rewrite"; 79 | 80 | static final boolean DEFAULT_REWRITE_DISABLED = false; 81 | 82 | private boolean isEnabled; 83 | 84 | private String path; 85 | 86 | private String includeSelector; 87 | 88 | private int ttl; 89 | 90 | private List resourceTypes; 91 | 92 | private boolean addComment; 93 | 94 | private String includeTypeName; 95 | 96 | private String requiredHeader; 97 | 98 | private List ignoreUrlParams; 99 | 100 | private boolean rewritePath; 101 | 102 | @Activate 103 | public void activate(ComponentContext context, Map properties) { 104 | isEnabled = PropertiesUtil.toBoolean(properties.get(PROPERTY_FILTER_ENABLED), DEFAULT_FILTER_ENABLED); 105 | path = PropertiesUtil.toString(properties.get(PROPERTY_FILTER_PATH), DEFAULT_FILTER_PATH); 106 | String[] resourceTypeList; 107 | resourceTypeList = PropertiesUtil.toStringArray(properties.get(PROPERTY_FILTER_RESOURCE_TYPES), new String[0]); 108 | for (int i = 0; i < resourceTypeList.length; i++) { 109 | String[] s = resourceTypeList[i].split(";"); 110 | String name = s[0].trim(); 111 | resourceTypeList[i] = name; 112 | } 113 | this.resourceTypes = Arrays.asList(resourceTypeList); 114 | 115 | includeSelector = PropertiesUtil.toString(properties.get(PROPERTY_FILTER_SELECTOR), 116 | DEFAULT_FILTER_SELECTOR); 117 | ttl = PropertiesUtil.toInteger(properties.get(PROPERTY_COMPONENT_TTL), -1); 118 | addComment = PropertiesUtil.toBoolean(properties.get(PROPERTY_ADD_COMMENT), DEFAULT_ADD_COMMENT); 119 | includeTypeName = PropertiesUtil 120 | .toString(properties.get(PROPERTY_INCLUDE_TYPE), DEFAULT_INCLUDE_TYPE); 121 | requiredHeader = PropertiesUtil.toString(properties.get(PROPERTY_REQUIRED_HEADER), 122 | DEFAULT_REQUIRED_HEADER); 123 | ignoreUrlParams = Arrays.asList(PropertiesUtil.toStringArray(properties.get(PROPERTY_IGNORE_URL_PARAMS), new String[0])); 124 | rewritePath = PropertiesUtil.toBoolean(properties.get(PROPERTY_REWRITE_PATH), DEFAULT_REWRITE_DISABLED); 125 | } 126 | 127 | public String getBasePath() { 128 | return path; 129 | } 130 | 131 | public boolean hasIncludeSelector(SlingHttpServletRequest request) { 132 | return ArrayUtils.contains(request.getRequestPathInfo().getSelectors(), includeSelector); 133 | } 134 | 135 | public String getIncludeSelector() { 136 | return includeSelector; 137 | } 138 | 139 | public boolean hasTtlSet() { 140 | return ttl >= 0; 141 | } 142 | 143 | public int getTtl() { 144 | return ttl; 145 | } 146 | 147 | public boolean isSupportedResourceType(String resourceType) { 148 | return StringUtils.isNotBlank(resourceType) && resourceTypes.contains(resourceType); 149 | } 150 | 151 | public boolean getAddComment() { 152 | return addComment; 153 | } 154 | 155 | public String getIncludeTypeName() { 156 | return includeTypeName; 157 | } 158 | 159 | public boolean isEnabled() { 160 | return isEnabled; 161 | } 162 | 163 | public String getRequiredHeader() { 164 | return requiredHeader; 165 | } 166 | 167 | public List getIgnoreUrlParams() { 168 | return ignoreUrlParams; 169 | } 170 | 171 | public boolean isRewritePath() { 172 | return rewritePath; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/main/java/com/cognifide/cq/includefilter/ConfigurationWhiteboard.java: -------------------------------------------------------------------------------- 1 | package com.cognifide.cq.includefilter; 2 | 3 | import java.util.Set; 4 | import java.util.concurrent.CopyOnWriteArraySet; 5 | 6 | import org.apache.commons.lang.StringUtils; 7 | import org.apache.felix.scr.annotations.Component; 8 | import org.apache.felix.scr.annotations.Reference; 9 | import org.apache.felix.scr.annotations.ReferenceCardinality; 10 | import org.apache.felix.scr.annotations.ReferencePolicy; 11 | import org.apache.felix.scr.annotations.Service; 12 | import org.apache.sling.api.SlingHttpServletRequest; 13 | 14 | @Component 15 | @Service(ConfigurationWhiteboard.class) 16 | public class ConfigurationWhiteboard { 17 | 18 | @Reference(referenceInterface = Configuration.class, cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE, policy = ReferencePolicy.DYNAMIC) 19 | private Set configs = new CopyOnWriteArraySet(); 20 | 21 | public Configuration getConfiguration(SlingHttpServletRequest request, String resourceType) { 22 | for (Configuration c : configs) { 23 | if (isEnabled(c, request) && c.isSupportedResourceType(resourceType)) { 24 | return c; 25 | } 26 | } 27 | return null; 28 | } 29 | 30 | private boolean isEnabled(Configuration config, SlingHttpServletRequest request) { 31 | final String requestPath = request.getRequestPathInfo().getResourcePath(); 32 | return config.isEnabled() && StringUtils.startsWith(requestPath, config.getBasePath()); 33 | } 34 | 35 | protected void bindConfigs(final Configuration config) { 36 | configs.add(config); 37 | } 38 | 39 | protected void unbindConfigs(final Configuration config) { 40 | configs.remove(config); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/cognifide/cq/includefilter/IncludeTagWritingFilter.java: -------------------------------------------------------------------------------- 1 | package com.cognifide.cq.includefilter; 2 | 3 | import java.io.IOException; 4 | import java.io.PrintWriter; 5 | import java.net.URI; 6 | import java.net.URISyntaxException; 7 | import java.util.Enumeration; 8 | import java.util.List; 9 | 10 | import javax.servlet.Filter; 11 | import javax.servlet.FilterChain; 12 | import javax.servlet.FilterConfig; 13 | import javax.servlet.ServletException; 14 | import javax.servlet.ServletRequest; 15 | import javax.servlet.ServletResponse; 16 | 17 | import org.apache.commons.lang.StringEscapeUtils; 18 | import org.apache.commons.lang.StringUtils; 19 | import org.apache.felix.scr.annotations.Reference; 20 | import org.apache.felix.scr.annotations.sling.SlingFilter; 21 | import org.apache.felix.scr.annotations.sling.SlingFilterScope; 22 | import org.apache.sling.api.SlingHttpServletRequest; 23 | import org.apache.sling.api.request.RequestPathInfo; 24 | import org.apache.sling.api.resource.Resource; 25 | import org.apache.sling.api.resource.ResourceUtil; 26 | import org.slf4j.Logger; 27 | import org.slf4j.LoggerFactory; 28 | 29 | import com.cognifide.cq.includefilter.generator.IncludeGenerator; 30 | import com.cognifide.cq.includefilter.generator.IncludeGeneratorWhiteboard; 31 | 32 | @SlingFilter(scope = SlingFilterScope.INCLUDE, order = -500) 33 | public class IncludeTagWritingFilter implements Filter { 34 | 35 | private static final Logger LOG = LoggerFactory.getLogger(IncludeTagWritingFilter.class); 36 | 37 | private static final String COMMENT = "\n"; 38 | 39 | @Reference 40 | private ConfigurationWhiteboard configurationWhiteboard; 41 | 42 | @Reference 43 | private IncludeGeneratorWhiteboard generatorWhiteboard; 44 | 45 | @Override 46 | public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 47 | throws IOException, ServletException { 48 | final SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) request; 49 | final String resourceType = slingRequest.getResource().getResourceType(); 50 | 51 | final Configuration config = configurationWhiteboard.getConfiguration(slingRequest, resourceType); 52 | if (config == null) { 53 | chain.doFilter(request, response); 54 | return; 55 | } 56 | 57 | final IncludeGenerator generator = generatorWhiteboard.getGenerator(config.getIncludeTypeName()); 58 | if (generator == null) { 59 | LOG.error("Invalid generator: " + config.getIncludeTypeName()); 60 | chain.doFilter(request, response); 61 | return; 62 | } 63 | 64 | final PrintWriter writer = response.getWriter(); 65 | final String url = getUrl(config, slingRequest); 66 | if (url == null) { 67 | chain.doFilter(request, response); 68 | return; 69 | } 70 | 71 | if (config.getAddComment()) { 72 | writer.append(String.format(COMMENT, StringEscapeUtils.escapeHtml(url), resourceType)); 73 | } 74 | 75 | // Only write the includes markup if the required, configurable request header is present 76 | if (shouldWriteIncludes(config, slingRequest)) { 77 | String include = generator.getInclude(url); 78 | LOG.debug(include); 79 | writer.append(include); 80 | } else { 81 | chain.doFilter(request, response); 82 | } 83 | } 84 | 85 | private boolean shouldWriteIncludes(Configuration config, SlingHttpServletRequest request) { 86 | if (requestHasParameters(config.getIgnoreUrlParams(), request)) { 87 | return false; 88 | } 89 | final String requiredHeader = config.getRequiredHeader(); 90 | return StringUtils.isBlank(requiredHeader) || containsHeader(requiredHeader, request); 91 | } 92 | 93 | private boolean requestHasParameters(List ignoreUrlParams, SlingHttpServletRequest request) { 94 | final Enumeration paramNames = request.getParameterNames(); 95 | while (paramNames.hasMoreElements()) { 96 | final String paramName = (String) paramNames.nextElement(); 97 | if (!ignoreUrlParams.contains(paramName)) { 98 | return true; 99 | } 100 | } 101 | return false; 102 | } 103 | 104 | private boolean containsHeader(String requiredHeader, SlingHttpServletRequest request) { 105 | final String name, expectedValue; 106 | if (StringUtils.contains(requiredHeader, '=')) { 107 | final String split[] = StringUtils.split(requiredHeader, '='); 108 | name = split[0]; 109 | expectedValue = split[1]; 110 | } else { 111 | name = requiredHeader; 112 | expectedValue = null; 113 | } 114 | 115 | final String actualValue = request.getHeader(name); 116 | if (actualValue == null) { 117 | return false; 118 | } else if (expectedValue == null) { 119 | return true; 120 | } else { 121 | return actualValue.equalsIgnoreCase(expectedValue); 122 | } 123 | } 124 | 125 | private String getUrl(Configuration config, SlingHttpServletRequest request) { 126 | String url = buildUrl(config, request); 127 | if (config.isRewritePath()) { 128 | url = removeQuestionMark(url); 129 | url = request.getResourceResolver().map(request, url); 130 | } 131 | else { 132 | url = encodeJcrContentPart(url); 133 | try { 134 | url = new URI(null, null, url, null).toASCIIString(); 135 | } catch (URISyntaxException e) { 136 | LOG.error("Include url is in the wrong format", e); 137 | return null; 138 | } 139 | } 140 | 141 | return url; 142 | } 143 | 144 | private String buildUrl(Configuration config, SlingHttpServletRequest request) { 145 | final boolean synthetic = ResourceUtil.isSyntheticResource(request.getResource()); 146 | final Resource resource = request.getResource(); 147 | final StringBuilder builder = new StringBuilder(); 148 | final RequestPathInfo pathInfo = request.getRequestPathInfo(); 149 | 150 | final String resourcePath = pathInfo.getResourcePath(); 151 | builder.append(resourcePath); 152 | if (pathInfo.getSelectorString() != null) { 153 | builder.append('.').append(sanitize(pathInfo.getSelectorString())); 154 | } 155 | builder.append('.').append(config.getIncludeSelector()); 156 | builder.append('.').append(pathInfo.getExtension()); 157 | if (synthetic) { 158 | builder.append('/').append(resource.getResourceType()); 159 | } else { 160 | builder.append(sanitize(pathInfo.getSuffix())); 161 | } 162 | return builder.toString(); 163 | } 164 | 165 | private static String sanitize(String path) { 166 | return StringUtils.defaultString(path); 167 | } 168 | 169 | private static String encodeJcrContentPart(String url) { 170 | return url.replace("jcr:content", "_jcr_content"); 171 | } 172 | 173 | private static String removeQuestionMark(String url) { 174 | return url.replaceAll("[?]", ""); 175 | } 176 | 177 | @Override 178 | public void init(FilterConfig filterConfig) throws ServletException { 179 | } 180 | 181 | @Override 182 | public void destroy() { 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/main/java/com/cognifide/cq/includefilter/MutableUrl.java: -------------------------------------------------------------------------------- 1 | package com.cognifide.cq.includefilter; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.apache.commons.lang.StringUtils; 7 | import org.apache.sling.api.SlingHttpServletRequest; 8 | import org.apache.sling.api.request.RequestPathInfo; 9 | 10 | /** 11 | * Class providing useful methods for URL manipulation (add/remove selector, set default extension, change 12 | * suffix, etc.) 13 | * 14 | * @author tomasz.rekawek 15 | * 16 | */ 17 | public class MutableUrl { 18 | private final RequestPathInfo originalPathInfo; 19 | 20 | private List selectorsToRemove; 21 | 22 | private List selectorsToAdd; 23 | 24 | private String replaceSuffix; 25 | 26 | private String replaceExt; 27 | 28 | private String replacePath; 29 | 30 | private String defaultExt; 31 | 32 | private boolean escapeNamespace; 33 | 34 | public MutableUrl(SlingHttpServletRequest request, boolean escapeNamespace) { 35 | originalPathInfo = request.getRequestPathInfo(); 36 | selectorsToAdd = new ArrayList(); 37 | selectorsToRemove = new ArrayList(); 38 | this.escapeNamespace = escapeNamespace; 39 | } 40 | 41 | public void addSelector(String selector) { 42 | selectorsToAdd.add(selector); 43 | selectorsToRemove.remove(selector); 44 | } 45 | 46 | public void removeSelector(String selector) { 47 | selectorsToRemove.add(selector); 48 | selectorsToAdd.remove(selector); 49 | } 50 | 51 | public void replacePath(String path) { 52 | replacePath = path; 53 | } 54 | 55 | public void replaceSuffix(String suffix) { 56 | replaceSuffix = suffix; 57 | } 58 | 59 | public void setDefaultExtension(String extension) { 60 | defaultExt = extension; 61 | } 62 | 63 | public void replaceExtension(String extension) { 64 | replaceExt = extension; 65 | } 66 | 67 | public String getPath() { 68 | String resPath; 69 | 70 | if (replacePath != null) { 71 | resPath = replacePath; 72 | } else { 73 | resPath = originalPathInfo.getResourcePath(); 74 | } 75 | 76 | // According to 77 | // http://sling.apache.org/apidocs/sling5/org/apache/sling/api/request/RequestPathInfo.html 78 | // it shouldn't contain dot or slash. Unfortunately, sometimes contains - especially if the resource 79 | // doesn't exist. 80 | if (resPath.contains(".")) { 81 | resPath = resPath.substring(0, resPath.indexOf('.')); 82 | } 83 | return resPath; 84 | } 85 | 86 | @Override 87 | public String toString() { 88 | StringBuffer buf = new StringBuffer(); 89 | buf.append(getPath()); 90 | buildSelectors(buf); 91 | 92 | if (replaceExt != null) { 93 | if (!replaceExt.isEmpty()) { 94 | buf.append('.'); 95 | buf.append(replaceExt); 96 | } 97 | } else if (originalPathInfo.getExtension() == null && defaultExt != null) { 98 | buf.append('.'); 99 | buf.append(defaultExt); 100 | } else if (originalPathInfo.getExtension() != null) { 101 | buf.append('.'); 102 | buf.append(originalPathInfo.getExtension()); 103 | } 104 | 105 | if (replaceSuffix != null) { 106 | if (!replaceSuffix.isEmpty()) { 107 | buf.append('/'); 108 | buf.append(sanitize(replaceSuffix)); 109 | } 110 | } else if (originalPathInfo.getSuffix() != null) { 111 | buf.append(sanitize(originalPathInfo.getSuffix())); 112 | } 113 | 114 | String url = buf.toString(); 115 | if(escapeNamespace) { 116 | url = url.replaceAll("(\\w+):(\\w+)", "_$1_$2"); 117 | } 118 | return url; 119 | } 120 | 121 | private void buildSelectors(StringBuffer buf) { 122 | String[] selectors = originalPathInfo.getSelectors(); 123 | for (String sel : selectors) { 124 | if (!selectorsToRemove.contains(sel) && !selectorsToAdd.contains(sel)) { 125 | buf.append('.'); 126 | buf.append(sanitize(sel)); 127 | } 128 | } 129 | for (String sel : selectorsToAdd) { 130 | buf.append('.'); 131 | buf.append(sanitize(sel)); 132 | } 133 | } 134 | 135 | private static String sanitize(String dirtyString) { 136 | if (StringUtils.isBlank(dirtyString)) { 137 | return ""; 138 | } else { 139 | return dirtyString.replaceAll("[^0-9a-zA-Z:.\\-/_=]", ""); 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/main/java/com/cognifide/cq/includefilter/SyntheticResourceIncludingFilter.java: -------------------------------------------------------------------------------- 1 | package com.cognifide.cq.includefilter; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.Filter; 6 | import javax.servlet.FilterChain; 7 | import javax.servlet.FilterConfig; 8 | import javax.servlet.RequestDispatcher; 9 | import javax.servlet.ServletException; 10 | import javax.servlet.ServletRequest; 11 | import javax.servlet.ServletResponse; 12 | 13 | import org.apache.commons.lang.StringUtils; 14 | import org.apache.felix.scr.annotations.Component; 15 | import org.apache.felix.scr.annotations.Properties; 16 | import org.apache.felix.scr.annotations.Property; 17 | import org.apache.felix.scr.annotations.Reference; 18 | import org.apache.felix.scr.annotations.Service; 19 | import org.apache.sling.api.SlingHttpServletRequest; 20 | import org.apache.sling.api.request.RequestDispatcherOptions; 21 | import org.apache.sling.api.resource.ResourceUtil; 22 | import org.apache.sling.engine.EngineConstants; 23 | import org.osgi.framework.Constants; 24 | 25 | @Component(metatype = true, label="Cognifide : SDI : Synthetic Resource Include Filter") 26 | @Service 27 | @Properties({ 28 | @Property(name = Constants.SERVICE_VENDOR, value = "Cognifide"), 29 | @Property(name = EngineConstants.SLING_FILTER_SCOPE, value = EngineConstants.FILTER_SCOPE_REQUEST, propertyPrivate = true), 30 | @Property(name = Constants.SERVICE_RANKING, intValue = Integer.MIN_VALUE, description = "< -2500 after 6.0 SP2, > -2500 before SP2 ", propertyPrivate = false), 31 | }) 32 | public class SyntheticResourceIncludingFilter implements Filter { 33 | 34 | @Reference 35 | private ConfigurationWhiteboard configurationWhiteboard; 36 | 37 | @Override 38 | public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 39 | throws IOException, ServletException { 40 | final SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) request; 41 | final String resourceType = getResourceTypeFromSuffix(slingRequest); 42 | final Configuration config = configurationWhiteboard.getConfiguration(slingRequest, resourceType); 43 | 44 | if (config == null 45 | || !config.hasIncludeSelector(slingRequest) 46 | || !ResourceUtil.isSyntheticResource(slingRequest.getResource())) { 47 | chain.doFilter(request, response); 48 | return; 49 | } 50 | 51 | final RequestDispatcherOptions options = new RequestDispatcherOptions(); 52 | options.setForceResourceType(resourceType); 53 | final RequestDispatcher dispatcher = slingRequest.getRequestDispatcher(slingRequest.getResource(), 54 | options); 55 | dispatcher.forward(request, response); 56 | } 57 | 58 | private static String getResourceTypeFromSuffix(SlingHttpServletRequest request) { 59 | final String suffix = request.getRequestPathInfo().getSuffix(); 60 | return StringUtils.removeStart(suffix, "/"); 61 | } 62 | 63 | @Override 64 | public void init(FilterConfig filterConfig) throws ServletException { 65 | } 66 | 67 | @Override 68 | public void destroy() { 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/cognifide/cq/includefilter/SyntheticResourceIncludingServlet.java: -------------------------------------------------------------------------------- 1 | package com.cognifide.cq.includefilter; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.RequestDispatcher; 6 | import javax.servlet.ServletException; 7 | 8 | import org.apache.commons.lang.StringUtils; 9 | import org.apache.felix.scr.annotations.Reference; 10 | import org.apache.felix.scr.annotations.sling.SlingServlet; 11 | import org.apache.sling.api.SlingHttpServletRequest; 12 | import org.apache.sling.api.SlingHttpServletResponse; 13 | import org.apache.sling.api.request.RequestDispatcherOptions; 14 | import org.apache.sling.api.servlets.OptingServlet; 15 | import org.apache.sling.api.servlets.SlingSafeMethodsServlet; 16 | 17 | /** 18 | * Experimental implementation for synthetic resource include 19 | * 20 | * @author miroslaw.stawniak 21 | * 22 | */ 23 | //@SlingServlet(resourceTypes = "sling:nonexisting", methods="GET") 24 | public class SyntheticResourceIncludingServlet extends SlingSafeMethodsServlet implements OptingServlet { 25 | 26 | private static final long serialVersionUID = 1L; 27 | 28 | @Reference 29 | private ConfigurationWhiteboard configurationWhiteboard; 30 | 31 | @Override 32 | protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) 33 | throws ServletException, IOException { 34 | 35 | final String resourceType = getResourceTypeFromSuffix(request); 36 | 37 | final RequestDispatcherOptions options = new RequestDispatcherOptions(); 38 | options.setForceResourceType(resourceType); 39 | final RequestDispatcher dispatcher = request.getRequestDispatcher(request.getResource(), 40 | options); 41 | dispatcher.forward(request, response); 42 | } 43 | 44 | @Override 45 | public boolean accepts(SlingHttpServletRequest request) { 46 | final String resourceType = getResourceTypeFromSuffix(request); 47 | final Configuration config = configurationWhiteboard.getConfiguration(request, resourceType); 48 | 49 | return config != null 50 | && config.hasIncludeSelector(request); 51 | } 52 | 53 | private static String getResourceTypeFromSuffix(SlingHttpServletRequest request) { 54 | final String suffix = request.getRequestPathInfo().getSuffix(); 55 | return StringUtils.removeStart(suffix, "/"); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/cognifide/cq/includefilter/generator/IncludeGenerator.java: -------------------------------------------------------------------------------- 1 | package com.cognifide.cq.includefilter.generator; 2 | 3 | /** 4 | * Include generator interface 5 | * 6 | * @author tomasz.rekawek 7 | * 8 | */ 9 | public interface IncludeGenerator { 10 | String getType(); 11 | 12 | String getInclude(String url); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/cognifide/cq/includefilter/generator/IncludeGeneratorWhiteboard.java: -------------------------------------------------------------------------------- 1 | package com.cognifide.cq.includefilter.generator; 2 | 3 | import java.util.Set; 4 | import java.util.concurrent.CopyOnWriteArraySet; 5 | 6 | import org.apache.felix.scr.annotations.Component; 7 | import org.apache.felix.scr.annotations.Reference; 8 | import org.apache.felix.scr.annotations.ReferenceCardinality; 9 | import org.apache.felix.scr.annotations.ReferencePolicy; 10 | import org.apache.felix.scr.annotations.Service; 11 | 12 | /** 13 | * Service that provides include generator of given type. 14 | * 15 | * @author tomasz.rekawek 16 | */ 17 | 18 | @Component 19 | @Service(IncludeGeneratorWhiteboard.class) 20 | public class IncludeGeneratorWhiteboard { 21 | 22 | @Reference(referenceInterface = IncludeGenerator.class, cardinality = ReferenceCardinality.MANDATORY_MULTIPLE, policy = ReferencePolicy.DYNAMIC) 23 | private Set generators = new CopyOnWriteArraySet(); 24 | 25 | public IncludeGenerator getGenerator(String type) { 26 | for (IncludeGenerator generator : generators) { 27 | if (type.equals(generator.getType())) { 28 | return generator; 29 | } 30 | } 31 | return null; 32 | } 33 | 34 | void bindGenerators(IncludeGenerator generator) { 35 | generators.add(generator); 36 | } 37 | 38 | void unbindGenerators(IncludeGenerator generator) { 39 | generators.remove(generator); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/cognifide/cq/includefilter/generator/types/EsiGenerator.java: -------------------------------------------------------------------------------- 1 | package com.cognifide.cq.includefilter.generator.types; 2 | 3 | import org.apache.commons.lang.StringEscapeUtils; 4 | import org.apache.felix.scr.annotations.Component; 5 | import org.apache.felix.scr.annotations.Service; 6 | 7 | import com.cognifide.cq.includefilter.generator.IncludeGenerator; 8 | 9 | /** 10 | * ESI include generator 11 | * 12 | * @author tomasz.rekawek 13 | * 14 | */ 15 | @Component 16 | @Service 17 | public class EsiGenerator implements IncludeGenerator { 18 | private static final String GENERATOR_NAME = "ESI"; 19 | 20 | @Override 21 | public String getType() { 22 | return GENERATOR_NAME; 23 | } 24 | 25 | @Override 26 | public String getInclude(String url) { 27 | StringBuffer buf = new StringBuffer(); 28 | buf.append(""); 31 | return buf.toString(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/cognifide/cq/includefilter/generator/types/JsiGenerator.java: -------------------------------------------------------------------------------- 1 | package com.cognifide.cq.includefilter.generator.types; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.InputStreamReader; 7 | import java.io.UnsupportedEncodingException; 8 | import java.net.URL; 9 | 10 | import org.apache.commons.lang.StringEscapeUtils; 11 | import org.apache.felix.scr.annotations.Activate; 12 | import org.apache.felix.scr.annotations.Component; 13 | import org.apache.felix.scr.annotations.Service; 14 | import org.osgi.service.component.ComponentContext; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | import com.cognifide.cq.includefilter.generator.IncludeGenerator; 19 | 20 | /** 21 | * Client side include generator - using Ajax/JQuery. 22 | * 23 | * @author tomasz.rekawek 24 | */ 25 | @Component 26 | @Service 27 | public class JsiGenerator implements IncludeGenerator { 28 | private static final String TEMPLATE_FILENAME = "generators/jquery.html"; 29 | 30 | private static final String UUID_FIELD = "${uniqueId}"; 31 | 32 | private static final String URL_FIELD = "${url}"; 33 | 34 | private static final Logger LOG = LoggerFactory.getLogger(JsiGenerator.class); 35 | 36 | private static final String GENERATOR_NAME = "JSI"; 37 | 38 | private volatile int divId = 1000; 39 | 40 | private String template; 41 | 42 | @Activate 43 | public void activate(ComponentContext ctx) { 44 | URL url = ctx.getBundleContext().getBundle().getResource(TEMPLATE_FILENAME); 45 | if (url == null) { 46 | LOG.error("File " + TEMPLATE_FILENAME + " not found in bundle."); 47 | return; 48 | } 49 | readTemplateFromUrl(url); 50 | } 51 | 52 | @Override 53 | public String getType() { 54 | return GENERATOR_NAME; 55 | } 56 | 57 | @Override 58 | public String getInclude(String url) { 59 | if (template == null) { 60 | throw new IllegalStateException("JSI generator hasn't be initialized"); 61 | } 62 | 63 | String divName; 64 | synchronized (this) { 65 | divName = "dynamic_include_filter_div_" + divId++; 66 | } 67 | 68 | return template 69 | .replace(UUID_FIELD, divName) 70 | .replace(URL_FIELD, StringEscapeUtils.escapeJavaScript(url)); 71 | } 72 | 73 | 74 | private void readTemplateFromUrl(URL url) { 75 | BufferedReader br = null; 76 | try { 77 | InputStream in = url.openStream(); 78 | br = new BufferedReader(new InputStreamReader(in, "UTF-8")); 79 | StringBuilder builder = new StringBuilder(); 80 | String line; 81 | while ((line = br.readLine()) != null) { 82 | builder.append(line).append('\n'); 83 | } 84 | template = builder.toString(); 85 | } catch (UnsupportedEncodingException e) { 86 | LOG.error("Error while reading template", e); 87 | } catch (IOException e) { 88 | LOG.error("Error while reading template", e); 89 | } finally { 90 | try { 91 | if (br != null) { 92 | br.close(); 93 | } 94 | } catch (Exception e) { 95 | LOG.error("Error while closing reader", e); 96 | } 97 | } 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/com/cognifide/cq/includefilter/generator/types/SsiGenerator.java: -------------------------------------------------------------------------------- 1 | package com.cognifide.cq.includefilter.generator.types; 2 | 3 | import org.apache.felix.scr.annotations.Component; 4 | import org.apache.felix.scr.annotations.Service; 5 | 6 | import com.cognifide.cq.includefilter.generator.IncludeGenerator; 7 | 8 | /** 9 | * Apache SSI include generator 10 | * 11 | * @author tomasz.rekawek 12 | * 13 | */ 14 | @Component 15 | @Service 16 | public class SsiGenerator implements IncludeGenerator { 17 | private static final String GENERATOR_NAME = "SSI"; 18 | 19 | @Override 20 | public String getType() { 21 | return GENERATOR_NAME; 22 | } 23 | 24 | @Override 25 | public String getInclude(String url) { 26 | StringBuffer buf = new StringBuffer(); 27 | buf.append(""); 30 | return buf.toString(); 31 | } 32 | 33 | /** 34 | * Escapes $ to \$ 35 | * 36 | * @param url url to escape 37 | * @return escaped url 38 | */ 39 | private Object escapeForApache(String url) { 40 | return url.replace("$", "\\$"); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/resources/generators/jquery.html: -------------------------------------------------------------------------------- 1 |
2 | 8 | --------------------------------------------------------------------------------