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.velocity;
018
019import java.io.StringWriter;
020import java.util.Map;
021
022import org.apache.velocity.VelocityContext;
023import org.apache.velocity.app.Velocity;
024import org.apache.wicket.Component;
025import org.apache.wicket.WicketRuntimeException;
026import org.apache.wicket.behavior.Behavior;
027import org.apache.wicket.markup.head.IHeaderResponse;
028import org.apache.wicket.markup.head.StringHeaderItem;
029import org.apache.wicket.model.IModel;
030import org.apache.wicket.util.lang.Args;
031import org.apache.wicket.util.string.Strings;
032
033/**
034 * An IHeaderContributor implementation that renders a velocity template and writes it to the
035 * response. The template is loaded via Velocity's resource loading mechanism, as defined in your
036 * velocity.properties. If you do not have a velocity.properties for your app, it will default to a
037 * directory "templates" in the root of your app.
038 */
039public class VelocityContributor extends Behavior
040{
041        private static final long serialVersionUID = 1L;
042
043        private String encoding = "ISO-8859-1";
044
045        private final IModel<? extends Map<String, Object>> model;
046
047        private final String templateName;
048
049        /**
050         * The templateName needs to have the full path relative to where the resource loader starts
051         * looking. For example, if there is a template next to this class in the package called foo.vm,
052         * and you have configured the ClassPathResourceLoader, template name will then be
053         * "wicket/contrib/util/resource/foo.vm". Wicket provides a nice utility
054         * {@link org.apache.wicket.util.lang.Packages} for this.
055         * 
056         * @param templateName
057         * @param model
058         */
059        public VelocityContributor(final String templateName, final IModel<? extends Map<String, Object>> model)
060        {
061                Args.notNull(model, "model");
062
063                this.templateName = templateName;
064                this.model = model;
065        }
066
067        /**
068         * {@inheritDoc}
069         */
070        @Override
071        public void detach(final Component c)
072        {
073                model.detach();
074        }
075
076        /**
077         * @return The encoding
078         */
079        public String getEncoding()
080        {
081                return encoding;
082        }
083
084        /**
085         * {@inheritDoc}
086         */
087        @Override
088        public void renderHead(final Component component, final IHeaderResponse response)
089        {
090                CharSequence s = evaluate();
091                if (null != s)
092                {
093                        response.render(StringHeaderItem.forString(s));
094                }
095        }
096
097        /**
098         * @param encoding
099         *            The encoding
100         */
101        public void setEncoding(final String encoding)
102        {
103                this.encoding = encoding;
104        }
105
106        /**
107         * @return whether to escape HTML characters. The default value is false
108         */
109        protected boolean escapeHtml()
110        {
111                return false;
112        }
113
114        /**
115         * Evaluate the template.
116         * 
117         * @return The evaluated template
118         */
119        protected final CharSequence evaluate()
120        {
121                if (!Velocity.resourceExists(templateName))
122                {
123                        return null;
124                }
125
126                // create a Velocity context object using the model if set
127                final VelocityContext ctx = new VelocityContext(model.getObject());
128
129                // create a writer for capturing the Velocity output
130                StringWriter writer = new StringWriter();
131
132                try
133                {
134                        // execute the velocity script and capture the output in writer
135                        if (!Velocity.mergeTemplate(templateName, encoding, ctx, writer))
136                        {
137                                return null;
138                        }
139
140                        // replace the tag's body the Velocity output
141                        CharSequence result = writer.getBuffer();
142
143                        if (escapeHtml())
144                        {
145                                // encode the result in order to get valid html output that
146                                // does not break the rest of the page
147                                result = Strings.escapeMarkup(result.toString());
148                        }
149                        return result;
150                }
151                catch (Exception e)
152                {
153                        throw new WicketRuntimeException("Error while executing velocity script: " +
154                                templateName, e);
155                }
156        }
157}