/FileExtraction/Java_unseen/dlitz_resin/modules/resin/src/com/caucho/jsf/application/SessionStateManager.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 version 2
 * as published by the Free Software Foundation.
 *
 * 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.jsf.application;

import java.io.*;
import java.util.*;
import java.util.logging.*;

import javax.faces.*;
import javax.faces.application.*;
import javax.faces.component.*;
import javax.faces.component.html.*;
import javax.faces.context.*;
import javax.faces.render.*;

import com.caucho.util.*;
import com.caucho.hessian.io.*;
import static com.caucho.jsf.application.SessionStateManager.StateSerializationMethod.HESSIAN;

public class SessionStateManager extends StateManager
{
  private static final L10N L = new L10N(SessionStateManager.class);
  private static final Logger log
    = Logger.getLogger(SessionStateManager.class.getName());

  private static final IntMap _typeMap = new IntMap();
  private static final ArrayList<Class> _typeList = new ArrayList();

  private StateSerializationMethod _stateSerializationMethod
    = HESSIAN;

  public void setStateSerializationMethod(StateSerializationMethod stateSerializationMethod)
  {
    _stateSerializationMethod = stateSerializationMethod;
  }

  @Override
  public Object saveView(FacesContext context)
  {
    UIViewRoot root = context.getViewRoot();

    if (root == null || root.isTransient())
      return null;

    try {
      ByteArrayOutputStream bos = new ByteArrayOutputStream();

      StateSerializationWriter stateWriter
        = createStateSerializationWriter(bos);

      serialize(stateWriter, context, root, new HashSet<String>());

      stateWriter.close();

      byte []state = bos.toByteArray();

      if (log.isLoggable(Level.FINE)) {
        log.fine("JSF[" + root.getViewId() + "] serialize (" + state.length + " bytes)");

        if (log.isLoggable(Level.FINER))
          debugState(state);
      }

      if (! isSavingStateInClient(context)) {
        Map sessionMap = context.getExternalContext().getSessionMap();

        Map viewMap = (Map) sessionMap.get("caucho.jsf.view");

        if (viewMap == null)
          viewMap = new HashMap();

        viewMap.put(root.getViewId(), state);

        sessionMap.put("caucho.jsf.view", viewMap);
        
        return "!";
      }

      return state;
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  private StateSerializationWriter createStateSerializationWriter(
    ByteArrayOutputStream out)
    throws IOException
  {
    if (HESSIAN.equals(_stateSerializationMethod))
      return new HessianStateSerializationWriter(new Hessian2Output(out));
    else
      return new JavaStateSerializationWriter(new ObjectOutputStream(out));
  }

  private StateSerializationReader createStateSerializationReader(
    ByteArrayInputStream in)
    throws IOException
  {
    if (HESSIAN.equals(_stateSerializationMethod))
      return new HessianStateSerializationReader(new Hessian2Input(in));
    else {
      ObjectInputStream ois = new ObjectInputStream(in) {
        protected Class<?> resolveClass(ObjectStreamClass objectStreamClass)
          throws IOException, ClassNotFoundException
        {
          ClassLoader cl = Thread.currentThread().getContextClassLoader();

          return cl.loadClass(objectStreamClass.getName());
        }
      };

      return new JavaStateSerializationReader(ois);
    }
  }

  @Deprecated
  @Override
  public SerializedView saveSerializedView(FacesContext context)
  {
    return new SerializedView(saveView(context), null);
  }

  @Deprecated
  public void writeState(FacesContext context,
                         SerializedView state)
    throws IOException
  {
    Object [] stateArray = new Object [2];

    stateArray [0] = state.getStructure();
    stateArray [1] = state.getState();

    writeState(context, stateArray);
  }

  public void writeState(FacesContext context, Object state)
    throws IOException
  {
    ResponseStateManager rsm = context.getRenderKit().getResponseStateManager();

    if (! (state instanceof Object []))
      rsm.writeState(context, new Object []{state, null});
    else
      rsm.writeState(context, state);
  }

  @Override
  public UIViewRoot restoreView(FacesContext context,
                                String viewId,
                                String renderKitId)
  {

    RenderKit renderKit = context.getRenderKit();
    
    if (renderKit == null) {
      RenderKitFactory renderKitFactory
        = (RenderKitFactory) FactoryFinder.getFactory(
        FactoryFinder.RENDER_KIT_FACTORY);
      
      renderKit = renderKitFactory.getRenderKit(context, renderKitId);
    }
    
    ResponseStateManager rsm =
      renderKit.getResponseStateManager();

    Object state = rsm.getState(context, viewId);//sessionMap.get("caucho.jsf.view");

    if (!isSavingStateInClient(context) && "!".equals(((Object [])state) [0])) {
      Map viewMap = (Map) context.getExternalContext()
        .getSessionMap()
        .get("caucho.jsf.view");

      if (viewMap != null)
        state = viewMap.get(viewId);
    }

    if (state == null)
      return null;
    else if (state instanceof byte[])
      return restoreView(context, (byte []) state, renderKitId);
    if (state instanceof Object [])
      return restoreView(context, (byte []) ((Object []) state) [0], renderKitId);
    else if (state instanceof StateManager.SerializedView) {
      StateManager.SerializedView serView
        = (StateManager.SerializedView) state;

      return restoreView(context, (byte[]) serView.getStructure(), renderKitId);
    }
    else
      throw new IllegalStateException(L.l("unexpected saved state: '{0}'",
                                          state));
  }

  private void serialize(StateSerializationWriter out,
                         FacesContext context,
                         UIComponent comp,
                         HashSet<String> idMap)
    throws IOException
  {
    if (comp.isTransient())
      return;

    if (idMap.contains(comp.getId()))
      throw new IllegalStateException(L.l("'{0}' is a duplicate component during serialization.",
                                          comp.getId()));

    if (comp.getId() != null)
      idMap.add(comp.getId());

    if (comp instanceof NamingContainer)
      idMap = new HashSet<String>(8);

    int typeId = _typeMap.get(comp.getClass());
    out.writeInt(typeId);
    if (typeId <= 0)
      out.writeString(comp.getClass().getName());

    int fullChildCount = comp.getChildCount();
    if (fullChildCount > 0) {
      int childCount = 0;

      List<UIComponent> children = comp.getChildren();

      for (int i = 0; i < fullChildCount; i++) {
        UIComponent child = children.get(i);

        if (! child.isTransient())
          childCount++;
      }

      out.writeInt(childCount);

      for (int i = 0; i < fullChildCount; i++) {
        UIComponent child = children.get(i);

        serialize(out, context, child, idMap);
      }
    }
    else
      out.writeInt(0);

    int facetCount = comp.getFacetCount();
    out.writeInt(facetCount);

    if (facetCount > 0) {
      for (Map.Entry<String,UIComponent> entry : comp.getFacets().entrySet()) {
        out.writeString(entry.getKey());

        serialize(out, context, entry.getValue(), idMap);
      }
    }

    out.writeObject(comp.saveState(context));
  }

  private UIViewRoot restoreView(FacesContext context,
                                 byte[] data,
                                 String renderKitId)
  {
    if (data == null)
      return null;

    try {
      ByteArrayInputStream bis = new ByteArrayInputStream(data);

      StateSerializationReader stateReader
        = createStateSerializationReader(bis);

      return (UIViewRoot) deserialize(stateReader, context, renderKitId);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  private UIComponent deserialize(StateSerializationReader in,
                                  FacesContext context,
                                  String renderKitId)
  throws IOException,
           ClassNotFoundException,
           InstantiationException,
           IllegalAccessException
  {
    int typeId = in.readInt();

    Class type;

    if (typeId > 0) {
      type = _typeList.get(typeId);
    }
    else {
      String typeName = in.readString();
      
      ClassLoader loader = Thread.currentThread().getContextClassLoader();

      type = Class.forName(typeName, false, loader);
    }

    final UIComponent comp = (UIComponent) type.newInstance();

    if (UIViewRoot.class.equals(type)) {
      final UIViewRoot viewRoot = (UIViewRoot) comp;

      viewRoot.setRenderKitId(renderKitId);

      context.setViewRoot(viewRoot);
    }

    int childCount = in.readInt();
    for (int i = 0; i < childCount; i++) {
      comp.getChildren().add(deserialize(in, context, renderKitId));
    }

    int facetCount = in.readInt();

    for (int i = 0; i < facetCount; i++) {
      String key = in.readString();

      comp.getFacets().put(key, deserialize(in, context, renderKitId));
    }

    comp.restoreState(context, in.readObject());

    return comp;
  }

  public String toString()
  {
    return "SessionStateManager[]";
  }

  private static void addType(Class type)
  {
    if (_typeMap.get(type) > 0)
      return;
    
    _typeMap.put(type, _typeList.size());
    _typeList.add(type);
  }

  private void debugState(byte []state)
  {
    for (int i = 0; i < state.length; i++) {
      if (i != 0 && i % 40 == 0)
        System.out.println();

      int ch = state[i];

      if ('a' <= ch && ch <= 'z'
          || 'A' <= ch && ch <= 'Z'
          || '0' <= ch && ch <= '9'
          || ch == ' ' || ch == '[' || ch == '.' || ch == ']'
          || ch == '/' || ch == '\\'
          || ch == '-' || ch == '_' || ch == '{' || ch == '}'
          || ch == '#' || ch == '$' || ch == ':')
        System.out.print((char) ch);
      else {
        System.out.print("x"
                         + Integer.toHexString((ch / 16) & 0xf)
                         + Integer.toHexString(ch & 0xf));
      }
    }
    System.out.println();
  }

  static {
    _typeList.add(null);

    addType(UIColumn.class);
    addType(UICommand.class);
    addType(UIData.class);
    addType(UIForm.class);
    addType(UIGraphic.class);
    addType(UIInput.class);
    addType(UIMessage.class);
    addType(UIMessages.class);
    addType(UINamingContainer.class);
    addType(UIOutput.class);
    addType(UIPanel.class);
    addType(UIParameter.class);
    addType(UISelectBoolean.class);
    addType(UISelectItem.class);
    addType(UISelectItems.class);
    addType(UISelectMany.class);
    addType(UISelectOne.class);
    addType(UIViewRoot.class);
    
    addType(HtmlColumn.class);
    addType(HtmlCommandButton.class);
    addType(HtmlCommandLink.class);
    addType(HtmlDataTable.class);
    addType(HtmlForm.class);
    addType(HtmlGraphicImage.class);
    addType(HtmlInputHidden.class);
    addType(HtmlInputSecret.class);
    addType(HtmlInputText.class);
    addType(HtmlInputTextarea.class);
    addType(HtmlMessage.class);
    addType(HtmlMessages.class);
    addType(HtmlOutputFormat.class);
    addType(HtmlOutputLabel.class);
    addType(HtmlOutputLink.class);
    addType(HtmlOutputText.class);
    addType(HtmlPanelGrid.class);
    addType(HtmlPanelGroup.class);
    addType(HtmlSelectBooleanCheckbox.class);
    addType(HtmlSelectManyCheckbox.class);
    addType(HtmlSelectManyListbox.class);
    addType(HtmlSelectManyMenu.class);
    addType(HtmlSelectOneListbox.class);
    addType(HtmlSelectOneMenu.class);
    addType(HtmlSelectOneRadio.class);
  }

  public static enum StateSerializationMethod {
    HESSIAN,
    JAVA
  }

  static interface StateSerializationWriter {
    public void writeInt(int value) throws IOException;
    public void writeString(String value) throws IOException;
    public void writeObject(Object object) throws IOException;
    public void close()
      throws IOException;
  }

  static interface StateSerializationReader {
    public int readInt() throws IOException;
    public String readString() throws IOException;
    public Object readObject() throws IOException, ClassNotFoundException;
  }

  static class HessianStateSerializationWriter
    implements StateSerializationWriter {
    private Hessian2Output _out;

    HessianStateSerializationWriter(Hessian2Output out)
    {
      _out = out;
    }
 
    public void writeString(String value)
      throws IOException
    {
      _out.writeString(value);
    }

    public void writeObject(Object object)
      throws IOException
    {
      _out.writeObject(object);
    }

    public void writeInt(int value)
      throws IOException
    {
      _out.writeInt(value);
    }
 
    public void close()
      throws IOException
    {
      _out.flush();
      _out.close();
    }
  }

  static class JavaStateSerializationWriter
    implements StateSerializationWriter {
    private ObjectOutputStream _out;

    JavaStateSerializationWriter(ObjectOutputStream out)
    {
      _out = out;
    }

    public void writeString(String s)
      throws IOException
    {
      _out.writeUTF(s);
    }
 
    public void writeInt(int i)
      throws IOException
    {
      _out.writeInt(i);
    }

    public void writeObject(Object o)
      throws IOException
    {
      _out.writeObject(o);
    }

    public void close()
      throws IOException
    {
      _out.flush();
      _out.close();
    }
  }

  static class HessianStateSerializationReader
    implements StateSerializationReader {
    private Hessian2Input _in;

    HessianStateSerializationReader(Hessian2Input in)
    {
      _in = in;
    }

    public String readString()
      throws IOException
    {
      return _in.readString();
    }

    public int readInt()
      throws IOException
    {
      return _in.readInt();
    }
 
    public Object readObject()
      throws IOException, ClassNotFoundException
    {
      return _in.readObject();
    }
  }

  static class JavaStateSerializationReader
    implements StateSerializationReader {
    private ObjectInputStream _in;

    JavaStateSerializationReader(ObjectInputStream in)
    {
      _in = in;
    }

    public String readString()
      throws IOException
    {
      return _in.readUTF();
    }

    public int readInt()
      throws IOException
    {
      return _in.readInt();
    }

    public Object readObject()
      throws IOException, ClassNotFoundException
    {
      return _in.readObject();
    }
  }
}
