All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.neba.core.rendering.BeanRendererImpl Maven / Gradle / Ivy

/**
 * Copyright 2013 the original author or authors.
 * 
 * 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.
**/

package io.neba.core.rendering;

import io.neba.api.Constants;
import io.neba.api.rendering.BeanRenderer;
import io.neba.core.util.FastStringWriter;
import org.apache.sling.scripting.api.BindingsValuesProvider;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;

import java.io.Writer;
import java.util.List;
import java.util.Map;

import static io.neba.core.util.ClassHierarchyIterator.hierarchyOf;
import static org.apache.commons.lang.StringUtils.endsWith;
import static org.apache.commons.lang.StringUtils.isBlank;
import static org.springframework.util.Assert.notNull;

/**
 * Renders an object using the provided view repository path, the object's
 * {@link io.neba.core.util.ClassHierarchyIterator class hierarchy} and a provided viewHint to
 * build possible template names for the object. Supports adding additional elements to the 
 * rendering context.
 * 
 * 

* Example: For a class io.neba.MyModel implementing the interface * io.neba.MyInterface, the renderer will attempt to resolve the following * templates (in this particular order according to the * {@link io.neba.core.util.ClassHierarchyIterator}), if the repositoryPath is "/apps/views/" and * the view hint is null: *

* *
    *
  1. /apps/views/io/neba/MyModel.vlt
  2. *
  3. /apps/views/io/neba/MyInterface.vlt
  4. *
  5. /java/lang/Object.vlt
  6. *
* *

* The first view found is used to render the object. *

* *

* Likewise, when rendering with a viewhint, e.g. "teaser", the following views * would be resolved: *

* *
    *
  1. /apps/views/io/neba/MyModel-teaser.vlt
  2. *
  3. /apps/views/io/neba/MyInterface-teaser.vlt
  4. *
  5. /java/lang/Object-teaser.vlt
  6. *
* * TODO: Performance optimization: Caching of failed lookups and hierarchy iteration. * * @author Olaf Otto */ public class BeanRendererImpl implements BeanRenderer { private final String repositoryPath; private final VelocityEngine engine; private final List bindingsValuesProviders; public BeanRendererImpl(String repositoryPath, VelocityEngine engine, List bindingsValuesProviders) { notNull(repositoryPath, "The view repository root path must not be null."); notNull(engine, "The velocity engine must not be null."); this.repositoryPath = normalize(repositoryPath); this.engine = engine; this.bindingsValuesProviders = bindingsValuesProviders; } private String normalize(String repositoryPath) { if (!endsWith(repositoryPath, "/")) { return repositoryPath + "/"; } return repositoryPath; } @Override public String render(Object bean, String viewHint, Map additionalContextElements) { notNull(bean, "Method argument bean must not be null."); String renderedObject = null; for (Class type : hierarchyOf(bean.getClass())) { String templatePath = createTemplateName(viewHint, type); if (this.engine.resourceExists(templatePath)) { renderedObject = renderInternal(bean, templatePath, additionalContextElements); break; } } return renderedObject; } public String renderInternal(Object object, String templatePath, Map additionalContextElements) { final Writer writer = new FastStringWriter(); final VelocityBindings bindings = prepareBindings(object); merge(additionalContextElements, bindings); final VelocityContext context = new VelocityContext(bindings); String renderedObject; try { this.engine.getTemplate(templatePath).merge(context, writer); } catch (Exception e) { throw new RuntimeException("Unable to render " + object + " with template " + templatePath + ".", e); } renderedObject = writer.toString(); return renderedObject; } private VelocityBindings prepareBindings(Object model) { VelocityBindings bindings = new VelocityBindings(); for (BindingsValuesProvider provider : this.bindingsValuesProviders) { provider.addBindings(bindings); } bindings.put(Constants.RENDERER, this); bindings.put(Constants.MODEL, model); return bindings; } public String createTemplateName(String viewHint, Class type) { StringBuilder templatePathBuilder = new StringBuilder(128); templatePathBuilder.append(this.repositoryPath).append(type.getName().replace('.', '/')); if (!isBlank(viewHint)) { templatePathBuilder.append('-').append(viewHint); } templatePathBuilder.append(".vlt"); return templatePathBuilder.toString(); } private void merge(Map context, final VelocityBindings bindings) { if (context != null) { for (Map.Entry entry : context.entrySet()) { final String key = entry.getKey(); if (bindings.containsKey(key)) { throw new IllegalArgumentException("The bindings already contain the key '" + key + "' (a " + bindings.get(key) + ")."); } else { bindings.put(key, entry.getValue()); } } } } @Override public String render(Object bean) { return render(bean, null, null); } @Override public String render(Object bean, Map context) { return render(bean, null, context); } @Override public String render(Object bean, String viewHint) { return render(bean, viewHint, null); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy