All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.shindig.gadgets.features.FeatureResourceLoader Maven / Gradle / Ivy

Go to download

Renders gadgets, provides the gadget metadata service, and serves all javascript required by the OpenSocial specification.

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
package org.apache.shindig.gadgets.features;

import com.google.common.base.Preconditions;
import com.google.inject.Inject;
import com.google.inject.name.Named;

import org.apache.shindig.common.logging.i18n.MessageKeys;
import org.apache.shindig.common.uri.Uri;
import org.apache.shindig.common.util.TimeSource;
import org.apache.shindig.gadgets.GadgetException;
import org.apache.shindig.gadgets.http.HttpFetcher;
import org.apache.shindig.gadgets.http.HttpRequest;
import org.apache.shindig.gadgets.http.HttpResponse;

import java.io.IOException;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Class that loads FeatureResource objects used to populate JS feature code.
 */
public class FeatureResourceLoader {

  // Class name for logging purpose
  private static final String classname = FeatureResourceLoader.class.getName();
  private static final Logger LOG = Logger.getLogger(classname, MessageKeys.MESSAGES);

  private final HttpFetcher fetcher;
  private final TimeSource timeSource;
  private final FeatureFileSystem fileSystem;
  private int updateCheckFrequency = 0;  // <= 0 -> only load data once, don't check for updates.

  @Inject
  public FeatureResourceLoader(
      HttpFetcher fetcher, TimeSource timeSource, FeatureFileSystem fileSystem) {
    this.fetcher = fetcher;
    this.timeSource = timeSource;
    this.fileSystem = fileSystem;
  }

  @Inject(optional = true)
  public void setSupportFileUpdates(
      @Named("shindig.features.loader.file-update-check-frequency-ms") int updateCheckFrequency) {
    this.updateCheckFrequency = updateCheckFrequency;
  }

  /**
   * Primary, and only public, method of FeatureResourceLoader. Loads the resource
   * keyed at the given {@code uri}, which was decorated with the provided list of attributes.
   *
   * The default implementation loads both file and res-schema resources using
   * ResourceLoader, attempting to load optimized content for files named [file].js as [file].opt.js.
   *
   * Override this method to provide custom functionality. Basic loadFile, loadResource, and loadUri
   * methods are kept protected for easy reuse.
   *
   * @param uri Uri of resource to be loaded.
   * @param attribs Attributes decorating the resource in the corresponding feature.xml
   * @return FeatureResource object providing content and debugContent loading capability.
   * @throws GadgetException If any failure occurs during this process.
   */
  public FeatureResource load(Uri uri, Map attribs) throws GadgetException {
    try {
      if ("file".equals(uri.getScheme())) {
        return loadFile(uri.getPath(), attribs);
      } else if ("res".equals(uri.getScheme())) {
        return loadResource(uri.getPath(), attribs);
      }
      return loadUri(uri, attribs);
    } catch (IOException e) {
      throw new GadgetException(GadgetException.Code.FAILED_TO_RETRIEVE_CONTENT, e);
    }
  }

  @SuppressWarnings("unused")
  protected FeatureResource loadFile(String path, Map attribs) throws IOException {
    return new DualModeFileResource(getOptPath(path), path, attribs);
  }

  protected String getFileContent(FeatureFile file) {
    try {
      return file.getContent();
    } catch (IOException e) {
      // This is fine; errors happen downstream.
      return null;
    }
  }

  @SuppressWarnings("unused")
  protected FeatureResource loadResource(
      String path, Map attribs) throws IOException {
    String optContent = null, debugContent = null;
    try {
      optContent = getResourceContent(getOptPath(path));
    } catch (IOException e) {
      // OK - optContent can be null. Error thrown downstream if both are null.
    }
    try {
      debugContent = getResourceContent(path);
    } catch (IOException e) {
      // See above; OK for debugContent to be null.
    }
    return new DualModeStaticResource(path, optContent, debugContent, attribs);
  }

  public String getResourceContent(String resource) throws IOException {
    return fileSystem.getResourceContent(resource);
  }

  /**
   * @throws IOException if failed to load uri (by derived classes)
   */
  protected FeatureResource loadUri(Uri uri, Map attribs) throws IOException {
    String inline = attribs.get("inline");
    inline = inline != null ? inline : "";
    return new UriResource(fetcher, uri,
        "1".equals(inline) || "true".equalsIgnoreCase(inline),
        attribs);
  }

  protected String getOptPath(String orig) {
    if (orig.endsWith(".js") && !orig.endsWith(".opt.js")) {
      return orig.substring(0, orig.length() - 3) + ".opt.js";
    }
    return orig;
  }

  // Overridable for easier testing.
  protected boolean fileHasChanged(FeatureFile file, long lastModified) {
    return file.lastModified() > lastModified;
  }

  private class DualModeFileResource extends FeatureResource.Attribute {
    private final FileContent optContent;
    private final FileContent dbgContent;
    private final String fileName;

