package com.psddev.cms.db; import java.io.IOException; import java.io.StringWriter; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.UUID; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.psddev.dari.db.ObjectField; import com.psddev.dari.db.ObjectType; import com.psddev.dari.db.State; import com.psddev.dari.util.AbstractFilter; import com.psddev.dari.util.CompactMap; import com.psddev.dari.util.JspBufferFilter; import com.psddev.dari.util.LazyWriter; import com.psddev.dari.util.LazyWriterResponse; import com.psddev.dari.util.ObjectUtils; import com.psddev.dari.util.ThreadLocalStack; /** * Internal filter that adds {@code <span data-field>} to the response * whenever an object field is accessed. */ public class FieldAccessFilter extends AbstractFilter { private static final String ATTRIBUTE_PREFIX = FieldAccessFilter.class.getName() + "."; private static final String DISPLAY_IDS_ATTRIBUTE = ATTRIBUTE_PREFIX + "displayIds"; static final String CURRENT_RESPONSE_ATTRIBUTE = ATTRIBUTE_PREFIX + "currentResponse"; static final ThreadLocalStack<LazyWriter> THREAD_DEFAULT_LAZY_WRITER = new ThreadLocalStack<>(); /** * Returns IDs of of all objects whose field accesses should be * displayed. * * @param request Nonnull. * @return Nonnull. */ public static Set<UUID> getDisplayIds(HttpServletRequest request) { @SuppressWarnings("unchecked") Set<UUID> displayIds = (Set<UUID>) request.getAttribute(DISPLAY_IDS_ATTRIBUTE); if (displayIds == null) { displayIds = new HashSet<>(); request.setAttribute(DISPLAY_IDS_ATTRIBUTE, displayIds); } return displayIds; } /** * Creates the marker HTML that identifies access to a field with the * given {@code name} in the given {@code state}. * * @param state Nonnull. * @param name Nonnull. * @return Nonnull. */ public static String createMarkerHtml(State state, String name) { Map<String, String> markerData = new CompactMap<>(); ObjectType type = state.getType(); if (type != null) { ObjectField field = type.getField(name); if (field != null) { String fieldType = field.getInternalType(); if (ObjectField.TEXT_TYPE.equals(fieldType)) { Object value = state.get(name); if (value != null) { markerData.put("text", value.toString()); } } } } markerData.put("id", state.getId().toString()); markerData.put("name", name); return PageFilter.createMarkerHtml("BrightspotCmsFieldAccess", markerData); } /** * Writes using the given {@code consumer}, inserting field access markers * as necessary, and returns the output. * * @param inBody {@code true} if the writes are within {@code <body>}. * @param consumer Nonnull. * @return Nonnull. */ public static String write(boolean inBody, FieldAccessWriteConsumer consumer) { StringWriter stringWriter = new StringWriter(); LazyWriter lazyWriter = new LazyWriter(stringWriter, inBody); THREAD_DEFAULT_LAZY_WRITER.with(lazyWriter, () -> { try { consumer.accept(lazyWriter); lazyWriter.writePending(); } catch (IOException error) { throw new IllegalStateException(error); } }); return stringWriter.toString(); } @Override protected void doDispatch( HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws Exception { if (ObjectUtils.to(boolean.class, request.getParameter("_fields")) || (PageFilter.Static.getMainObject(request) != null && PageFilter.Static.isInlineEditingAllContents(request))) { super.doDispatch(request, response, chain); } else { chain.doFilter(request, response); } } @Override protected void doInclude( HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { Object current = request.getAttribute(CURRENT_RESPONSE_ATTRIBUTE); LazyWriterResponse lazyResponse = new LazyWriterResponse(request, response); try { request.setAttribute(CURRENT_RESPONSE_ATTRIBUTE, lazyResponse); chain.doFilter(request, lazyResponse); } finally { request.setAttribute(CURRENT_RESPONSE_ATTRIBUTE, current); lazyResponse.getLazyWriter().writePending(); } } @Override protected void doRequest( HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { JspBufferFilter.Static.overrideBuffer(0); try { Object mainObject = PageFilter.Static.getMainObject(request); FieldAccessListener listener = new FieldAccessListener(request); try { State.Static.addListener(listener); doInclude(request, response, chain); } finally { State.Static.removeListener(listener); } } finally { JspBufferFilter.Static.restoreBuffer(); } } /** * @deprecated * @see FieldAccessFilter */ public static class Static { /** * @deprecated * @see FieldAccessFilter#createMarkerHtml(State, String) */ @Deprecated public static String createMarkerHtml(State state, String name) { return FieldAccessFilter.createMarkerHtml(state, name); } /** * @deprecated * @see FieldAccessFilter#getDisplayIds(HttpServletRequest) */ @Deprecated public static Set<UUID> getDisplayIds(HttpServletRequest request) { return FieldAccessFilter.getDisplayIds(request); } } }