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.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.Utils.*;
022import static org.apache.juneau.http.HttpHeaders.*;
023import static org.apache.juneau.http.HttpResources.*;
024
025import java.io.*;
026import java.util.*;
027
028import org.apache.http.*;
029import org.apache.juneau.commons.collections.*;
030import org.apache.juneau.commons.io.*;
031import org.apache.juneau.cp.*;
032import org.apache.juneau.http.resource.*;
033import org.apache.juneau.http.response.*;
034import org.apache.juneau.rest.*;
035
036/**
037 * API for retrieving localized static files from either the classpath or file system.
038 *
039 * <p>
040 * Provides the same functionality as {@link BasicFileFinder} but adds support for returning files as {@link HttpResource}
041 * objects with arbitrary headers.
042 *
043 * <h5 class='section'>See Also:</h5><ul>
044 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/StaticFiles">Static files</a>
045 * </ul>
046 */
047@SuppressWarnings("resource")
048public class BasicStaticFiles implements StaticFiles {
049
050   /**
051    * Creates a new builder for this object.
052    *
053    * @param beanStore The bean store to use for creating beans.
054    * @return A new builder for this object.
055    */
056   public static StaticFiles.Builder create(BeanStore beanStore) {
057      return new StaticFiles.Builder(beanStore);
058   }
059
060   private final Header[] headers;
061   private final MimeTypeDetector mimeTypes;
062   private final int hashCode;
063
064   private final FileFinder fileFinder;
065
066   /**
067    * Constructor.
068    *
069    * @param beanStore The bean store containing injectable beans for this logger.
070    */
071   public BasicStaticFiles(BeanStore beanStore) {
072      // @formatter:off
073      this(StaticFiles
074         .create(beanStore)
075         .type(BasicStaticFiles.class)
076         .dir("static")
077         .dir("htdocs")
078         .cp(beanStore.getBean(ResourceSupplier.class).get().getResourceClass(), "htdocs", true)
079         .cp(beanStore.getBean(ResourceSupplier.class).get().getResourceClass(), "/htdocs", true)
080         .caching(1_000_000)
081         .exclude("(?i).*\\.(class|properties)")
082         .headers(cacheControl("max-age=86400, public"))
083      );
084      // @formatter:on
085   }
086
087   /**
088    * Constructor.
089    *
090    * @param builder The builder object.
091    */
092   public BasicStaticFiles(StaticFiles.Builder builder) {
093      this.headers = builder.headers.toArray(new Header[builder.headers.size()]);
094      this.mimeTypes = builder.mimeTypes;
095      this.hashCode = h(hashCode(), headers);
096      this.fileFinder = builder.fileFinder.build();
097   }
098
099   /**
100    * Constructor.
101    *
102    * <p>
103    * Can be used when subclassing and overriding the {@link #resolve(String, Locale)} method.
104    */
105   protected BasicStaticFiles() {
106      this.headers = new Header[0];
107      this.mimeTypes = null;
108      this.hashCode = h(hashCode(), headers);
109      this.fileFinder = null;
110   }
111
112   @Override /* Overridden from Object */
113   public boolean equals(Object o) {
114      return super.equals(o) && o instanceof BasicStaticFiles o2 && eq(this, o2, (x, y) -> eq(x.headers, y.headers));
115   }
116
117   @Override /* Overridden from FileFinder */
118   public Optional<InputStream> getStream(String name, Locale locale) throws IOException {
119      return fileFinder.getStream(name, locale);
120   }
121
122   @Override /* Overridden from FileFinder */
123   public Optional<String> getString(String name, Locale locale) throws IOException {
124      return fileFinder.getString(name, locale);
125   }
126
127   @Override
128   public int hashCode() {
129      return hashCode;
130   }
131
132   /**
133    * Resolve the specified path.
134    *
135    * <p>
136    * Subclasses can override this method to provide specialized handling.
137    *
138    * @param path The path to resolve to a static file.
139    * @param locale Optional locale.
140    * @return The resource, or <jk>null</jk> if not found.
141    */
142   @Override /* Overridden from StaticFiles */
143   public Optional<HttpResource> resolve(String path, Locale locale) {
144      try {
145         Optional<InputStream> is = getStream(path, locale);
146         if (! is.isPresent())
147            return opte();
148         return opt(streamResource(is.get()).setHeaders(contentType(mimeTypes == null ? null : mimeTypes.getContentType(getFileName(path)))).addHeaders(headers));
149      } catch (IOException e) {
150         throw new InternalServerError(e);
151      }
152   }
153
154   protected FluentMap<String,Object> properties() {
155      // @formatter:off
156      return filteredBeanPropertyMap()
157         .a("headers", headers);
158      // @formatter:on
159   }
160
161   @Override /* Overridden from Object */
162   public String toString() {
163      return r(properties());
164   }
165}