    protected DualModeFileResource(String optFilePath, String dbgFilePath,
        Map attribs) {
      super(attribs);
      this.optContent = new FileContent(optFilePath);
      this.dbgContent = new FileContent(dbgFilePath);
      this.fileName = dbgFilePath;
      Preconditions.checkArgument(optContent.get() != null || dbgContent.get() != null,
        "Problems reading resource: %s", dbgFilePath);
    }

    public String getContent() {
      String opt = optContent.get();
      return opt != null ? opt : dbgContent.get();
    }

    public String getDebugContent() {
      String dbg = dbgContent.get();
      return dbg != null ? dbg : optContent.get();
    }

    public String getName() {
      return fileName;
    }

    private final class FileContent {
      private final String filePath;
      private long lastModified;
      private long lastUpdateCheckTime;
      private String content;

      private FileContent(String filePath) {
        this.filePath = filePath;
        this.lastModified = 0;
        this.lastUpdateCheckTime = 0;
      }

      private String get() {
        long nowTime = timeSource.currentTimeMillis();
        if (content == null ||
            (updateCheckFrequency > 0 &&
             (lastUpdateCheckTime + updateCheckFrequency) < nowTime)) {
          // Only check for file updates at preconfigured intervals. This prevents
          // overwhelming the file system while maintaining a reasonable update rate w/o
          // implementing a full event-driven mechanism.
          lastUpdateCheckTime = nowTime;
          FeatureFile file;
          try {
            file = fileSystem.getFile(filePath);
          } catch (IOException e) {
            return null;
          }
          if (fileHasChanged(file, lastModified)) {
            // Only reload file content if it's changed (or if it's the first
            // load, when this check will succeed).
            String newContent = getFileContent(file);
            if (newContent != null) {
              content = newContent;
              lastModified = file.lastModified();
            } else if (content != null) {
              // Content existed before, file removed - log error.
              if (LOG.isLoggable(Level.WARNING)) {
                LOG.logp(Level.WARNING, classname, "get", MessageKeys.MISSING_FILE,
                    new Object[] {filePath});
              }
            }
          }
        }
        return content;
      }
    }
  }

  private static final class DualModeStaticResource extends FeatureResource.Attribute {
    private final String content;
    private final String debugContent;
    private final String path;

    private DualModeStaticResource(
        String path, String content, String debugContent, Map attribs) {
      super(attribs);
      this.content = content != null ? content : debugContent;
      this.debugContent = debugContent != null ? debugContent : content;
      this.path = path;
      Preconditions.checkArgument(this.content != null, "Problems reading resource: %s", path);
    }

    public String getContent() {
      return content;
    }

    public String getDebugContent() {
      return debugContent;
    }

    public String getName() {
      return path;
    }
  }

  private static final class UriResource extends FeatureResource.Attribute {
    private final HttpFetcher fetcher;
    private final Uri uri;
    private final boolean isInline;
    private String content;
    private long lastLoadTryMs;

    private UriResource(HttpFetcher fetcher, Uri uri, boolean isInline,
        Map attribs) {
      super(attribs);
      this.fetcher = fetcher;
      this.uri = uri;
      this.isInline = isInline;
      this.lastLoadTryMs = 0;
      this.content = getContent();
    }

    public String getContent() {
      if (isExternal()) {
        return uri.toString();
      } else if (content != null) {
        // Variable content is a one-time content cache for inline JS features.
        return content;
      }

      // Try to load the content. Ideally, and most of the time, this
      // will happen immediately at startup. However, if the target server is
      // down it shouldn't hose the entire server, so in that case we defer
      // and try at most once per minute thereafter, the delay in place to
      // avoid overwhelming a server down on its heels.
      long now = System.currentTimeMillis();
      if (fetcher != null && now > (lastLoadTryMs + (60 * 1000))) {
        lastLoadTryMs = now;
        try {
          HttpRequest request = new HttpRequest(uri);
          HttpResponse response = fetcher.fetch(request);
          if (response.getHttpStatusCode() == HttpResponse.SC_OK) {
            content = response.getResponseAsString();
          } else {
            if (LOG.isLoggable(Level.WARNING)) {
              LOG.logp(Level.WARNING, classname, "getContent", MessageKeys.UNABLE_RETRIEVE_LIB,
                  new Object[] {uri});
            }
          }
        } catch (GadgetException e) {
          if (LOG.isLoggable(Level.WARNING)) {
            LOG.logp(Level.WARNING, classname, "getContent", MessageKeys.UNABLE_RETRIEVE_LIB,
                new Object[] {uri});
          }
        }
      }

      return content;
    }

    public String getDebugContent() {
      return getContent();
    }

    @Override
    public boolean isExternal() {
      return !isInline;
    }

    @Override
    public boolean isProxyCacheable() {
      return content != null;
    }

    public String getName() {
      return uri.toString();
    }

  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy