/FileExtraction/Java_unseen/dlitz_resin/modules/resin/src/com/caucho/server/webapp/WebAppVersioningController.java
/*
 * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.server.webapp;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;

import com.caucho.config.ConfigException;
import com.caucho.env.deploy.DeployControllerApi;
import com.caucho.env.deploy.DeployControllerType;
import com.caucho.env.deploy.ExpandVersion;
import com.caucho.util.Alarm;
import com.caucho.util.L10N;

/**
 * A configuration entry for a versioning web-app.
 */
public class WebAppVersioningController extends WebAppController {
  private static final L10N L = new L10N(WebAppVersioningController.class);
  private static final Logger log
    = Logger.getLogger(WebAppController.class.getName());

  private static final long EXPIRE_PERIOD = 3600 * 1000L;

  private final String _baseKey;
  
  private long _versionRolloverTime = EXPIRE_PERIOD;

  private ArrayList<WebAppController> _controllerList
    = new ArrayList<WebAppController>();

  private final WebAppExpandDeployGenerator _generator;
  
  private long _restartTime;
  
  private ExpandVersion _primaryVersion;
  private WebAppController _primaryController;
  
  private ArrayList<DeployControllerApi<WebApp>> _mergeList
    = new ArrayList<DeployControllerApi<WebApp>>();
  
  private boolean _isModified = false;
  private AtomicBoolean _isUpdating = new AtomicBoolean();

  public WebAppVersioningController(String id,
                                    String baseKey,
                                    String contextPath,
                                    WebAppExpandDeployGenerator generator,
                                    WebAppContainer container)
  {
    super(id + "-0.0.0.versioning", null, container, contextPath);

    _baseKey = baseKey;
    _generator = generator;
  }

  void setModified(boolean isModified)
  {
    _isModified = isModified;
  }

  @Override
  public boolean isVersioning()
  {
    return true;
  }
  
  @Override
  public DeployControllerType getControllerType()
  {
    return DeployControllerType.VERSIONING;
  }

  @Override
  public String getVersion()
  {
    if (_primaryController != null)
      return _primaryController.getVersion();
    else
      return "";
  }
  
  @Override
  public WebAppAdmin getAdmin()
  {
    if (_primaryController != null)
      return _primaryController.getAdmin();
    else
      return null;
  }

  /**
   * Returns the instance for a top-level request
   *
   * @return the request object or null for none.
   */
  @Override
  public WebApp instantiateDeployInstance()
  {
    updateVersion();
    
    WebAppController controller = _primaryController;

    if (controller != null) {
      WebApp webApp = controller.request();
      
      return webApp;
    }
    else
      throw new NullPointerException(getClass().getName());
  }

  /**
   * Starts the entry.
   */
  @Override
  protected WebApp startImpl()
  {
    // server/1h2g
    // super.startImpl();
    
    updateVersion();

    WebAppController controller = _primaryController;

    if (controller != null)
      return controller.request();
    else
      return null;
  }

  @Override
  public WebApp getDeployInstance()
  {
    WebAppController controller = _primaryController;

    if (controller != null)
      return controller.request();
    else
      return null;
  }
  
  @Override
  protected void stopImpl()
  {
  }
  
  @Override
  protected void stopLazyImpl()
  {
  }
  
  @Override
  protected void destroyInstance(WebApp instance)
  {
  }

  /**
   * Initialize the controller.
   */
  @Override
  protected void initBegin()
  {
    /*
    super.initBegin();
    */
  }

  /**
   * Initialize the controller.
   */
  @Override
  protected void initEnd()
  {
    /*
    super.initBegin();
    */
  }

  @Override
  public boolean isModified()
  {
    boolean isModified = updateVersion();
    
    return isModified;
  }
  
  @Override
  public void merge(DeployControllerApi<WebApp> newControllerV)
  {
    _mergeList.add(newControllerV);

    if (_primaryController != null)
      _primaryController.merge(newControllerV);
    else {
      // server/12ab
      super.merge(newControllerV);
    }
  }

  
  public boolean updateVersion()
  {
//    _isModified = true;

    return updateVersionImpl();
  }

