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.collections.JsonMap.*;
020import static org.apache.juneau.http.HttpHeaders.*;
021import static org.apache.juneau.http.HttpResources.*;
022import static org.apache.juneau.internal.FileUtils.*;
023
024import java.io.*;
025import java.util.*;
026
027import org.apache.http.*;
028import org.apache.juneau.common.utils.*;
029import org.apache.juneau.cp.*;
030import org.apache.juneau.http.resource.*;
031import org.apache.juneau.http.response.*;
032import org.apache.juneau.internal.*;
033import org.apache.juneau.rest.*;
034
035import jakarta.activation.*;
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 */
048public class BasicStaticFiles implements StaticFiles {
049
050   private final Header[] headers;
051   private final MimetypesFileTypeMap mimeTypes;
052   private final int hashCode;
053   private final FileFinder fileFinder;
054
055   /**
056    * Creates a new builder for this object.
057    *
058    * @param beanStore The bean store to use for creating beans.
059    * @return A new builder for this object.
060    */
061   public static StaticFiles.Builder create(BeanStore beanStore) {
062      return new StaticFiles.Builder(beanStore);
063   }
064
065   /**
066    * Constructor.
067    *
068    * @param beanStore The bean store containing injectable beans for this logger.
069    */
070   public BasicStaticFiles(BeanStore beanStore) {
071      this(StaticFiles
072         .create(beanStore)
073         .type(BasicStaticFiles.class)
074         .dir("static")
075         .dir("htdocs")
076         .cp(beanStore.getBean(ResourceSupplier.class).get().getResourceClass(), "htdocs", true)
077         .cp(beanStore.getBean(ResourceSupplier.class).get().getResourceClass(), "/htdocs", true)
078         .caching(1_000_000)
079         .exclude("(?i).*\\.(class|properties)")
080         .headers(cacheControl("max-age=86400, public"))
081      );
082   }
083
084   /**
085    * Constructor.
086    *
087    * @param builder The builder object.
088    */
089   public BasicStaticFiles(StaticFiles.Builder builder) {
090      this.headers = builder.headers.toArray(new Header[builder.headers.size()]);
091      this.mimeTypes = builder.mimeTypes;
092      this.hashCode = HashCode.of(hashCode(), headers);
093      this.fileFinder = builder.fileFinder.build();
094   }
095
096   /**
097    * Constructor.
098    *
099    * <p>
100    * Can be used when subclassing and overriding the {@link #resolve(String, Locale)} method.
101    */
102   protected BasicStaticFiles() {
103      this.headers = new Header[0];
104      this.mimeTypes = null;
105      this.hashCode = HashCode.of(hashCode(), headers);
106      this.fileFinder = null;
107   }
108
109   /**
110    * Resolve the specified path.
111    *
112    * <p>
113    * Subclasses can override this method to provide specialized handling.
114    *
115    * @param path The path to resolve to a static file.
116    * @param locale Optional locale.
117    * @return The resource, or <jk>null</jk> if not found.
118    */
119   @Override /* StaticFiles */
120   public Optional<HttpResource> resolve(String path, Locale locale) {
121      try {
122         Optional<InputStream> is = getStream(path, locale);
123         if (! is.isPresent())
124            return Utils.opte();
125         return Utils.opt(streamResource(is.get())
126         .setHeaders(contentType(mimeTypes == null ? null : mimeTypes.getContentType(getFileName(path))))
127         .addHeaders(headers));
128      } catch (IOException e) {
129         throw new InternalServerError(e);
130      }
131   }
132
133   @Override
134   public int hashCode() {
135      return hashCode;
136   }
137
138   @Override /* Object */
139   public boolean equals(Object o) {
140      return super.equals(o) && o instanceof BasicStaticFiles && Utils.eq(this, (BasicStaticFiles)o, (x,y)->Utils.eq(x.headers, y.headers));
141   }
142
143   @Override /* FileFinder */
144   public Optional<InputStream> getStream(String name, Locale locale) throws IOException {
145      return fileFinder.getStream(name, locale);
146   }
147
148   @Override /* FileFinder */
149   public Optional<String> getString(String name, Locale locale) throws IOException {
150      return fileFinder.getString(name, locale);
151   }
152
153   @Override /* Object */
154   public String toString() {
155      return filteredMap()
156         .append("headers", headers)
157         .asReadableString();
158   }
159}