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