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

org.jabsorb.serializer.impl.BeanSerializer Maven / Gradle / Ivy

There is a newer version: 6.0.86
Show newest version
/*
 * jabsorb - a Java to JavaScript Advanced Object Request Broker
 * http://www.jabsorb.org
 *
 * Copyright 2007-2009 The jabsorb team
 *
 * based on original code from
 * JSON-RPC-Java - a JSON-RPC to Java Bridge with dynamic invocation
 *
 * Copyright Metaparadigm Pte. Ltd. 2004.
 * Michael Clark 
 *
 * Licensed 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.jabsorb.serializer.impl;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.jabsorb.JSONSerializer;
import org.jabsorb.serializer.AbstractSerializer;
import org.jabsorb.serializer.MarshallException;
import org.jabsorb.serializer.ObjectMatch;
import org.jabsorb.serializer.SerializerState;
import org.jabsorb.serializer.UnmarshallException;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Serialises java beans that are known to have readable and writable properties
 */
public class BeanSerializer extends AbstractSerializer
{
  /**
   * Stores the readable and writable properties for the Bean.
   */
  protected static class BeanData
  {
    // TODO: Legacy comment. WTF?
    // in absence of getters and setters, these fields are
    // public to allow subclasses to access.
    /**
     * The bean info for a certain bean
     */
    public BeanInfo beanInfo;

    /**
     * The readable properties of the bean.
     */
    public Map readableProps;

    /**
     * The writable properties of the bean.
     */
    public Map writableProps;
  }

  /**
   * Unique serialisation id.
   */
  private final static long serialVersionUID = 2;

  /**
   * The logger for this class
   */
  private final static Logger log = LoggerFactory
      .getLogger(BeanSerializer.class);

  /**
   * Caches analysed beans
   */
  private static HashMap beanCache = new HashMap();

  /**
   * Classes that this can serialise.
   * 
   * TODO: Yay for bloat!
   */
  private static Class[] _serializableClasses = new Class[] {};

  /**
   * Classes that this can serialise to.
   * 
   * TODO: Yay for bloat!
   */
  private static Class[] _JSONClasses = new Class[] {};

  /**
   * Analyses a bean, returning a BeanData with the data extracted from it.
   * 
   * @param clazz The class of the bean to analyse
   * @return A populated BeanData
   * @throws IntrospectionException If a problem occurs during getting the bean
   *           info.
   */
  public static BeanData analyzeBean(Class clazz) throws IntrospectionException
  {
    log.info("analyzing " + clazz.getName());
    BeanData bd = new BeanData();
    bd.beanInfo = Introspector.getBeanInfo(clazz, Object.class);
    PropertyDescriptor props[] = bd.beanInfo.getPropertyDescriptors();
    bd.readableProps = new HashMap();
    bd.writableProps = new HashMap();
    for (int i = 0; i < props.length; i++)
    {
      //This is declared by enums and shouldn't be shown.
      if(props[i].getName().equals("declaringClass"))
      {
        continue;
      }
      if (props[i].getWriteMethod() != null)
      {
        bd.writableProps.put(props[i].getName(), props[i].getWriteMethod());
      }
      if (props[i].getReadMethod() != null)
      {
        bd.readableProps.put(props[i].getName(), props[i].getReadMethod());
      }
    }
    return bd;
  }

  /**
   * Gets the bean data from cache if possible, otherwise analyses the bean.
   * 
   * @param clazz The class of the bean to analyse
   * @return A populated BeanData
   * @throws IntrospectionException If a problem occurs during getting the bean
   *           info.
   */
  public static BeanData getBeanData(Class clazz) throws IntrospectionException
  {
    BeanData bd;
    synchronized (beanCache)
    {
      bd = (BeanData) beanCache.get(clazz);
      if (bd == null)
      {
        bd = analyzeBean(clazz);
        beanCache.put(clazz, bd);
      }
    }
    return bd;
  }

  public boolean canSerialize(Class clazz, Class jsonClazz)
  {
    return (!clazz.isArray() && !clazz.isPrimitive() && !clazz.isInterface() && (jsonClazz == null || jsonClazz == JSONObject.class));
  }

  public Class[] getJSONClasses()
  {
    return _JSONClasses;
  }

  public Class[] getSerializableClasses()
  {
    return _serializableClasses;
  }

  public Object marshall(SerializerState state, Object p, Object o)
      throws MarshallException
  {
    BeanData bd;
    try
    {
      bd = getBeanData(o.getClass());
    }
    catch (IntrospectionException e)
    {
      throw new MarshallException(o.getClass().getName() +
        " is not a bean", e);
    }

    JSONObject val = new JSONObject();
    if (ser.getMarshallClassHints())
    {
      try
      {
        val.put("javaClass", o.getClass().getName());
      }
      catch (JSONException e)
      {
        throw new MarshallException(
          "JSONException: " + e.getMessage(), e);
      }
    }
    Iterator i = bd.readableProps.entrySet().iterator();
    Object args[] = new Object[0];
    Object result;
    while (i.hasNext())
    {
      Map.Entry ent = (Map.Entry) i.next();
      String prop = (String) ent.getKey();
      Method getMethod = (Method) ent.getValue();
      if (log.isDebugEnabled())
      {
        log.debug("invoking " + getMethod.getName() + "()");
      }
      try
      {
        result = getMethod.invoke(o, args);
      }
      catch (Throwable e)
      {
        if (e instanceof InvocationTargetException)
        {
          e = ((InvocationTargetException) e).getTargetException();
        }
        throw new MarshallException("bean " + o.getClass().getName()
            + " can't invoke " + getMethod.getName() + ": " + e.getMessage(), e);
      }
      try
      {
        if (result != null || ser.getMarshallNullAttributes())
        {
          try
          {
            Object json = ser.marshall(state, o, result, prop);

            // omit the object entirely if it's a circular reference or duplicate
            // it will be regenerated in the fixups phase
            if (JSONSerializer.CIRC_REF_OR_DUPLICATE != json)
            {
              val.put(prop, json);
            }
          }
          catch (JSONException e)
          {
            throw new MarshallException(
              "JSONException: " + e.getMessage(), e);
          }
        }
      }
      catch (MarshallException e)
      {
        throw new MarshallException("bean " + o.getClass().getName() + " "
            + e.getMessage(), e);
      }
    }

    return val;
  }

