001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.rest.server.staticfile; 018 019import static org.apache.juneau.commons.utils.CollectionUtils.*; 020import static org.apache.juneau.commons.utils.FileUtils.*; 021import static org.apache.juneau.commons.utils.Shorts.*; 022 023import java.io.*; 024import java.util.*; 025 026import org.apache.juneau.commons.collections.*; 027import org.apache.juneau.commons.inject.*; 028import org.apache.juneau.commons.io.*; 029import org.apache.juneau.http.*; 030import org.apache.juneau.http.entity.*; 031import org.apache.juneau.http.header.*; 032import org.apache.juneau.http.resource.*; 033import org.apache.juneau.http.response.*; 034import org.apache.juneau.marshall.cp.*; 035import org.apache.juneau.rest.server.*; 036 037/** 038 * API for retrieving localized static files from either the classpath or file system. 039 * 040 * <p> 041 * Provides the same functionality as {@link BasicFileFinder} but adds support for returning files as {@link HttpResource} 042 * objects with arbitrary headers. 043 * 044 * <h5 class='section'>See Also:</h5><ul> 045 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/StaticFiles">Static files</a> 046 * </ul> 047 */ 048@SuppressWarnings({ 049 "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants 050 "resource" // Resource management handled externally 051}) 052public class BasicStaticFiles implements StaticFiles { 053 054 /** 055 * Creates a new builder for this object. 056 * 057 * @param beanStore The bean store to use for creating beans. 058 * @return A new builder for this object. 059 */ 060 public static StaticFiles.Builder create(BeanStore beanStore) { 061 return new StaticFiles.Builder(beanStore); 062 } 063 064 private final HttpHeader[] headers; 065 private final MimeTypeDetector mimeTypes; 066 private final int hashCode; 067 068 private final FileFinder fileFinder; 069 070 /** 071 * Constructor. 072 * 073 * @param beanStore The bean store containing injectable beans for this static files object. Must not be <jk>null</jk>. 074 */ 075 public BasicStaticFiles(BeanStore beanStore) { 076 // @formatter:off 077 this(StaticFiles 078 .create(beanStore) 079 .dir("static") 080 .dir("htdocs") 081 .cp(beanStore.getBean(ResourceSupplier.class).orElseThrow(() -> new IllegalStateException("ResourceSupplier not found")).getResourceClass(), "htdocs", true) 082 .cp(beanStore.getBean(ResourceSupplier.class).orElseThrow(() -> new IllegalStateException("ResourceSupplier not found")).getResourceClass(), "/htdocs", true) 083 .caching(1_000_000) 084 .exclude("(?i).*\\.(class|properties)") 085 .headers(CacheControl.of("max-age=86400, public")) 086 ); 087 // @formatter:on 088 } 089 090 /** 091 * Constructor. 092 * 093 * @param builder The builder object. Must not be <jk>null</jk>. 094 */ 095 public BasicStaticFiles(StaticFiles.Builder builder) { 096 this.headers = builder.headers.toArray(new HttpHeader[builder.headers.size()]); 097 this.mimeTypes = builder.mimeTypes; 098 this.hashCode = h(hashCode(), headers); 099 this.fileFinder = builder.fileFinder.build(); 100 } 101 102 /** 103 * Constructor. 104 * 105 * <p> 106 * Can be used when subclassing and overriding the {@link #resolve(String, Locale)} method. 107 */ 108 protected BasicStaticFiles() { 109 this.headers = new HttpHeader[0]; 110 this.mimeTypes = null; 111 this.hashCode = h(hashCode(), headers); 112 this.fileFinder = null; 113 } 114 115 @Override /* Overridden from Object */ 116 public boolean equals(Object o) { 117 return this == o || (o instanceof BasicStaticFiles o2 && eq(this, o2, (x, y) -> Arrays.equals(x.headers, y.headers))); 118 } 119 120 @Override /* Overridden from FileFinder */ 121 public Optional<InputStream> getStream(String name, Locale locale) throws IOException { 122 return fileFinder.getStream(name, locale); 123 } 124 125 @Override /* Overridden from FileFinder */ 126 public Optional<String> getString(String name, Locale locale) throws IOException { 127 return fileFinder.getString(name, locale); 128 } 129 130 @Override 131 public int hashCode() { 132 return hashCode; 133 } 134 135 /** 136 * Resolve the specified path. 137 * 138 * <p> 139 * Subclasses can override this method to provide specialized handling. 140 * 141 * @param path The path to resolve to a static file. 142 * @param locale Optional locale. 143 * @return The resource, or an empty {@link Optional} if not found. 144 */ 145 @Override /* Overridden from StaticFiles */ 146 public Optional<HttpResource> resolve(String path, Locale locale) { 147 try { 148 Optional<InputStream> is = getStream(path, locale); 149 if (! is.isPresent()) 150 return oe(); 151 var ct = mimeTypes == null ? null : mimeTypes.getContentType(getFileName(path)); 152 var hdrs = new ArrayList<HttpHeader>(); 153 if (ct != null) 154 hdrs.add(ContentType.of(ct)); 155 Arrays.stream(headers).filter(Objects::nonNull).forEach(hdrs::add); 156 return o(HttpResourceBean.of(StreamBody.of(is.get()), hdrs)); 157 } catch (IOException e) { 158 throw new InternalServerError(e); 159 } 160 } 161 162 protected FluentMap<String,Object> properties() { 163 // @formatter:off 164 return filteredBeanPropertyMap() 165 .a("headers", headers); 166 // @formatter:on 167 } 168 169 @Override /* Overridden from Object */ 170 public String toString() { 171 return r(properties()); 172 } 173}