001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.rest.staticfile;
014
015import static org.apache.juneau.collections.JsonMap.*;
016import static org.apache.juneau.http.HttpHeaders.*;
017import static org.apache.juneau.http.HttpResources.*;
018import static org.apache.juneau.internal.CollectionUtils.*;
019import static org.apache.juneau.internal.FileUtils.*;
020import static org.apache.juneau.internal.ObjectUtils.*;
021
022import java.io.*;
023import java.util.*;
024
025import javax.activation.*;
026
027import org.apache.http.*;
028import org.apache.juneau.cp.*;
029import org.apache.juneau.http.resource.*;
030import org.apache.juneau.http.response.*;
031import org.apache.juneau.internal.*;
032import org.apache.juneau.rest.*;
033
034/**
035 * API for retrieving localized static files from either the classpath or file system.
036 *
037 * <p>
038 * Provides the same functionality as {@link BasicFileFinder} but adds support for returning files as {@link HttpResource}
039 * objects with arbitrary headers.
040 *
041 * <h5 class='section'>See Also:</h5><ul>
042 *    <li class='link'><a class="doclink" href="../../../../../index.html#jrs.StaticFiles">Static files</a>
043 * </ul>
044 */
045public class BasicStaticFiles implements StaticFiles {
046
047   private final Header[] headers;
048   private final MimetypesFileTypeMap mimeTypes;
049   private final int hashCode;
050   private final FileFinder fileFinder;
051
052   /**
053    * Creates a new builder for this object.
054    *
055    * @param beanStore The bean store to use for creating beans.
056    * @return A new builder for this object.
057    */
058   public static StaticFiles.Builder create(BeanStore beanStore) {
059      return new StaticFiles.Builder(beanStore);
060   }
061
062   /**
063    * Constructor.
064    *
065    * @param beanStore The bean store containing injectable beans for this logger.
066    */
067   public BasicStaticFiles(BeanStore beanStore) {
068      this(StaticFiles
069         .create(beanStore)
070         .type(BasicStaticFiles.class)
071         .dir("static")
072         .dir("htdocs")
073         .cp(beanStore.getBean(ResourceSupplier.class).get().getResourceClass(), "htdocs", true)
074         .cp(beanStore.getBean(ResourceSupplier.class).get().getResourceClass(), "/htdocs", true)
075         .caching(1_000_000)
076         .exclude("(?i).*\\.(class|properties)")
077         .headers(cacheControl("max-age=86400, public"))
078      );
079   }
080
081   /**
082    * Constructor.
083    *
084    * @param builder The builder object.
085    */
086   public BasicStaticFiles(StaticFiles.Builder builder) {
087      this.headers = builder.headers.toArray(new Header[builder.headers.size()]);
088      this.mimeTypes = builder.mimeTypes;
089      this.hashCode = HashCode.of(hashCode(), headers);
090      this.fileFinder = builder.fileFinder.build();
091   }
092
093   /**
094    * Constructor.
095    *
096    * <p>
097    * Can be used when subclassing and overriding the {@link #resolve(String, Locale)} method.
098    */
099   protected BasicStaticFiles() {
100      super();
101      this.headers = new Header[0];
102      this.mimeTypes = null;
103      this.hashCode = HashCode.of(hashCode(), headers);
104      this.fileFinder = null;
105   }
106
107   /**
108    * Resolve the specified path.
109    *
110    * <p>
111    * Subclasses can override this method to provide specialized handling.
112    *
113    * @param path The path to resolve to a static file.
114    * @param locale Optional locale.
115    * @return The resource, or <jk>null</jk> if not found.
116    */
117   @Override /* StaticFiles */
118   public Optional<HttpResource> resolve(String path, Locale locale) {
119      try {
120         Optional<InputStream> is = getStream(path, locale);
121         if (! is.isPresent())
122            return empty();
123         return optional(
124            streamResource(is.get())
125               .setHeaders(contentType(mimeTypes == null ? null : mimeTypes.getContentType(getFileName(path))))
126               .addHeaders(headers)
127         );
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 && eq(this, (BasicStaticFiles)o, (x,y)->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}