/* This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2012 Servoy BV This program is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program 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. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program; if not, see http://www.gnu.org/licenses or write to the Free Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 */ package com.servoy.j2db.util.serialize; import java.io.ObjectStreamException; import java.io.StringWriter; import java.io.Writer; 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 com.servoy.j2db.query.AbstractBaseQuery; import com.servoy.j2db.query.QuerySelect; import com.servoy.j2db.query.QueryTable; import com.servoy.j2db.query.QueryTable1; import com.servoy.j2db.querybuilder.impl.QBFactory; import com.servoy.j2db.querybuilder.impl.QBSelect; import com.servoy.j2db.util.visitor.IVisitor; import com.servoy.j2db.util.visitor.VisitOnceDelegateVisitor; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.converters.Converter; import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.UnmarshallingContext; import com.thoughtworks.xstream.core.util.HierarchicalStreams; import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper; import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import com.thoughtworks.xstream.io.xml.CompactWriter; import com.thoughtworks.xstream.io.xml.DomDriver; import com.thoughtworks.xstream.mapper.Mapper; /** * Responsible for serializing QBSelect objects * * @author rgansevles * * @since 6.1 */ public class QueryBuilderSerializer extends AbstractSerializer { /** * Unique serialisation id. */ private final static long serialVersionUID = 2; /** * The classes that this can serialise */ private final static Class[] _serializableClasses = new Class[] { QBSelect.class }; /** * The class that this serialises to */ private final static Class[] _JSONClasses = new Class[] { JSONObject.class }; private final IQueryBuilderFactoryProvider queryBuilderFactoryProvider; private XStream xStream; public QueryBuilderSerializer(IQueryBuilderFactoryProvider queryBuilderFactoryProvider) { this.queryBuilderFactoryProvider = queryBuilderFactoryProvider; } public Class[] getSerializableClasses() { return _serializableClasses; } public Class[] getJSONClasses() { return _JSONClasses; } public Object marshall(SerializerState state, Object p, Object o) throws MarshallException { if (!(o instanceof QBSelect)) { throw new MarshallException("QueryBuilderSerializer cannot marshall class " + o.getClass()); } QBSelect qbSelect = (QBSelect)o; QuerySelect query = qbSelect.getQuery(false); // make sure that queries are serialized in full, standard serialization optimizations result in missing data that cannot be resolved Writer writer = new StringWriter(); getXstream().marshal(query, new CompactWriter(writer)); String xml = writer.toString(); JSONObject obj = new JSONObject(); try { if (ser.getMarshallClassHints()) { obj.put("javaClass", o.getClass().getName()); } obj.put("query", xml); // required obj.put("datasource", qbSelect.getDataSource()); // required obj.put("alias", qbSelect.getTableAlias()); // optional } catch (JSONException e) { throw new MarshallException(e.getMessage(), e); } return obj; } public ObjectMatch tryUnmarshall(SerializerState state, Class clazz, Object o) throws UnmarshallException { if (queryBuilderFactoryProvider.getQueryBuilderFactory() == null) { throw new UnmarshallException("QueryBuilderSerializer needs query builder factory"); } JSONObject jso = (JSONObject)o; String java_class; try { java_class = jso.getString("javaClass"); } catch (JSONException e) { throw new UnmarshallException("no type hint", e); } if (java_class == null) { throw new UnmarshallException("no type hint"); } if (!(java_class.equals(QBSelect.class.getName()))) { throw new UnmarshallException("not a QBSelect"); } state.setSerialized(o, ObjectMatch.OKAY); return ObjectMatch.OKAY; } public Object unmarshall(SerializerState state, Class clazz, Object o) throws UnmarshallException { final QBFactory queryBuilderFactory = queryBuilderFactoryProvider.getQueryBuilderFactory(); if (queryBuilderFactory == null) { throw new UnmarshallException("QueryBuilderSerializer needs query builder factory"); } JSONObject jso = (JSONObject)o; String xml; String dataSource; String alias; try { xml = jso.getString("query"); // required dataSource = jso.getString("datasource"); // required alias = jso.optString("alias", null); // optional } catch (JSONException e) { throw new UnmarshallException("Could not get the query in QueryBuilderSerializer", e); } if (jso.has("javaClass")) { try { clazz = Class.forName(jso.getString("javaClass")); } catch (ClassNotFoundException e) { throw new UnmarshallException(e.getMessage(), e); } catch (JSONException e) { throw new UnmarshallException("Could not find javaClass", e); } } Object returnValue = null; if (QBSelect.class.equals(clazz)) { final String queryDataSource = dataSource; QuerySelect query = (QuerySelect)getXstream().fromXML(xml); // If the xml was from before adding dataSource to QueryTable, the query will contain QueryTable1 objects, these need to be replaced // with QueryTable with resolved dataSource. query.acceptVisitor(new VisitOnceDelegateVisitor(new IVisitor() { public Object visit(Object obj) { if (obj instanceof QueryTable1) { return ((QueryTable1)obj).addDataSource(queryBuilderFactory.resolveDataSource(queryDataSource, ((QueryTable1)obj).getName())); } return obj; } })); returnValue = queryBuilderFactory.createSelect(dataSource, alias, query); } if (returnValue == null) { throw new UnmarshallException("invalid class " + clazz); } state.setSerialized(o, returnValue); return returnValue; } /** * @return */ private XStream getXstream() { if (xStream == null) { xStream = new XStream(new DomDriver()); xStream.registerConverter(new ReplacedObjectConverter(xStream.getMapper(), AbstractBaseQuery.QUERY_SERIALIZE_DOMAIN)); for (Class< ? extends IWriteReplace> cls : ReplacedObject.getDomainClasses(AbstractBaseQuery.QUERY_SERIALIZE_DOMAIN)) { String className = cls.getSimpleName(); String registeredName; if (QueryTable.class.getSimpleName().equals(className)) { // QueryTable version 2, includes dataSource registeredName = className + "2"; } else if (QueryTable1.class.getSimpleName().equals(className)) { // legacy QueryTable version 1, without dataSource registeredName = QueryTable.class.getSimpleName(); } else { registeredName = className; } xStream.alias(registeredName, cls); } } return xStream; } static class ReplacedObjectConverter implements Converter { private final Mapper mapper; private final String domain; public ReplacedObjectConverter(Mapper mapper, String domain) { this.domain = domain; this.mapper = mapper; } public boolean canConvert(@SuppressWarnings("rawtypes") Class type) { return ReplacedObject.getDomainClasses(domain).contains(type); } public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) { try { ReplacedObject replaced; if (source instanceof IWriteReplaceExtended) { replaced = ((IWriteReplaceExtended)source).writeReplace(true); } else { replaced = (ReplacedObject)((IWriteReplace)source).writeReplace(); } Object o = replaced.getObject(); String name = mapper.serializedClass(o.getClass()); ExtendedHierarchicalStreamWriterHelper.startNode(writer, name, o.getClass()); context.convertAnother(o); writer.endNode(); } catch (ObjectStreamException e) { throw new RuntimeException(e); } } public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { reader.moveDown(); Class< ? > type = HierarchicalStreams.readClassType(reader, mapper); Object o = context.convertAnother(null, type); reader.moveUp(); Class< ? > realClass = mapper.realClass(reader.getNodeName()); return new ReplacedObject(domain, realClass, o).readResolve(); } } }