Package com.noelios.restlet

Source Code of com.noelios.restlet.Engine

/*
* Copyright 2005-2007 Noelios Consulting.
*
* The contents of this file are subject to the terms of the Common Development
* and Distribution License (the "License"). You may not use this file except in
* compliance with the License.
*
* You can obtain a copy of the license at
* http://www.opensource.org/licenses/cddl1.txt See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each file and
* include the License file at http://www.opensource.org/licenses/cddl1.txt If
* applicable, add the following below this CDDL HEADER, with the fields
* enclosed by brackets "[]" replaced with your own identifying information:
* Portions Copyright [yyyy] [name of copyright owner]
*/

package com.noelios.restlet;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.restlet.Application;
import org.restlet.Client;
import org.restlet.Component;
import org.restlet.Context;
import org.restlet.Directory;
import org.restlet.Server;
import org.restlet.data.CharacterSet;
import org.restlet.data.ClientInfo;
import org.restlet.data.Form;
import org.restlet.data.Language;
import org.restlet.data.MediaType;
import org.restlet.data.Parameter;
import org.restlet.data.Preference;
import org.restlet.data.Protocol;
import org.restlet.data.Request;
import org.restlet.data.Response;
import org.restlet.resource.Representation;
import org.restlet.resource.Resource;
import org.restlet.resource.Variant;
import org.restlet.util.Helper;

import com.noelios.restlet.application.ApplicationHelper;
import com.noelios.restlet.component.ComponentHelper;
import com.noelios.restlet.local.DirectoryResource;
import com.noelios.restlet.util.FormUtils;

/**
* Restlet factory supported by the engine.
*
* @author Jerome Louvel (contact@noelios.com)
*/
public class Engine extends org.restlet.util.Engine {
  /** Obtain a suitable logger. */
  private static Logger logger = Logger.getLogger(Engine.class
      .getCanonicalName());

  /** Complete version. */
  public static final String VERSION = org.restlet.util.Engine.VERSION;

  /** Complete version header. */
  public static final String VERSION_HEADER = "Noelios-Restlet-Engine/"
      + VERSION;

  /**
   * Registers a new Noelios Restlet Engine.
   */
  public static void register() {
    Engine.setInstance(new Engine());
  }

  /** List of available client connectors. */
  private List<ConnectorHelper> registeredClients;

  /** List of available server connectors. */
  private List<ConnectorHelper> registeredServers;

  /**
   * Constructor that will automatically attempt to discover connectors.
   */
  @SuppressWarnings("unchecked")
  public Engine() {
    this(true);
  }

  /**
   * Constructor.
   *
   * @param discoverConnectors
   *            True if connectors should be automatically discovered.
   */
  @SuppressWarnings("unchecked")
  public Engine(boolean discoverConnectors) {
    if (discoverConnectors) {
      // Find the factory class name
      String line = null;
      String provider = null;

      // Find the factory class name
      ClassLoader cl = org.restlet.util.Engine.getClassLoader();
      URL configURL;

      // Register the client connector providers
      try {
        for (Enumeration<URL> configUrls = cl
            .getResources("META-INF/services/com.noelios.restlet.ClientHelper"); configUrls
            .hasMoreElements();) {
          configURL = configUrls.nextElement();

          BufferedReader reader = null;
          try {
            reader = new BufferedReader(new InputStreamReader(
                configURL.openStream(), "utf-8"));
            line = reader.readLine();

            while (line != null) {
              provider = getProviderClassName(line);

              if ((provider != null) && (!provider.equals(""))) {
                // Instantiate the factory
                try {
                  Class<? extends ConnectorHelper> providerClass = (Class<? extends ConnectorHelper>) Class
                      .forName(provider);
                  getRegisteredClients().add(
                      providerClass.getConstructor(
                          Client.class).newInstance(
                          (Client) null));
                } catch (Exception e) {
                  logger.log(Level.SEVERE,
                      "Unable to register the client connector "
                          + provider, e);
                }
              }

              line = reader.readLine();
            }
          } catch (IOException e) {
            logger.log(Level.SEVERE,
                "Unable to read the provider descriptor: "
                    + configURL.toString());
          } finally {
            if (reader != null)
              reader.close();
          }
        }
      } catch (IOException ioe) {
        logger
            .log(
                Level.SEVERE,
                "Exception while detecting the client connectors.",
                ioe);
      }

      // Register the server connector providers
      try {
        for (Enumeration<URL> configUrls = cl
            .getResources("META-INF/services/com.noelios.restlet.ServerHelper"); configUrls
            .hasMoreElements();) {
          configURL = configUrls.nextElement();

          BufferedReader reader = null;
          try {
            reader = new BufferedReader(new InputStreamReader(
                configURL.openStream(), "utf-8"));
            line = reader.readLine();

            while (line != null) {
              provider = getProviderClassName(line);

              if ((provider != null) && (!provider.equals(""))) {
                // Instantiate the factory
                try {
                  Class<? extends ConnectorHelper> providerClass = (Class<? extends ConnectorHelper>) Class
                      .forName(provider);
                  getRegisteredServers().add(
                      providerClass.getConstructor(
                          Server.class).newInstance(
                          (Server) null));
                } catch (Exception e) {
                  logger.log(Level.SEVERE,
                      "Unable to register the server connector "
                          + provider, e);
                }
              }

              line = reader.readLine();
            }
          } catch (IOException e) {
            logger.log(Level.SEVERE,
                "Unable to read the provider descriptor: "
                    + configURL.toString());
          } finally {
            if (reader != null)
              reader.close();
          }
        }
      } catch (IOException ioe) {
        logger
            .log(
                Level.SEVERE,
                "Exception while detecting the client connectors.",
                ioe);
      }
    }
  }

  /**
   * Returns the list of available client connectors.
   *
   * @return The list of available client connectors.
   */
  public List<ConnectorHelper> getRegisteredClients() {
    if (this.registeredClients == null)
      this.registeredClients = new ArrayList<ConnectorHelper>();
    return this.registeredClients;
  }

  /**
   * Returns the list of available server connectors.
   *
   * @return The list of available server connectors.
   */
  public List<ConnectorHelper> getRegisteredServers() {
    if (this.registeredServers == null)
      this.registeredServers = new ArrayList<ConnectorHelper>();
    return this.registeredServers;
  }

  /**
   * Creates a directory resource.
   *
   * @param handler
   *            The parent directory handler.
   * @param request
   *            The request to handle.
   * @param response
   *            The response to return.
   * @return A new directory resource.
   * @throws IOException
   */
  public Resource createDirectoryResource(Directory handler, Request request,
      Response response) throws IOException {
    return new DirectoryResource(handler, request, response);
  }

  /**
   * Creates a new helper for a given component.
   *
   * @param application
   *            The application to help.
   * @param parentContext
   *            The parent context, typically the component's context.
   * @return The new helper.
   */
  public Helper createHelper(Application application, Context parentContext) {
    return new ApplicationHelper(application, parentContext);
  }

  /**
   * Creates a new helper for a given client connector.
   *
   * @param client
   *            The client to help.
   * @return The new helper.
   */
  public Helper createHelper(Client client) {
    Helper result = null;

    if (client.getProtocols().size() > 0) {
      ConnectorHelper connector = null;
      for (Iterator<ConnectorHelper> iter = getRegisteredClients()
          .iterator(); (result == null) && iter.hasNext();) {
        connector = iter.next();

        if (connector.getProtocols().containsAll(client.getProtocols())) {
          try {
            result = connector.getClass().getConstructor(
                Client.class).newInstance(client);
          } catch (Exception e) {
            logger
                .log(
                    Level.SEVERE,
                    "Exception while instantiation the client connector.",
                    e);
          }
        }
      }

      if (result == null) {
        // Couldn't find a matching connector
        StringBuilder sb = new StringBuilder();
        sb
            .append("No available client connector supports the required protocols: ");

        for (Protocol p : client.getProtocols()) {
          sb.append(p.getName()).append(" ");
        }

        sb
            .append(". Please add the JAR of a matching connector to your classpath.");

        logger.log(Level.WARNING, sb.toString());
      }
    }

    return result;
  }

  /**
   * Creates a new helper for a given component.
   *
   * @param component
   *            The component to help.
   * @return The new helper.
   */
  public Helper createHelper(Component component) {
    return new ComponentHelper(component);
  }

  /**
   * Creates a new helper for a given server connector.
   *
   * @param server
   *            The server to help.
   * @return The new helper.
   */
  public Helper createHelper(Server server) {
    Helper result = null;

    if (server.getProtocols().size() > 0) {
      ConnectorHelper connector = null;
      for (Iterator<ConnectorHelper> iter = getRegisteredServers()
          .iterator(); (result == null) && iter.hasNext();) {
        connector = iter.next();

        if (connector.getProtocols().containsAll(server.getProtocols())) {
          try {
            result = connector.getClass().getConstructor(
                Server.class).newInstance(server);
          } catch (Exception e) {
            logger
                .log(
                    Level.SEVERE,
                    "Exception while instantiation the server connector.",
                    e);
          }
        }
      }

      if (result == null) {
        // Couldn't find a matching connector
        StringBuilder sb = new StringBuilder();
        sb
            .append("No available server connector supports the required protocols: ");

        for (Protocol p : server.getProtocols()) {
          sb.append(p.getName()).append(" ");
        }

        sb
            .append(". Please add the JAR of a matching connector to your classpath.");

        logger.log(Level.WARNING, sb.toString());
      }
    }

    return result;
  }

  /**
   * Parses the "java.version" system property and returns the first digit of
   * the version number of the Java Runtime Environment (e.g. "1" for
   * "1.3.0").
   *
   * @see <a href="http://java.sun.com/j2se/versioning_naming.html">Official
   *      Java versioning</a>
   * @return The major version number of the Java Runtime Environment.
   */
  public static int getJavaMajorVersion() {
    int result;
    String javaVersion = System.getProperty("java.version");
    try {
      result = Integer.parseInt(javaVersion.substring(0, javaVersion
          .indexOf(".")));
    } catch (Exception e) {
      result = 0;
    }

    return result;
  }

  /**
   * Parses the "java.version" system property and returns the second digit of
   * the version number of the Java Runtime Environment (e.g. "3" for
   * "1.3.0").
   *
   * @see <a href="http://java.sun.com/j2se/versioning_naming.html">Official
   *      Java versioning</a>
   * @return The minor version number of the Java Runtime Environment.
   */
  public static int getJavaMinorVersion() {
    int result;
    String javaVersion = System.getProperty("java.version");
    try {
      result = Integer.parseInt(javaVersion.split("\\.")[1]);
    } catch (Exception e) {
      result = 0;
    }

    return result;
  }

  /**
   * Parses the "java.version" system property and returns the update release
   * number of the Java Runtime Environment (e.g. "10" for "1.3.0_10").
   *
   * @see <a href="http://java.sun.com/j2se/versioning_naming.html">Official
   *      Java versioning</a>
   * @return The release number of the Java Runtime Environment or 0 if it
   *         does not exist.
   */
  public static int getJavaUpdateVersion() {
    int result;
    String javaVersion = System.getProperty("java.version");
    try {
      result = Integer.parseInt(javaVersion.substring(javaVersion
          .indexOf('_') + 1));
    } catch (Exception e) {
      result = 0;
    }

    return result;
  }

  /**
   * Returns the preferred variant representation for a given resource
   * according the the client preferences.
   *
   * @param client
   *            The client preferences.
   * @param variants
   *            The list of variants to compare.
   * @return The preferred variant.
   * @see <a
   *      href="http://httpd.apache.org/docs/2.2/en/content-negotiation.html#algorithm">Apache
   *      content negotiation algorithm</a>
   */
  public Variant getPreferredVariant(ClientInfo client,
      List<Variant> variants, Language defaultLanguage) {
    if (variants == null) {
      return null;
    } else {
      List<Language> variantLanguages = null;
      MediaType variantMediaType = null;

      boolean compatibleLanguage = false;
      boolean compatibleMediaType = false;

      Variant currentVariant = null;
      Variant bestVariant = null;

      Preference<Language> currentLanguagePref = null;
      Preference<Language> bestLanguagePref = null;
      Preference<MediaType> currentMediaTypePref = null;
      Preference<MediaType> bestMediaTypePref = null;

      float bestQuality = 0;
      float bestLanguageScore = 0;
      float bestMediaTypeScore = 0;

      // If no language preference is defined or even none matches, we
      // want to make sure that at least a variant can be returned.
      // Based on experience, it appears that browsers are often
      // misconfigured and don't expose all the languages actually
      // understood by end users.
      // Thus, a few other preferences are added to the user's ones:
      // - primary languages inferred from and sorted according to the
      // user's preferences with quality between 0.005 and 0.006
      // - default language (if any) with quality 0.003
      // - primary language of the default language (if available) with
      // quality 0.002
      // - all languages with quality 0.001
      List<Preference<Language>> languagePrefs = client
          .getAcceptedLanguages();
      List<Preference<Language>> primaryLanguagePrefs = new ArrayList<Preference<Language>>();
      // A default language preference is defined with a better weight
      // than the "All languages" preference
      Preference<Language> defaultLanguagePref = ((defaultLanguage == null) ? null
          : new Preference<Language>(defaultLanguage, 0.003f));
      Preference<Language> allLanguagesPref = new Preference<Language>(
          Language.ALL, 0.001f);

      if (languagePrefs.isEmpty()) {
        // All languages accepted.
        languagePrefs.add(new Preference<Language>(Language.ALL));
      } else {
        // Get the primary language preferences that are not currently
        // accepted by the client
        List<String> list = new ArrayList<String>();
        for (Preference<Language> preference : languagePrefs) {
          Language language = preference.getMetadata();
          if (!language.getSubTags().isEmpty()) {
            if (!list.contains(language.getPrimaryTag())) {
              list.add(language.getPrimaryTag());
              primaryLanguagePrefs
                  .add(new Preference<Language>(new Language(
                      language.getPrimaryTag()),
                      0.005f + (0.001f * preference
                          .getQuality())));
            }
          }
        }
        // If the default language is a "primary" language but is not
        // present in the list of all primary languages, add it.
        if (defaultLanguage != null
            && !defaultLanguage.getSubTags().isEmpty()) {
          if (!list.contains(defaultLanguage.getPrimaryTag())) {
            primaryLanguagePrefs.add(new Preference<Language>(
                new Language(defaultLanguage.getPrimaryTag()),
                0.002f));
          }
        }

      }

      // Client preferences are altered
      languagePrefs.addAll(primaryLanguagePrefs);
      if (defaultLanguagePref != null) {
        languagePrefs.add(defaultLanguagePref);
        // In this case, if the client adds the "all languages"
        // preference, the latter is removed, in order to support the
        // default preference defined by the server
        List<Preference<Language>> list = new ArrayList<Preference<Language>>();
        for (Preference<Language> preference : languagePrefs) {
          Language language = preference.getMetadata();
          if (!language.equals(Language.ALL)) {
            list.add(preference);
          }
        }
        languagePrefs = list;
      }
      languagePrefs.add(allLanguagesPref);

      // For each available variant, we will compute the negotiation score
      // which is dependant on the language score and on the media type
      // score
      for (Iterator<Variant> iter1 = variants.iterator(); iter1.hasNext();) {
        currentVariant = iter1.next();
        variantLanguages = currentVariant.getLanguages();
        variantMediaType = currentVariant.getMediaType();

        // All languages of the current variant are scored.
        for (Language variantLanguage : variantLanguages) {
          // For each language preference defined in the call
          // Calculate the score and remember the best scoring
          // preference
          for (Iterator<Preference<Language>> iter2 = languagePrefs
              .iterator(); (variantLanguage != null)
              && iter2.hasNext();) {
            currentLanguagePref = iter2.next();
            float currentScore = getScore(variantLanguage,
                currentLanguagePref.getMetadata());
            boolean compatiblePref = (currentScore != -1.0f);
            // 3) Do we have a better preference?
            // currentScore *= currentPref.getQuality();
            if (compatiblePref
                && ((bestLanguagePref == null) || (currentScore > bestLanguageScore))) {
              bestLanguagePref = currentLanguagePref;
              bestLanguageScore = currentScore;
            }
          }
        }

        // Are the preferences compatible with the current variant
        // language?
        compatibleLanguage = (variantLanguages.isEmpty())
            || (bestLanguagePref != null);

        // If no media type preference is defined, assume that all media
        // types are acceptable
        List<Preference<MediaType>> mediaTypePrefs = client
            .getAcceptedMediaTypes();
        if (mediaTypePrefs.size() == 0)
          mediaTypePrefs
              .add(new Preference<MediaType>(MediaType.ALL));

        // For each media range preference defined in the call
        // Calculate the score and remember the best scoring preference
        for (Iterator<Preference<MediaType>> iter2 = mediaTypePrefs
            .iterator(); compatibleLanguage && iter2.hasNext();) {
          currentMediaTypePref = iter2.next();
          float currentScore = getScore(variantMediaType,
              currentMediaTypePref.getMetadata());
          boolean compatiblePref = (currentScore != -1.0f);
          // 3) Do we have a better preference?
          // currentScore *= currentPref.getQuality();
          if (compatiblePref
              && ((bestMediaTypePref == null) || (currentScore > bestMediaTypeScore))) {
            bestMediaTypePref = currentMediaTypePref;
            bestMediaTypeScore = currentScore;
          }

        }

        // Are the preferences compatible with the current media type?
        compatibleMediaType = (variantMediaType == null)
            || (bestMediaTypePref != null);

        if (compatibleLanguage && compatibleMediaType) {
          // Do we have a compatible media type?
          float currentQuality = 0;
          if (bestLanguagePref != null) {
            currentQuality += (bestLanguagePref.getQuality() * 10F);
          } else if (!variantLanguages.isEmpty()) {
            currentQuality += 0.1F * 10F;
          }

          if (bestMediaTypePref != null) {
            // So, let's conclude on the current variant, its
            // quality
            currentQuality += bestMediaTypePref.getQuality();
          }

          if (bestVariant == null) {
            bestVariant = currentVariant;
            bestQuality = currentQuality;
          } else if (currentQuality > bestQuality) {
            bestVariant = currentVariant;
            bestQuality = currentQuality;
          }
        }

        // Reset the preference variables
        bestLanguagePref = null;
        bestLanguageScore = 0;
        bestMediaTypePref = null;
        bestMediaTypeScore = 0;
      }

      return bestVariant;
    }
  }

  /**
   * Returns a matching score between 2 Languages
   *
   * @param variantLanguage
   * @param preferenceLanguage
   * @return the positive matching score or -1 if the languages are not
   *         compatible
   */
  private float getScore(Language variantLanguage, Language preferenceLanguage) {
    float score = 0.0f;
    boolean compatibleLang = true;

    // 1) Compare the main tag
    if (variantLanguage.getPrimaryTag().equalsIgnoreCase(
        preferenceLanguage.getPrimaryTag())) {
      score += 100;
    } else if (!preferenceLanguage.getPrimaryTag().equals("*")) {
      compatibleLang = false;
    } else if (!preferenceLanguage.getSubTags().isEmpty()) {
      // Only "*" is an acceptable language range
      compatibleLang = false;
    } else {
      // The valid "*" range has the lowest valid score
      score++;
    }

    if (compatibleLang) {
      // 2) Compare the sub tags
      if ((preferenceLanguage.getSubTags().isEmpty())
          || (variantLanguage.getSubTags().isEmpty())) {
        if (variantLanguage.getSubTags().isEmpty()
            && preferenceLanguage.getSubTags().isEmpty()) {
          score += 10;
        } else {
          // Don't change the score
        }
      } else {
        int maxSize = Math.min(preferenceLanguage.getSubTags().size(),
            variantLanguage.getSubTags().size());
        for (int i = 0; i < maxSize && compatibleLang; i++) {
          if (preferenceLanguage.getSubTags().get(i)
              .equalsIgnoreCase(
                  variantLanguage.getSubTags().get(i))) {
            // Each subtag contribution to the score
            // is getting less and less important
            score += Math.pow(10, 1 - i);
          } else {
            // SubTags are different
            compatibleLang = false;
          }
        }
      }
    }

    return (compatibleLang ? score : -1.0f);
  }