  public ObjectMatch tryUnmarshall(SerializerState state, Class clazz, Object o)
      throws UnmarshallException
  {
    JSONObject jso = (JSONObject) o;
    BeanData bd;
    try
    {
      bd = getBeanData(clazz);
    }
    catch (IntrospectionException e)
    {
      throw new UnmarshallException(clazz.getName() + " is not a bean", e);
    }

    int match = 0;
    int mismatch = 0;
    Iterator i = bd.writableProps.entrySet().iterator();
    while (i.hasNext())
    {
      Map.Entry ent = (Map.Entry) i.next();
      String prop = (String) ent.getKey();
      if (jso.has(prop))
      {
        match++;
      }
      else
      {
        mismatch++;
      }
    }
    if (match == 0)
    {
      throw new UnmarshallException("bean has no matches");
    }

    // create a concrete ObjectMatch that is always returned in order to satisfy circular reference requirements
    ObjectMatch returnValue = new ObjectMatch(-1);
    state.setSerialized(o, returnValue);

    ObjectMatch m = null;
    ObjectMatch tmp;
    i = jso.keys();
    while (i.hasNext())
    {
      String field = (String) i.next();
      Method setMethod = (Method) bd.writableProps.get(field);
      if (setMethod != null)
      {
        try
        {
          Class param[] = setMethod.getParameterTypes();
          if (param.length != 1)
          {
            throw new UnmarshallException("bean " + clazz.getName()
                + " method " + setMethod.getName() + " does not have one arg");
          }
          tmp = ser.tryUnmarshall(state, param[0], jso.get(field));
          if(tmp!=null)
          {
            if (m == null)
            {
              m = tmp;
            }
            else
            {
              m = m.max(tmp);
            }
          }
        }
        catch (UnmarshallException e)
        {
          throw new UnmarshallException("bean " + clazz.getName() + " "
              + e.getMessage(), e);
        }
        catch (JSONException e)
        {
          throw new UnmarshallException("bean " + clazz.getName() + " "
              + e.getMessage(), e);
        }
      }
      else
      {
        mismatch++;
      }
    }
    if (m != null)
    {
      returnValue.setMismatch(m.max(new ObjectMatch(mismatch)).getMismatch());
    }
    else
    {
      returnValue.setMismatch(mismatch);
    }
    return returnValue;
  }

  public Object unmarshall(SerializerState state, Class clazz, Object o)
      throws UnmarshallException
  {
    JSONObject jso = (JSONObject) o;
    BeanData bd;
    try
    {
      bd = getBeanData(clazz);
    }
    catch (IntrospectionException e)
    {
      throw new UnmarshallException(clazz.getName() + " is not a bean", e);
    }
    if (log.isDebugEnabled())
    {
      log.debug("instantiating " + clazz.getName());
    }
    Object instance;
    try
    {
      instance = clazz.newInstance();
    }
    catch (InstantiationException e)
    {
      throw new UnmarshallException(
        "could not instantiate bean of type " + 
        clazz.getName() + ", make sure it has a no argument " +
        "constructor and that it is not an interface or " +
        "abstract class", e);
    }
    catch (IllegalAccessException e)
    {
      throw new UnmarshallException(
        "could not instantiate bean of type " + 
        clazz.getName(), e);
    }
    catch (RuntimeException e)
    {
      throw new UnmarshallException(
        "could not instantiate bean of type " + 
        clazz.getName(), e);
    }
    state.setSerialized(o, instance);
    Object invokeArgs[] = new Object[1];
    Object fieldVal;
    Iterator i = jso.keys();
    while (i.hasNext())
    {
      String field = (String) i.next();
      Method setMethod = (Method) bd.writableProps.get(field);
      if (setMethod != null)
      {
        try
        {
          Class param[] = setMethod.getParameterTypes();
          fieldVal = ser.unmarshall(state, param[0], jso.get(field));
        }
        catch (UnmarshallException e)
        {
          throw new UnmarshallException(
            "could not unmarshall field \"" + field + "\" of bean " + 
            clazz.getName(), e);
        }
        catch (JSONException e)
        {
          throw new UnmarshallException(
              "could not unmarshall field \"" + field + "\" of bean " + 
              clazz.getName(), e);
        }
        if (log.isDebugEnabled())
        {
          log.debug("invoking " + setMethod.getName() + "(" + fieldVal + ")");
        }
        invokeArgs[0] = fieldVal;
        try
        {
          setMethod.invoke(instance, invokeArgs);
        }
        catch (Throwable e)
        {
          if (e instanceof InvocationTargetException)
          {
            e = ((InvocationTargetException) e).getTargetException();
          }
          throw new UnmarshallException("bean " + clazz.getName()
              + "can't invoke " + setMethod.getName() + ": " + e.getMessage(), e);
        }
      }
    }
    return instance;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy