001/* 002 * Copyright 2015 DuraSpace, Inc. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.fcrepo.http.api.responses; 017 018import static java.util.stream.Stream.of; 019import static javax.ws.rs.core.MediaType.APPLICATION_XHTML_XML; 020import static javax.ws.rs.core.MediaType.APPLICATION_XHTML_XML_TYPE; 021import static javax.ws.rs.core.MediaType.TEXT_HTML; 022import static javax.ws.rs.core.MediaType.TEXT_HTML_TYPE; 023import static com.google.common.collect.ImmutableMap.builder; 024import static com.hp.hpl.jena.graph.Node.ANY; 025import static com.hp.hpl.jena.sparql.util.graph.GraphUtils.multiValueURI; 026import static com.hp.hpl.jena.vocabulary.RDF.type; 027import static org.fcrepo.kernel.api.RdfLexicon.LDP_NAMESPACE; 028import static org.fcrepo.kernel.api.RdfLexicon.REPOSITORY_NAMESPACE; 029import static org.fcrepo.kernel.api.RdfCollectors.toModel; 030import static org.slf4j.LoggerFactory.getLogger; 031 032import java.io.IOException; 033import java.io.InputStream; 034import java.io.OutputStream; 035import java.io.OutputStreamWriter; 036import java.io.Writer; 037import java.lang.annotation.Annotation; 038import java.lang.reflect.Type; 039import java.net.URL; 040import java.util.Arrays; 041import java.util.List; 042import java.util.Map; 043import java.util.Properties; 044 045import javax.annotation.PostConstruct; 046import javax.ws.rs.Produces; 047import javax.ws.rs.core.MediaType; 048import javax.ws.rs.core.MultivaluedMap; 049import javax.ws.rs.core.UriInfo; 050import javax.ws.rs.ext.MessageBodyWriter; 051import javax.ws.rs.ext.Provider; 052 053import com.google.common.collect.ImmutableMap; 054import com.hp.hpl.jena.graph.Node; 055import com.hp.hpl.jena.rdf.model.Model; 056import org.apache.velocity.Template; 057import org.apache.velocity.VelocityContext; 058import org.apache.velocity.app.VelocityEngine; 059import org.apache.velocity.context.Context; 060import org.apache.velocity.tools.generic.EscapeTool; 061import org.apache.velocity.tools.generic.FieldTool; 062import org.fcrepo.http.commons.responses.HtmlTemplate; 063import org.fcrepo.http.commons.responses.RdfNamespacedStream; 064import org.fcrepo.http.commons.responses.ViewHelpers; 065import org.fcrepo.kernel.api.RdfLexicon; 066import org.slf4j.Logger; 067 068/** 069 * Simple HTML provider for RdfNamespacedStreams 070 * 071 * @author ajs6f 072 * @since Nov 19, 2013 073 */ 074@Provider 075@Produces({TEXT_HTML, APPLICATION_XHTML_XML}) 076public class StreamingBaseHtmlProvider implements MessageBodyWriter<RdfNamespacedStream> { 077 078 079 @javax.ws.rs.core.Context 080 UriInfo uriInfo; 081 082 private static EscapeTool escapeTool = new EscapeTool(); 083 084 protected VelocityEngine velocity = new VelocityEngine(); 085 086 /** 087 * Location in the classpath where Velocity templates are to be found. 088 */ 089 public static final String templatesLocation = "/views"; 090 091 /** 092 * Location in the classpath where the common css file is to be found. 093 */ 094 public static final String commonCssLocation = "/views/common.css"; 095 096 /** 097 * Location in the classpath where the common javascript file is to be found. 098 */ 099 public static final String commonJsLocation = "/views/common.js"; 100 101 /** 102 * A map from String names for primary node types to the Velocity templates 103 * that should be used for those node types. 104 */ 105 protected Map<String, Template> templatesMap; 106 107 public static final String templateFilenameExtension = ".vsl"; 108 109 public static final String velocityPropertiesLocation = 110 "/velocity.properties"; 111 112 private static final ViewHelpers VIEW_HELPERS = ViewHelpers.getInstance(); 113 114 private static final Logger LOGGER = 115 getLogger(StreamingBaseHtmlProvider.class); 116 117 @PostConstruct 118 void init() throws IOException { 119 120 LOGGER.trace("Velocity engine initializing..."); 121 final Properties properties = new Properties(); 122 final URL propertiesUrl = 123 getClass().getResource(velocityPropertiesLocation); 124 LOGGER.debug("Using Velocity configuration from {}", propertiesUrl); 125 try (final InputStream propertiesStream = propertiesUrl.openStream()) { 126 properties.load(propertiesStream); 127 } 128 velocity.init(properties); 129 LOGGER.trace("Velocity engine initialized."); 130 131 LOGGER.trace("Assembling a map of node primary types -> templates..."); 132 final ImmutableMap.Builder<String, Template> templatesMapBuilder = builder(); 133 134 of("jcr:nodetypes", "fcr:versions", "fcr:fixity", "default") 135 .forEach(key -> templatesMapBuilder.put(key, velocity.getTemplate(getTemplateLocation(key)))); 136 137 templatesMap = templatesMapBuilder 138 .put(REPOSITORY_NAMESPACE + "RepositoryRoot", velocity.getTemplate(getTemplateLocation("root"))) 139 .put(REPOSITORY_NAMESPACE + "Binary", velocity.getTemplate(getTemplateLocation("binary"))) 140 .put(REPOSITORY_NAMESPACE + "Version", velocity.getTemplate(getTemplateLocation("resource"))) 141 .put(REPOSITORY_NAMESPACE + "Pairtree", velocity.getTemplate(getTemplateLocation("resource"))) 142 .put(REPOSITORY_NAMESPACE + "Container", velocity.getTemplate(getTemplateLocation("resource"))) 143 .put(LDP_NAMESPACE + "NonRdfSource", velocity.getTemplate(getTemplateLocation("binary"))) 144 .put(LDP_NAMESPACE + "RdfSource", velocity.getTemplate(getTemplateLocation("resource"))).build(); 145 146 LOGGER.trace("Assembled template map."); 147 LOGGER.trace("HtmlProvider initialization complete."); 148 } 149 150 @Override 151 public void writeTo(final RdfNamespacedStream nsStream, final Class<?> type, 152 final Type genericType, final Annotation[] annotations, 153 final MediaType mediaType, 154 final MultivaluedMap<String, Object> httpHeaders, 155 final OutputStream entityStream) throws IOException { 156 157 final Node subject = ViewHelpers.getContentNode(nsStream.stream.topic()); 158 159 final Model model = nsStream.stream.collect(toModel()); 160 model.setNsPrefixes(nsStream.namespaces); 161 162 final Template nodeTypeTemplate = getTemplate(model, subject, Arrays.asList(annotations)); 163 164 final Context context = getContext(model, subject); 165 166 // the contract of MessageBodyWriter<T> is _not_ to close the stream 167 // after writing to it 168 final Writer outWriter = new OutputStreamWriter(entityStream); 169 nodeTypeTemplate.merge(context, outWriter); 170 outWriter.flush(); 171 } 172 173 protected Context getContext(final Model model, final Node subject) { 174 final FieldTool fieldTool = new FieldTool(); 175 176 final Context context = new VelocityContext(); 177 context.put("rdfLexicon", fieldTool.in(RdfLexicon.class)); 178 context.put("helpers", VIEW_HELPERS); 179 context.put("esc", escapeTool); 180 context.put("rdf", model.getGraph()); 181 182 context.put("model", model); 183 context.put("subjects", model.listSubjects()); 184 context.put("nodeany", ANY); 185 context.put("topic", subject); 186 context.put("uriInfo", uriInfo); 187 return context; 188 } 189 190 private Template getTemplate(final Model rdf, final Node subject, 191 final List<Annotation> annotations) { 192 193 final String tplName = annotations.stream().filter(x -> x instanceof HtmlTemplate) 194 .map(x -> ((HtmlTemplate) x).value()).filter(templatesMap::containsKey).findFirst() 195 .orElseGet(() -> { 196 final List<String> types = multiValueURI(rdf.getResource(subject.getURI()), type); 197 if (types.contains(REPOSITORY_NAMESPACE + "RepositoryRoot")) { 198 return REPOSITORY_NAMESPACE + "RepositoryRoot"; 199 } 200 return types.stream().filter(templatesMap::containsKey).findFirst().orElse("default"); 201 }); 202 LOGGER.debug("Using template: {}", tplName); 203 return templatesMap.get(tplName); 204 } 205 206 @Override 207 public boolean isWriteable(final Class<?> type, final Type genericType, 208 final Annotation[] annotations, final MediaType mediaType) { 209 LOGGER.debug( 210 "Checking to see if type: {} is serializable to mimeType: {}", 211 type.getName(), mediaType); 212 return (mediaType.equals(TEXT_HTML_TYPE) || mediaType 213 .equals(APPLICATION_XHTML_XML_TYPE)) 214 && RdfNamespacedStream.class.isAssignableFrom(type); 215 } 216 217 @Override 218 public long getSize(final RdfNamespacedStream t, final Class<?> type, 219 final Type genericType, final Annotation[] annotations, 220 final MediaType mediaType) { 221 // we don't know in advance how large the result might be 222 return -1; 223 } 224 225 private static String getTemplateLocation(final String nodeTypeName) { 226 return templatesLocation + "/" + 227 nodeTypeName.replace(':', '-') + templateFilenameExtension; 228 } 229}