  /**
   * Returns a matching score between 2 Media types
   *
   * @param variantMediaType
   * @param preferenceMediaType
   * @return the positive matching score or -1 if the media types are not
   *         compatible
   */
  private float getScore(MediaType variantMediaType,
      MediaType preferenceMediaType) {
    float score = 0.0f;
    boolean comptabibleMediaType = true;

    // 1) Compare the main types
    if (preferenceMediaType.getMainType().equals(
        variantMediaType.getMainType())) {
      score += 1000;
    } else if (!preferenceMediaType.getMainType().equals("*")) {
      comptabibleMediaType = false;
    } else if (!preferenceMediaType.getSubType().equals("*")) {
      // Ranges such as "*/html" are not supported
      // Only "*/*" is acceptable in this case
      comptabibleMediaType = false;
    }

    if (comptabibleMediaType) {
      // 2) Compare the sub types
      if (variantMediaType.getSubType().equals(
          preferenceMediaType.getSubType())) {
        score += 100;
      } else if (!preferenceMediaType.getSubType().equals("*")) {
        // Subtype are different
        comptabibleMediaType = false;
      }

      if (comptabibleMediaType
          && (variantMediaType.getParameters() != null)) {
        // 3) Compare the parameters
        // If current media type is compatible with the
        // current media range then the parameters need to
        // be checked too
        for (Iterator<Parameter> iter3 = variantMediaType
            .getParameters().iterator(); iter3.hasNext();) {
          Parameter currentParam = iter3.next();

          if (isParameterFound(currentParam, preferenceMediaType)) {
            score++;
          }
        }
      }

    }
    return (comptabibleMediaType ? score : -1.0f);
  }

  /**
   * Parses a line to extract the provider class name.
   *
   * @param line
   *            The line to parse.
   * @return The provider's class name or an empty string.
   */
  private String getProviderClassName(String line) {
    int index = line.indexOf('#');
    if (index != -1)
      line = line.substring(0, index);
    return line.trim();
  }

  /**
   * Indicates if the searched parameter is specified in the given media
   * range.
   *
   * @param searchedParam
   *            The searched parameter.
   * @param mediaRange
   *            The media range to inspect.
   * @return True if the searched parameter is specified in the given media
   *         range.
   */
  private boolean isParameterFound(Parameter searchedParam,
      MediaType mediaRange) {
    boolean result = false;

    for (Iterator<Parameter> iter = mediaRange.getParameters().iterator(); !result
        && iter.hasNext();) {
      result = searchedParam.equals((Parameter) iter.next());
    }

    return result;
  }

  /**
   * Parses an URL encoded Web form.
   *
   * @param logger
   *            The logger to use.
   * @param form
   *            The target form.
   * @param webForm
   *            The posted form.
   */
  public void parse(Logger logger, Form form, Representation webForm) {
    if (webForm != null) {
      FormUtils.parsePost(logger, form, webForm);
    }
  }

  /**
   * Parses an URL encoded query string into a given form.
   *
   * @param logger
   *            The logger to use.
   * @param form
   *            The target form.
   * @param queryString
   *            Query string.
   * @param characterSet
   *            The supported character encoding.
   */
  public void parse(Logger logger, Form form, String queryString,
      CharacterSet characterSet) {
    if ((queryString != null) && !queryString.equals("")) {
      FormUtils.parseQuery(logger, form, queryString, characterSet);
    }
  }

}
TOP

Related Classes of com.noelios.restlet.Engine

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.