package com.psddev.cms.db; import java.io.IOException; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.psddev.dari.db.Modification; import com.psddev.dari.db.ObjectField; import com.psddev.dari.db.ObjectType; import com.psddev.dari.db.Recordable; import com.psddev.dari.util.CompactMap; import com.psddev.dari.util.HtmlWriter; import com.psddev.dari.util.ObjectUtils; public interface Renderer extends Recordable { public void renderObject( HttpServletRequest request, HttpServletResponse response, HtmlWriter writer) throws IOException, ServletException; /** * Global modification that stores rendering hints. */ @FieldInternalNamePrefix("cms.renderable.") public static class Data extends Modification<Object> { public Map<String, List<String>> listLayouts; public Map<String, List<String>> getListLayouts() { if (listLayouts == null) { listLayouts = new HashMap<String, List<String>>(); } return listLayouts; } public void setListLayouts(Map<String, List<String>> listLayouts) { this.listLayouts = listLayouts; } } /** * Type modification that stores how objects should be rendered. */ @FieldInternalNamePrefix("cms.render.") public static class TypeModification extends Modification<ObjectType> { @InternalName("renderScript") private String path; private Map<String, String> paths; private String layoutPath; private String embedPath; private int embedPreviewWidth; // Returns the legacy rendering JSP. private String getDefaultRecordJsp() { return (String) getState().get("cms.defaultRecordJsp"); } /** * Returns the default servlet path used to render instances of this * type. * * @return May be {@code null}. */ public String getPath() { if (ObjectUtils.isBlank(path)) { String jsp = getDefaultRecordJsp(); if (!ObjectUtils.isBlank(jsp)) { path = jsp; } } return path; } /** * Returns the default servlet path used to render instances of this * type. * * @param path May be {@code nul}. */ public void setPath(String path) { this.path = path; } /** * Returns all servlet paths associated with rendering instances of * this type in a specific context. * * @return Never {@code null}. * @see ContextTag */ public Map<String, String> getPaths() { if (paths == null) { paths = new CompactMap<String, String>(); } return paths; } /** * Sets all servlet paths associated with rendering instances of * this type in a specific context. * * @param paths May be {@code null} to remove all associations. * @see ContextTag */ public void setPaths(Map<String, String> paths) { this.paths = paths; } /** * Returns the servlet path used to render the layout around the * instances of this type. * * @return May be {@code null}. */ public String getLayoutPath() { return layoutPath; } /** * Sets the servlet path used to render the layout around the * instances of this type. * * @param layoutPath May be {@code null}. */ public void setLayoutPath(String layoutPath) { this.layoutPath = layoutPath; } public String getEmbedPath() { return embedPath; } public void setEmbedPath(String embedPath) { this.embedPath = embedPath; } public int getEmbedPreviewWidth() { return embedPreviewWidth; } public void setEmbedPreviewWidth(int embedPreviewWidth) { this.embedPreviewWidth = embedPreviewWidth; } /** * Finds the servlet path that should be used to render the instances * of this type in the current context of the given {@code request}. * * @param request Can't be {@code null}. * @return May be {@code null}. */ public String findContextualPath(ServletRequest request) { Map<String, String> paths = getPaths(); for (Iterator<String> i = ContextTag.Static.getContexts(request).descendingIterator(); i.hasNext();) { String context = i.next(); String path = paths.get(context); if (!ObjectUtils.isBlank(path)) { return path; } } return getPath(); } // --- Deprecated --- private static final String FIELD_PREFIX = "cms.render."; /** @deprecated No replacement. */ @Deprecated public static final String ENGINE_FIELD = FIELD_PREFIX + "renderEngine"; /** @deprecated No replacement. */ @Deprecated public static final String SCRIPT_FIELD = FIELD_PREFIX + "renderScript"; @Deprecated @InternalName(ENGINE_FIELD) private String engine; /** @deprecated No replacement. */ @Deprecated public String getEngine() { if (ObjectUtils.isBlank(engine)) { String jsp = getDefaultRecordJsp(); if (!ObjectUtils.isBlank(jsp)) { setEngine("JSP"); } } return engine; } /** @deprecated No replacement. */ @Deprecated public void setEngine(String engine) { this.engine = engine; } /** @deprecated Use {@link #getPath} instead. */ @Deprecated public String getScript() { return getPath(); } /** @deprecated Use {@link #setPath} instead. */ @Deprecated public void setScript(String script) { setPath(script); } } /** * Field modification that stores how field values should be * rendered. */ public static class FieldData extends Modification<ObjectField> { private Map<String, List<String>> listLayouts; public Map<String, List<String>> getListLayouts() { if (listLayouts == null) { listLayouts = new HashMap<String, List<String>>(); } return listLayouts; } public void setListLayouts(Map<String, List<String>> listLayouts) { this.listLayouts = listLayouts; } } /** * Specifies the servlet path used to render instances of the target type * as a module. */ @Documented @Inherited @ObjectType.AnnotationProcessorClass(PathProcessor.class) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Path { String value(); String context() default ""; } @Documented @Inherited @ObjectType.AnnotationProcessorClass(PathsProcessor.class) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Paths { Path[] value(); } /** * Specifies the servlet path used to render instances of the target type * as a page. */ @Documented @Inherited @ObjectType.AnnotationProcessorClass(LayoutPathProcessor.class) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface LayoutPath { String value(); } /** * Specifies the servlet path used to render instances of the target type * when embedded in another page. */ @Documented @Inherited @ObjectType.AnnotationProcessorClass(EmbedPathProcessor.class) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface EmbedPath { String value(); } /** * Specifies the width (in pixels) of the preview for the instances of * the target type. */ @Documented @Inherited @ObjectType.AnnotationProcessorClass(EmbedPreviewWidthProcessor.class) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface EmbedPreviewWidth { int value(); } @Documented @Retention(RetentionPolicy.RUNTIME) public @interface ListLayout { String name(); Class<?>[] itemClasses(); } @Documented @Inherited @ObjectField.AnnotationProcessorClass(ListLayoutsProcessor.class) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ListLayouts { String[] value() default { }; ListLayout[] map() default { }; } // --- Deprecated --- /** @deprecated No replacement. */ @Deprecated @Documented @Inherited @ObjectType.AnnotationProcessorClass(EngineProcessor.class) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Engine { String value(); } /** @deprecated Use {@link Path} instead. */ @Deprecated @Documented @Inherited @ObjectType.AnnotationProcessorClass(ScriptProcessor.class) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Script { String value(); } } class PathProcessor implements ObjectType.AnnotationProcessor<Renderer.Path> { @Override public void process(ObjectType type, Renderer.Path annotation) { Renderer.TypeModification rendererData = type.as(Renderer.TypeModification.class); Map<String, String> paths = rendererData.getPaths(); String value = annotation.value(); String context = annotation.context(); if (ObjectUtils.isBlank(context)) { rendererData.setPath(value); } else { paths.put(context, value); } } } class PathsProcessor implements ObjectType.AnnotationProcessor<Renderer.Paths> { @Override public void process(ObjectType type, Renderer.Paths annotation) { PathProcessor pathProcessor = new PathProcessor(); for (Renderer.Path pathAnnotation : annotation.value()) { pathProcessor.process(type, pathAnnotation); } } } class LayoutPathProcessor implements ObjectType.AnnotationProcessor<Renderer.LayoutPath> { @Override public void process(ObjectType type, Renderer.LayoutPath annotation) { type.as(Renderer.TypeModification.class).setLayoutPath(annotation.value()); } } class EmbedPathProcessor implements ObjectType.AnnotationProcessor<Renderer.EmbedPath> { @Override public void process(ObjectType type, Renderer.EmbedPath annotation) { type.as(Renderer.TypeModification.class).setEmbedPath(annotation.value()); } } class EmbedPreviewWidthProcessor implements ObjectType.AnnotationProcessor<Renderer.EmbedPreviewWidth> { @Override public void process(ObjectType type, Renderer.EmbedPreviewWidth annotation) { type.as(Renderer.TypeModification.class).setEmbedPreviewWidth(annotation.value()); } } class ListLayoutsProcessor implements ObjectField.AnnotationProcessor<Renderer.ListLayouts> { @Override public void process(ObjectType type, ObjectField field, Renderer.ListLayouts annotation) { String[] value = annotation.value(); Renderer.ListLayout[] map = annotation.map(); Map<String, List<String>> listLayouts = field.as(Renderer.FieldData.class).getListLayouts(); for (String layoutName : value) { listLayouts.put(layoutName, new ArrayList<String>()); } for (Renderer.ListLayout layout : map) { List<String> layoutItems = new ArrayList<String>(); listLayouts.put(layout.name(), layoutItems); for (Class<?> itemClass : layout.itemClasses()) { layoutItems.add(itemClass.getName()); } } } } @Deprecated class EngineProcessor implements ObjectType.AnnotationProcessor<Renderer.Engine> { @Override public void process(ObjectType type, Renderer.Engine annotation) { type.as(Renderer.TypeModification.class).setEngine(annotation.value()); } } @Deprecated class ScriptProcessor implements ObjectType.AnnotationProcessor<Renderer.Script> { @Override public void process(ObjectType type, Renderer.Script annotation) { type.as(Renderer.TypeModification.class).setScript(annotation.value()); } }