  private boolean updateVersionImpl()
  {
    if (! _isUpdating.compareAndSet(false, true))
      return false;

    try {
      synchronized (this) {
        ExpandVersion oldPrimaryVersion = _primaryVersion;
        WebAppController oldPrimaryController = _primaryController;
        
        WebAppController newPrimaryController = null;

        ExpandVersion version = _generator.getPrimaryVersion(_baseKey);

        if (oldPrimaryVersion != null && oldPrimaryVersion.equals(version))
          return false;

        if (version != null) {
          newPrimaryController = _generator.createVersionController(version);
          newPrimaryController.merge(newPrimaryController);
        }

        if (newPrimaryController == null) {
          throw new ConfigException(L.l(this + " does not have an implementing version"));
        }

        if (newPrimaryController == oldPrimaryController)
          return false;

        log.fine(this + " updating primary to " + newPrimaryController);

        if (oldPrimaryController != null
            && oldPrimaryController != newPrimaryController) {
          _controllerList.add(oldPrimaryController);
        }
        
        // server/1h20
        newPrimaryController.init();

        newPrimaryController.setVersionAlias(true);
        // server/12ab
        newPrimaryController.merge(this);
        
        // server/1h35
        for (DeployControllerApi<WebApp> newController : _mergeList) {
          newPrimaryController.merge(newController);
        }
        
        _primaryController = newPrimaryController;
        
        _primaryVersion = version;

        _controllerList.remove(newPrimaryController);

        int size = _controllerList.size();
        if (size > 0) {
          WebAppController oldController = _controllerList.get(size - 1);

          long expireTime = Alarm.getCurrentTime() + _versionRolloverTime;

          _primaryController.setOldWebApp(oldController, expireTime);
        }

        _restartTime = Alarm.getCurrentTime();
        clearCache();
        
        return true;
      }
    } finally {
      _isUpdating.set(false);
    }
  }
  
  /**
   * Returns a printable view.
   */
  @Override
  public String toString()
  {
    return getClass().getSimpleName() +  "[" + getId() + "]";
  }

  static class VersionNameComparator implements Comparator<String>
  {
    public int compare(String versionA, String versionB)
    {
      /*
      String versionA = a.getVersion();
      String versionB = b.getVersion();
      */

      int lengthA = versionA.length();
      int lengthB = versionB.length();

      int indexA = 0;
      int indexB = 0;

      while (indexA < lengthA && indexB < lengthB) {
        int valueA = 0;
        int valueB = 0;
        char chA;
        char chB;

        for (;
             indexA < lengthA
               && '0' <= (chA = versionA.charAt(indexA)) && chA <= '9';
             indexA++) {
          valueA = 10 * valueA + chA - '0';
        }

        for (;
             indexB < lengthB
               && '0' <= (chB = versionB.charAt(indexB)) && chB <= '9';
             indexB++) {
          valueB = 10 * valueB + chB - '0';
        }

        if (valueA < valueB)
          return 1;
        else if (valueB < valueA)
          return -1;

        while (indexA < lengthA && indexB < lengthB
               && ! ('0' <= (chA = versionA.charAt(indexA)) && chA <= '9')
               && ! ('0' <= (chB = versionB.charAt(indexB)) && chB <= '9')) {

          if (chA < chB)
            return 1;
          else if (chB < chA)
            return -1;

          indexA++;
          indexB++;
        }

        if (indexA < lengthA
            && ! ('0' <= (chA = versionA.charAt(indexA)) && chA <= '9'))
          return 1;
        else if (indexB < lengthB
                 && ! ('0' <= (chB = versionB.charAt(indexB)) && chB <= '9'))
          return -1;
      }

      if (indexA != lengthA)
        return 1;
      else if (indexB != lengthB)
        return -1;
      else
        return 0;
    }
  }
}
