ResourceLoaderAdaptor.java revision 56ed4167b942ec265f9cee70ac4d71d10b3835ce
19648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov/*
29648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov * Copyright (C) 2010 Google Inc.
39648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov *
49648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov * Licensed under the Apache License, Version 2.0 (the "License");
59648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov * you may not use this file except in compliance with the License.
69648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov * You may obtain a copy of the License at
79648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov *
89648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov * http://www.apache.org/licenses/LICENSE-2.0
99648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov *
109648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov * Unless required by applicable law or agreed to in writing, software
119648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov * distributed under the License is distributed on an "AS IS" BASIS,
129648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
139648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov * See the License for the specific language governing permissions and
149648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov * limitations under the License.
159648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov */
169648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov
179648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganovpackage com.google.clearsilver.jsilver.adaptor;
189648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov
199648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganovimport com.google.clearsilver.jsilver.exceptions.JSilverTemplateNotFoundException;
209648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganovimport com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
219648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov
229648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganovimport org.clearsilver.CSFileLoader;
239648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganovimport org.clearsilver.CSUtil;
249648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov
259648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganovimport java.io.File;
269648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganovimport java.io.FileInputStream;
279648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganovimport java.io.FileNotFoundException;
289648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganovimport java.io.IOException;
299648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganovimport java.io.InputStreamReader;
309648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganovimport java.io.Reader;
319648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganovimport java.io.StringReader;
329648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganovimport java.util.List;
339648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov
349648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov/**
359648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov * Wrap a CSFileLoader with a ResourceLoader
369648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov */
379648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganovpublic class ResourceLoaderAdaptor implements ResourceLoader {
389648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov
399648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov  private final JHdf hdf;
409648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov  private final LoadPathToFileCache loadPathCache;
419648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov  private final CSFileLoader csFileLoader;
429648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov  private List<String> loadPaths;
439648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov
449648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov  ResourceLoaderAdaptor(JHdf hdf, LoadPathToFileCache loadPathCache, CSFileLoader csFileLoader) {
459648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov    this.hdf = hdf;
469648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov    this.loadPathCache = loadPathCache;
479648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov    this.csFileLoader = csFileLoader;
489648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov  }
499648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov
509648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov  @Override
519648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov  public Reader open(String name) throws IOException {
529648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov    if (csFileLoader != null) {
539648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov      if (hdf.getData() == null) {
549648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov        throw new IllegalStateException("HDF is already closed");
559648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov      }
569648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov      return new StringReader(csFileLoader.load(hdf, name));
579648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov    } else {
589648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov      File file = locateFile(name);
599648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov      if (file == null) {
609648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov        throw new FileNotFoundException("Could not locate file " + name);
619648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov      }
629648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov      return new InputStreamReader(new FileInputStream(file), "UTF-8");
639648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov    }
649648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov  }
659648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov
669648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov  @Override
679648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov  public Reader openOrFail(String name) throws JSilverTemplateNotFoundException, IOException {
689648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov    Reader reader = open(name);
699648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov    if (reader == null) {
709648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov      final StringBuffer text = new StringBuffer();
719648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov      text.append("No file '");
729648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov      text.append(name);
739648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov      text.append("' ");
749648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov      if (loadPaths == null || loadPaths.isEmpty()) {
759648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov        text.append("with no load paths");
769648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov      } else if (loadPaths.size() == 1) {
779648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov        text.append("inside directory '");
789648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov        text.append(loadPaths.get(0));
799648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov        text.append("'");
809648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov      } else {
819648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov        text.append("inside directories ( ");
829648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov        for (String path : getLoadPaths()) {
839648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov          text.append("'");
849648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov          text.append(path);
859648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov          text.append("' ");
869648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov        }
879648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov        text.append(")");
889648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov      }
899648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov      throw new JSilverTemplateNotFoundException(text.toString());
909648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov    } else {
919648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov      return reader;
929648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov    }
939648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov  }
949648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov
959648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov  /**
969648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov   *
979648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov   * @param name name of the file to locate.
989648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov   * @return a File object corresponding to the existing file or {@code null} if it does not exist.
999648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov   */
1009648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov  File locateFile(String name) {
1019648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov    if (name.startsWith(File.separator)) {
1029648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov      // Full path to file was given.
1039648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov      File file = newFile(name);
1049648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov      return file.exists() ? file : null;
1059648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov    }
1069648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov    File file = null;
1079648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov    // loadPathCache is null when load path caching is disabled at the
1089648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov    // JSilverFactory level. This is implied by setting cache size
1099648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov    // to 0 using JSilverOptions.setLoadPathCacheSize(0).
1109648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov    if (loadPathCache != null) {
1119648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov      String filePath = loadPathCache.lookup(getLoadPaths(), name);
1129648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov      if (filePath != null) {
1139648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov        file = newFile(filePath);
1149648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov        return file.exists() ? file : null;
1159648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov      }
1169648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov    }
1179648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov
1189648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov    file = locateFile(getLoadPaths(), name);
1199648c538bac4f04145c118cc41168d1d7a536312Svetoslav Ganov    if (file != null && loadPathCache != null) {
120      loadPathCache.add(getLoadPaths(), name, file.getAbsolutePath());
121    }
122    return file;
123  }
124
125  /**
126   * Given an ordered list of directories to look in, locate the specified file. Returns
127   * <code>null</code> if file not found.
128   * <p>
129   * This is copied from {@link org.clearsilver.CSUtil#locateFile(java.util.List, String)} but has
130   * one important difference. It calls our subclassable newFile method.
131   *
132   * @param loadPaths the ordered list of paths to search.
133   * @param filename the name of the file.
134   * @return a File object corresponding to the file. <code>null</code> if file not found.
135   */
136  File locateFile(List<String> loadPaths, String filename) {
137    if (filename == null) {
138      throw new NullPointerException("No filename provided");
139    }
140    if (loadPaths == null) {
141      throw new NullPointerException("No loadpaths provided.");
142    }
143    for (String path : loadPaths) {
144      File file = newFile(path, filename);
145      if (file.exists()) {
146        return file;
147      }
148    }
149    return null;
150  }
151
152  /**
153   * Separate methods to allow tests to subclass and override File creation and return mocks or
154   * fakes.
155   */
156  File newFile(String filename) {
157    return new File(filename);
158  }
159
160  File newFile(String path, String filename) {
161    return new File(path, filename);
162  }
163
164  @Override
165  public void close(Reader reader) throws IOException {
166    reader.close();
167  }
168
169  @Override
170  public Object getKey(String filename) {
171    if (filename.startsWith(File.separator)) {
172      return filename;
173    } else {
174      File file = locateFile(filename);
175      if (file == null) {
176        // The file does not exist, use the full loadpath and file name as the
177        // key.
178        return LoadPathToFileCache.makeCacheKey(getLoadPaths(), filename);
179      } else {
180        return file.getAbsolutePath();
181      }
182    }
183  }
184
185  /**
186   * Some applications, e.g. online help, need to know when a file has changed due to a symlink
187   * modification hence the use of {@link File#getCanonicalFile()}, if possible.
188   */
189  @Override
190  public Object getResourceVersionId(String filename) {
191    File file = locateFile(filename);
192    if (file == null) {
193      return null;
194    }
195
196    String fullPath;
197    try {
198      fullPath = file.getCanonicalPath();
199    } catch (IOException e) {
200      fullPath = file.getAbsolutePath();
201    }
202    return String.format("%s@%s", fullPath, file.lastModified());
203  }
204
205  final CSFileLoader getCSFileLoader() {
206    return csFileLoader;
207  }
208
209  private synchronized List<String> getLoadPaths() {
210    if (loadPaths == null) {
211      if (hdf.getData() == null) {
212        throw new IllegalStateException("HDF is already closed");
213      }
214      loadPaths = CSUtil.getLoadPaths(hdf, true);
215    }
216    return loadPaths;
217  }
218}
219