package com.psddev.cms.view; import com.psddev.cms.db.PageFilter; import com.psddev.dari.db.ObjectType; import com.psddev.dari.db.State; import com.psddev.dari.util.PageContextFilter; import com.psddev.dari.util.StringUtils; import com.psddev.dari.util.TypeDefinition; import org.slf4j.LoggerFactory; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Supplier; /** * A renderer of views. */ public interface ViewRenderer { /** * @deprecated Use {@link ViewRenderer#render(Object, ViewTemplateLoader)} instead. */ @Deprecated default ViewOutput render(Object view) { throw new UnsupportedOperationException("Must implement ViewRenderer#render(Object, ViewTemplateLoader)!"); } /** * Renders a view, storing the result. * * @param view the view to render. * @param templateLoader the template loader. * @return the result of rendering a view. */ default ViewOutput render(Object view, ViewTemplateLoader templateLoader) { return render(view); } /** * Gets the content type produced by this view renderer. * * @return the content type. */ default String getContentType() { return "text/html"; } /** * Creates an appropriate ViewRenderer based on the specified view. * * @param view the view from which to create a view renderer. * @return the view renderer for the specified view. */ static ViewRenderer createRenderer(Object view) { if (view == null) { return null; } if (view instanceof ViewMap) { view = ((ViewMap) view).toView(); } // we expect a list of size 1 List<ViewRenderer> renderers = new ArrayList<>(); for (Class<?> viewClass : ViewUtils.getAnnotatableClasses(view.getClass())) { ViewRendererClass rendererAnnotation = viewClass.getAnnotation(ViewRendererClass.class); if (rendererAnnotation != null) { Class<? extends ViewRenderer> rendererClass = rendererAnnotation.value(); if (rendererClass != null) { try { ViewRenderer renderer = TypeDefinition.getInstance(rendererClass).newInstance(); if (renderer != null) { renderers.add(renderer); } } catch (Exception e) { LoggerFactory.getLogger(ViewRenderer.class) .warn("Unable to create instance of renderer of type [" + rendererClass.getName() + "]"); } } } // check for annotation processors. for (Annotation viewAnnotation : viewClass.getAnnotations()) { Class<?> annotationClass = viewAnnotation.annotationType(); ViewRendererAnnotationProcessorClass annotation = annotationClass.getAnnotation( ViewRendererAnnotationProcessorClass.class); if (annotation != null) { Class<? extends ViewRendererAnnotationProcessor<? extends Annotation>> annotationProcessorClass = annotation.value(); if (annotationProcessorClass != null) { @SuppressWarnings("unchecked") ViewRendererAnnotationProcessor<Annotation> annotationProcessor = (ViewRendererAnnotationProcessor<Annotation>) TypeDefinition.getInstance(annotationProcessorClass).newInstance(); ViewRenderer renderer = annotationProcessor.createRenderer(view.getClass(), viewAnnotation); if (renderer != null) { renderers.add(renderer); } } } } } if (!renderers.isEmpty()) { if (renderers.size() == 1) { ViewRenderer renderer = renderers.get(0); // wrap the view renderer so that it always converts the view to a ViewMap // before delegating to the actual renderer if it's not already a map. return new ViewRenderer() { @Override public String getContentType() { return renderer.getContentType(); } @Deprecated @Override public ViewOutput render(Object view) { return createViewOutput( view, () -> view instanceof Map ? renderer.render(view) : renderer.render(new ViewMap(view))); } @Override public ViewOutput render(Object view, ViewTemplateLoader loader) { return createViewOutput( view, () -> view instanceof Map ? renderer.render(view, loader) : renderer.render(new ViewMap(view), loader)); } private ViewOutput createViewOutput(Object view, Supplier<ViewOutput> viewOutputSupplier) { HttpServletRequest request = PageContextFilter.Static.getRequestOrNull(); HttpServletResponse response = PageContextFilter.Static.getResponseOrNull(); String contentType = response != null ? response.getContentType() : null; if (request == null || !PageFilter.Static.isInlineEditingAllContents(request) || (contentType != null && !StringUtils.ensureEnd(contentType, ";").startsWith("text/html;"))) { return viewOutputSupplier.get(); } if (view instanceof ViewMap) { view = ((ViewMap) view).toView(); } if (!(view instanceof ViewModel)) { return viewOutputSupplier.get(); } Object model = ((ViewModel) view).model; PageFilter.Static.pushObject(request, model); try { Map<String, String> map = new HashMap<>(); Object concrete = PageFilter.Static.peekConcreteObject(request); if (concrete != null) { State state = State.getInstance(concrete); ObjectType stateType = state.getType(); map.put("id", state.getId().toString()); if (stateType != null) { map.put("typeLabel", stateType.getLabel()); } try { map.put("label", state.getLabel()); } catch (RuntimeException error) { // Not a big deal if label can't be retrieved. } } String viewOutput = viewOutputSupplier.get().get(); return () -> PageFilter.createMarkerHtml("BrightspotCmsObjectBegin", map) + (viewOutput != null ? viewOutput : "") + PageFilter.createMarkerHtml("BrightspotCmsObjectEnd", null); } finally { PageFilter.Static.popObject(request); } } }; } else { LoggerFactory.getLogger(ViewRenderer.class) .warn("Found multiple renderers for view of type [" + view.getClass().getName() + "]!"); return null; } } else { return null; } } }