001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.resource;
018
019import java.time.Duration;
020import java.time.Instant;
021import java.util.Locale;
022import java.util.Map;
023import org.apache.wicket.Application;
024import org.apache.wicket.model.IModel;
025import org.apache.wicket.request.resource.IResource;
026import org.apache.wicket.request.resource.ResourceReference;
027import org.apache.wicket.request.resource.ResourceReferenceRegistry;
028import org.apache.wicket.request.resource.ResourceStreamResource;
029import org.apache.wicket.util.io.IClusterable;
030import org.apache.wicket.util.resource.IResourceStream;
031import org.apache.wicket.util.resource.StringResourceStream;
032import org.apache.wicket.util.template.PackageTextTemplate;
033import org.apache.wicket.util.template.TextTemplate;
034
035/**
036 * A class which adapts a {@link PackageTextTemplate} to a {@link ResourceReference}.
037 * 
038 * See <a href="https://cwiki.apache.org/confluence/display/WICKET/Dynamically+Generate+a+CSS+Stylesheet">Dynamically generate a CSS stylesheet</a>
039 * 
040 * @author James Carman
041 */
042public class TextTemplateResourceReference extends ResourceReference implements IClusterable
043{
044
045        private static final long serialVersionUID = 1L;
046
047        private final TextTemplate textTemplate;
048        private final IModel<Map<String, Object>> variablesModel;
049        private final ResourceStreamResource resource;
050
051        /**
052         * Creates a resource reference to a {@link PackageTextTemplate}.
053         * 
054         * @param scope
055         *            the <code>Class</code> to be used for retrieving the classloader for loading the
056         *            <code>PackagedTextTemplate</code>
057         * @param fileName
058         *            the file name
059         * @param variablesModel
060         *            the template variables as a model
061         */
062        public TextTemplateResourceReference(final Class<?> scope, final String fileName,
063                IModel<Map<String, Object>> variablesModel)
064        {
065                this(scope, fileName, PackageTextTemplate.DEFAULT_CONTENT_TYPE,
066                        PackageTextTemplate.DEFAULT_ENCODING, variablesModel);
067        }
068
069        /**
070         * Creates a resource reference to a {@link PackageTextTemplate}.
071         * 
072         * @param scope
073         *            the <code>Class</code> to be used for retrieving the classloader for loading the
074         *            <code>PackagedTextTemplate</code>
075         * @param fileName
076         *            the file name
077         * @param contentType
078         *            the mime type of this resource, such as "<code>image/jpeg</code>" or "
079         *            <code>text/html</code>"
080         * @param variablesModel
081         *            the template variables as a model
082         */
083        public TextTemplateResourceReference(final Class<?> scope, final String fileName,
084                final String contentType, IModel<Map<String, Object>> variablesModel)
085        {
086                this(scope, fileName, contentType, PackageTextTemplate.DEFAULT_ENCODING, variablesModel);
087        }
088
089        /**
090         * Creates a resource reference to a {@link PackageTextTemplate}.
091         * 
092         * @param scope
093         *            the <code>Class</code> to be used for retrieving the classloader for loading the
094         *            <code>PackagedTextTemplate</code>
095         * @param fileName
096         *            the file name
097         * @param contentType
098         *            the mime type of this resource, such as "<code>image/jpeg</code>" or "
099         *            <code>text/html</code>"
100         * @param encoding
101         *            the file's encoding, for example, "<code>UTF-8</code>"
102         * @param variablesModel
103         *            the template variables as a model
104         */
105        public TextTemplateResourceReference(final Class<?> scope, final String fileName,
106                final String contentType, final String encoding, IModel<Map<String, Object>> variablesModel)
107        {
108                this(scope, fileName, contentType, encoding, variablesModel, null, null, null);
109        }
110
111        /**
112         * Construct.
113         * 
114         * @param scope
115         *            the <code>Class</code> to be used for retrieving the classloader for loading the
116         *            <code>PackagedTextTemplate</code>
117         * @param fileName
118         *            the file name
119         * @param contentType
120         *            the mime type of this resource, such as "<code>image/jpeg</code>" or "
121         *            <code>text/html</code>"
122         * @param encoding
123         *            the file's encoding, for example, "<code>UTF-8</code>"
124         * @param variablesModel
125         *            the template variables as a model
126         * @param locale
127         *            Preferred locale for the resource
128         * @param style
129         *            Preferred style for the resource
130         * @param variation
131         *            Preferred variation for the resource
132         */
133        public TextTemplateResourceReference(final Class<?> scope, final String fileName,
134                final String contentType, final String encoding,
135                IModel<Map<String, Object>> variablesModel, Locale locale, String style, String variation)
136        {
137                super(scope, fileName, locale, style, variation);
138
139                textTemplate = new PackageTextTemplate(scope, fileName, contentType, encoding);
140                this.variablesModel = variablesModel;
141
142                resource = new ResourceStreamResource(null)
143                {
144                        @Override
145                        protected IResourceStream getResourceStream(Attributes attributes)
146                        {
147                                IModel<Map<String, Object>> variables = TextTemplateResourceReference.this.variablesModel;
148                                String stringValue = textTemplate.asString(variables.getObject());
149                                variables.detach(); // We're done with the model so detach it!
150
151                                StringResourceStream resourceStream = new StringResourceStream(stringValue,
152                                                textTemplate.getContentType());
153                                resourceStream.setLastModified(Instant.now());
154
155                                return resourceStream;
156                        }
157                };
158                resource.setCacheDuration(Duration.ZERO);
159
160                if (Application.exists())
161                {
162                        // TextTemplateResourceReference should not be cached due to its dynamic nature
163                        // Old entry in the registry would keep wrong 'variablesModel'
164                        ResourceReferenceRegistry resourceReferenceRegistry = Application.get().getResourceReferenceRegistry();
165                        resourceReferenceRegistry.unregisterResourceReference(getKey());
166                        resourceReferenceRegistry.registerResourceReference(this);
167                }
168        }
169
170        /**
171         * Creates a new resource which returns the interpolated value of the text template.
172         * 
173         * @return a new resource which returns the interpolated value of the text template
174         */
175        @Override
176        public IResource getResource()
177        {
178                return resource;
179        }
180}