001/*
002 * The contents of this file are subject to the license and copyright
003 * detailed in the LICENSE and NOTICE files at the root of the source
004 * tree.
005 */
006package org.fcrepo.http.api.responses;
007
008import org.apache.velocity.Template;
009import org.apache.velocity.VelocityContext;
010import org.apache.velocity.app.VelocityEngine;
011import org.apache.velocity.context.Context;
012import org.apache.velocity.tools.generic.EscapeTool;
013
014import org.fcrepo.config.FedoraPropsConfig;
015import org.fcrepo.http.api.FedoraSearch;
016import org.fcrepo.http.commons.responses.ViewHelpers;
017import org.fcrepo.search.api.Condition;
018import org.fcrepo.search.api.SearchResult;
019import org.slf4j.Logger;
020
021import java.io.IOException;
022import java.io.InputStream;
023import java.io.OutputStream;
024import java.io.PrintWriter;
025import java.lang.annotation.Annotation;
026import java.lang.reflect.Type;
027import java.net.URL;
028import java.nio.charset.StandardCharsets;
029import java.util.Arrays;
030import java.util.Properties;
031
032import javax.annotation.PostConstruct;
033import javax.inject.Inject;
034import javax.servlet.http.HttpServletRequest;
035import javax.ws.rs.Produces;
036import javax.ws.rs.WebApplicationException;
037import javax.ws.rs.core.MediaType;
038import javax.ws.rs.core.MultivaluedMap;
039import javax.ws.rs.core.UriInfo;
040import javax.ws.rs.ext.MessageBodyWriter;
041import javax.ws.rs.ext.Provider;
042
043import static org.fcrepo.http.commons.domain.RDFMediaType.TEXT_HTML_WITH_CHARSET;
044import static org.slf4j.LoggerFactory.getLogger;
045
046/**
047 * HTML writer for search results
048 *
049 * @author awoods
050 * @since 2020-08-04
051 */
052@Provider
053@Produces({TEXT_HTML_WITH_CHARSET})
054public class SearchResultProvider implements MessageBodyWriter<SearchResult> {
055
056    @Inject
057    UriInfo uriInfo;
058
059    @Inject
060    HttpServletRequest request;
061
062    @Inject
063    private FedoraPropsConfig fedoraPropsConfig;
064
065    private static final EscapeTool escapeTool = new EscapeTool();
066
067    private final VelocityEngine velocity = new VelocityEngine();
068
069    // Location in the classpath where Velocity templates are to be found.
070    private static final String templatesLocation = "/views";
071
072    private static final String templateFilenameExtension = ".vsl";
073
074    private static final String velocityPropertiesLocation = "/velocity.properties";
075
076    private static final ViewHelpers VIEW_HELPERS = ViewHelpers.getInstance();
077
078    private static final Logger LOGGER = getLogger(SearchResultProvider.class);
079
080    @PostConstruct
081    void init() throws IOException {
082        LOGGER.trace("Velocity engine initializing...");
083        final Properties properties = new Properties();
084        final var velocityLog = fedoraPropsConfig.getVelocityLog().toString();
085        LOGGER.debug("Setting Velocity runtime log: {}", velocityLog);
086        properties.setProperty("runtime.log", velocityLog);
087
088        final URL propertiesUrl = getClass().getResource(velocityPropertiesLocation);
089
090        LOGGER.debug("Using Velocity configuration from {}", propertiesUrl);
091        try (final InputStream propertiesStream = propertiesUrl.openStream()) {
092            properties.load(propertiesStream);
093        }
094        velocity.init(properties);
095        LOGGER.trace("Velocity engine initialized.");
096    }
097
098    @Override
099    public boolean isWriteable(final Class<?> type,
100                               final Type genericType,
101                               final Annotation[] annotations,
102                               final MediaType mediaType) {
103        return SearchResult.class.isAssignableFrom(type);
104    }
105
106    @Override
107    public long getSize(final SearchResult result,
108                        final Class<?> type,
109                        final Type genericType,
110                        final Annotation[] annotations,
111                        final MediaType mediaType) {
112        return -1;
113    }
114
115    @Override
116    public void writeTo(final SearchResult result,
117                        final Class<?> type,
118                        final Type genericType,
119                        final Annotation[] annotations,
120                        final MediaType mediaType,
121                        final MultivaluedMap<String, Object> httpHeaders,
122                        final OutputStream entityStream)
123            throws WebApplicationException {
124
125        final Template template = velocity.getTemplate(getTemplateLocation("search"));
126
127        final Context context;
128        context = getContext();
129        context.put("searchResults", result);
130
131        // The contract of MessageBodyWriter<T> is _not_ to close the stream after writing to it
132        final PrintWriter writer = new PrintWriter(entityStream, false, StandardCharsets.UTF_8);
133        template.merge(context, writer);
134        writer.flush();
135    }
136
137    private Context getContext() {
138
139        final Context context = new VelocityContext();
140        final String[] baseUrl = uriInfo.getBaseUri().getPath().split("/");
141        if (baseUrl.length > 0) {
142            final String staticBaseUrl = String.join("/", Arrays.copyOf(baseUrl, baseUrl.length - 1));
143            context.put("staticBaseUrl", staticBaseUrl);
144        } else {
145            context.put("staticBaseUrl", "/");
146        }
147        final var searchPage = uriInfo.getBaseUriBuilder().clone().path(FedoraSearch.class).toString();
148        context.put("searchPage", searchPage);
149        context.put("fields",
150                Arrays.stream(Condition.Field.values()).map(Condition.Field::toString).toArray(String[]::new));
151        context.put("operators", Arrays.stream(Condition.Operator.values()).map(Condition.Operator::getStringValue)
152                .toArray(String[]::new));
153        context.put("isOriginalResource", null);
154        context.put("helpers", VIEW_HELPERS);
155        context.put("esc", escapeTool);
156        context.put("uriInfo", uriInfo);
157        return context;
158    }
159
160    private static String getTemplateLocation(final String templateName) {
161        return templatesLocation + "/" + templateName.replace(':', '-') + templateFilenameExtension;
162    }
163
164}