/*
*
* Copyright 2016 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.netflix.genie.web.resources.handlers;
import com.netflix.genie.core.jobs.JobConstants;
import com.netflix.genie.web.resources.writers.DirectoryWriter;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.resource.EncodedResource;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
import org.springframework.web.servlet.resource.VersionedResource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
/**
* Class extends ResourceHttpRequestHandler to override handling a request to return directory listing if it
* is a directory otherwise follow default behavior.
*
* @author tgianos
* @see ResourceHttpRequestHandler
* @since 3.0.0
*/
public class GenieResourceHttpRequestHandler extends ResourceHttpRequestHandler {
/**
* Used to flag if this is the root directory or not for a given job. If not present defaults to true.
*/
public static final String GENIE_JOB_IS_ROOT_DIRECTORY
= GenieResourceHttpRequestHandler.class.getName() + ".isRootDirectory";
private static final Charset UTF_8 = Charset.forName("UTF-8");
private static final String CONTENT_LENGTH = "Content-Length";
private static final String BYTES = "bytes";
private DirectoryWriter directoryWriter;
/**
* Constructor.
*
* @param directoryWriter The class to use to convert directories to representations like HTML
*/
public GenieResourceHttpRequestHandler(final DirectoryWriter directoryWriter) {
super();
this.directoryWriter = directoryWriter;
}
/**
* {@inheritDoc}
*/
@Override
public void handleRequest(
final HttpServletRequest request,
final HttpServletResponse response
) throws ServletException, IOException {
Assert.state(this.getLocations().size() == 1, "Too many resource locations");
Assert.state(
request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE) != null,
"Request doesn't have a " + HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE + " attribute."
);
final String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
final Resource resource = this.getLocations().get(0).createRelative(path);
if (!resource.exists()) {
response.sendError(HttpStatus.NOT_FOUND.value());
return;
}
final File file = resource.getFile();
if (file.isDirectory()) {
final Object rootDirAttribute = request.getAttribute(GENIE_JOB_IS_ROOT_DIRECTORY);
final boolean isRootDirectory;
if (rootDirAttribute != null) {
isRootDirectory = (Boolean) rootDirAttribute;
} else {
isRootDirectory = true;
}
final String accept = request.getHeader(HttpHeaders.ACCEPT);
final String requestUrl;
if (request.getHeader(JobConstants.GENIE_FORWARDED_FROM_HEADER) != null) {
requestUrl = request.getHeader(JobConstants.GENIE_FORWARDED_FROM_HEADER);
} else {
requestUrl = request.getRequestURL().toString();
}
try {
if (accept != null && accept.contains(MediaType.TEXT_HTML_VALUE)) {
response.setContentType(MediaType.TEXT_HTML_VALUE);
response
.getOutputStream()
.write(
this.directoryWriter.toHtml(
file,
requestUrl,
!isRootDirectory
).getBytes(UTF_8)
);
} else {
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response
.getOutputStream()
.write(
this.directoryWriter.toJson(
file,
requestUrl,
!isRootDirectory
).getBytes(UTF_8)
);
}
} catch (final Exception e) {
throw new ServletException(e);
}
} else {
super.handleRequest(request, response);
}
}
/**
* {@inheritDoc}
*
* Overriding this method so can handle content lengths greater than Integer.MAX_VALUE.
*/
@Override
protected void setHeaders(
final HttpServletResponse response,
final Resource resource,
final MediaType mediaType
) throws IOException {
// Note: This API call is only in Servlet spec 3.1+ (Tomcat 8.x or greater)
// This is to get around the resources with content lengths greater than Integer.MAX_VALUE which was limiting
// our ability to serve large files
response.setContentLengthLong(resource.contentLength());
// The rest of this is taken directly from the super implementation
if (mediaType != null) {
response.setContentType(mediaType.toString());
}
if (resource instanceof EncodedResource) {
response.setHeader(HttpHeaders.CONTENT_ENCODING, ((EncodedResource) resource).getContentEncoding());
}
if (resource instanceof VersionedResource) {
response.setHeader(HttpHeaders.ETAG, "\"" + ((VersionedResource) resource).getVersion() + "\"");
}
response.setHeader(HttpHeaders.ACCEPT_RANGES, BYTES);
}
}