001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.runtime.converter;
020
021import java.io.File;
022import java.io.IOException;
023import java.io.Writer;
024import java.lang.reflect.Field;
025import java.lang.reflect.Method;
026import java.lang.reflect.Modifier;
027import java.nio.charset.Charset;
028import java.nio.charset.CharsetEncoder;
029import java.util.Date;
030import java.util.HashSet;
031import java.util.Iterator;
032import java.util.List;
033import java.util.ListIterator;
034import java.util.Map;
035import java.util.Map.Entry;
036import java.util.Set;
037
038import lucee.commons.lang.CFTypes;
039import lucee.commons.lang.ExceptionUtil;
040import lucee.commons.lang.StringUtil;
041import lucee.runtime.Component;
042import lucee.runtime.ComponentScope;
043import lucee.runtime.ComponentSpecificAccess;
044import lucee.runtime.PageContext;
045import lucee.runtime.component.Property;
046import lucee.runtime.exp.PageException;
047import lucee.runtime.java.JavaObject;
048import lucee.runtime.op.Caster;
049import lucee.runtime.op.Decision;
050import lucee.runtime.orm.ORMUtil;
051import lucee.runtime.reflection.Reflector;
052import lucee.runtime.text.xml.XMLCaster;
053import lucee.runtime.type.Array;
054import lucee.runtime.type.Collection;
055import lucee.runtime.type.Collection.Key;
056import lucee.runtime.type.KeyImpl;
057import lucee.runtime.type.ObjectWrap;
058import lucee.runtime.type.Query;
059import lucee.runtime.type.Struct;
060import lucee.runtime.type.StructImpl;
061import lucee.runtime.type.UDF;
062import lucee.runtime.type.dt.DateTime;
063import lucee.runtime.type.dt.DateTimeImpl;
064import lucee.runtime.type.dt.TimeSpan;
065import lucee.runtime.type.util.ArrayUtil;
066import lucee.runtime.type.util.CollectionUtil;
067import lucee.runtime.type.util.ComponentProUtil;
068import lucee.runtime.type.util.ComponentUtil;
069
070import org.w3c.dom.Node;
071
072/**
073 * class to serialize and desirilize WDDX Packes
074 */
075public final class JSONConverter extends ConverterSupport {
076    
077        private static final Collection.Key REMOTING_FETCH = KeyImpl.intern("remotingFetch");
078
079        private static final Key TO_JSON = KeyImpl.intern("_toJson");
080    private static final Object NULL = new Object();
081    private static final String NULL_STRING = "";
082
083        private boolean ignoreRemotingFetch;
084
085        private CharsetEncoder charsetEncoder;
086
087        /**
088         * @param ignoreRemotingFetch
089         * @param charset if set, characters not supported by the charset are escaped.
090         */
091    public JSONConverter(boolean ignoreRemotingFetch, Charset charset) {
092        this.ignoreRemotingFetch=ignoreRemotingFetch;
093        charsetEncoder = charset!=null?charset.newEncoder():null;//.canEncode("string");
094    }
095        
096        
097        /**
098         * serialize Serializable class
099         * @param serializable
100     * @param sb
101         * @param serializeQueryByColumns 
102         * @param done 
103         * @throws ConverterException
104     */
105    
106    private void _serializeClass(PageContext pc,Set test,Class clazz,Object obj, StringBuilder sb, boolean serializeQueryByColumns, Set<Object> done) throws ConverterException {
107        Struct sct=new StructImpl(Struct.TYPE_LINKED);
108        if(test==null)test=new HashSet();
109        
110        // Fields
111        Field[] fields = clazz.getFields();
112        Field field;
113        for(int i=0;i<fields.length;i++){
114                field=fields[i];
115                if(obj!=null || (field.getModifiers()&Modifier.STATIC)>0)
116                                try {
117                                        sct.setEL(field.getName(), testRecusrion(test,field.get(obj)));
118                                } catch (Exception e) {
119                                        e.printStackTrace();
120                                }
121        }
122        if(obj !=null){
123                // setters
124                Method[] setters=Reflector.getSetters(clazz);
125                for(int i=0;i<setters.length;i++){
126                        sct.setEL(setters[i].getName().substring(3), NULL);
127                }
128                // getters
129                Method[] getters=Reflector.getGetters(clazz);
130                for(int i=0;i<getters.length;i++){
131                        try {
132                                sct.setEL(getters[i].getName().substring(3), testRecusrion(test,getters[i].invoke(obj, ArrayUtil.OBJECT_EMPTY)));
133                                
134                                } 
135                        catch (Exception e) {}
136                }
137        }
138        test.add(clazz);
139        
140        
141        _serializeStruct(pc,test,sct, sb, serializeQueryByColumns, true,done);
142    }
143    
144        
145        private Object testRecusrion(Set test, Object obj) {
146                if(test.contains(obj.getClass())) return obj.getClass().getName();
147                return obj;
148        }
149
150
151        /**
152         * serialize a Date
153         * @param date Date to serialize
154         * @param sb
155         * @throws ConverterException
156         */
157        private void _serializeDate(Date date, StringBuilder sb) {
158                _serializeDateTime(new DateTimeImpl(date),sb);
159        }
160        /**
161         * serialize a DateTime
162         * @param dateTime DateTime to serialize
163         * @param sb
164         * @throws ConverterException
165         */
166        private void _serializeDateTime(DateTime dateTime, StringBuilder sb) {
167                
168                sb.append(StringUtil.escapeJS(JSONDateFormat.format(dateTime,null),'"',charsetEncoder));
169                
170                /*try {
171                sb.append(goIn());
172                    sb.append("createDateTime(");
173                    sb.append(DateFormat.call(null,dateTime,"yyyy,m,d"));
174                    sb.append(' ');
175                    sb.append(TimeFormat.call(null,dateTime,"HH:mm:ss"));
176                    sb.append(')');
177                } 
178            catch (PageException e) {
179                        throw new ConverterException(e);
180                }*/
181            //Januar, 01 2000 01:01:01
182        }
183
184        /**
185         * serialize a Array
186         * @param array Array to serialize
187         * @param sb
188         * @param serializeQueryByColumns 
189         * @param done 
190         * @throws ConverterException
191         */
192        private void _serializeArray(PageContext pc,Set test,Array array, StringBuilder sb, boolean serializeQueryByColumns, Set<Object> done) throws ConverterException {
193                _serializeList(pc,test,array.toList(),sb,serializeQueryByColumns,done);
194        }
195        
196        /**
197         * serialize a List (as Array)
198         * @param list List to serialize
199         * @param sb
200         * @param serializeQueryByColumns 
201         * @param done 
202         * @throws ConverterException
203         */
204        private void _serializeList(PageContext pc,Set test,List list, StringBuilder sb, boolean serializeQueryByColumns, Set<Object> done) throws ConverterException {
205                
206            sb.append(goIn());
207            sb.append("[");
208            boolean doIt=false;
209                ListIterator it=list.listIterator();
210                while(it.hasNext()) {
211                    if(doIt)sb.append(',');
212                    doIt=true;
213                        _serialize(pc,test,it.next(),sb,serializeQueryByColumns,done);
214                }
215                
216                sb.append(']');
217        }
218        private void _serializeArray(PageContext pc,Set test,Object[] arr, StringBuilder sb, boolean serializeQueryByColumns, Set<Object> done) throws ConverterException {
219                
220            sb.append(goIn());
221            sb.append("[");
222            for(int i=0;i<arr.length;i++) {
223                    if(i>0)sb.append(',');
224                    _serialize(pc,test,arr[i],sb,serializeQueryByColumns,done);
225                }
226                sb.append(']');
227        }
228
229    /**
230     * serialize a Struct
231     * @param struct Struct to serialize
232     * @param sb
233     * @param serializeQueryByColumns 
234     * @param addUDFs 
235     * @param done 
236     * @throws ConverterException
237     */
238    public void _serializeStruct(PageContext pc,Set test,Struct struct, StringBuilder sb, boolean serializeQueryByColumns, boolean addUDFs, Set<Object> done) throws ConverterException {
239        // Component
240        if(struct instanceof Component){
241                String res = castToJson(pc, (Component)struct, NULL_STRING);
242                if(res!=NULL_STRING) {
243                        sb.append(res);
244                        return;
245                }
246        }
247        
248        
249        sb.append(goIn());
250        sb.append("{");
251        //Key[] keys = struct.keys();
252        //Key key;
253        Iterator<Entry<Key, Object>> it = struct.entryIterator();
254        Entry<Key, Object> e;
255        Object value;
256        boolean doIt=false;
257        while(it.hasNext()) {
258                e = it.next();
259                //key=keys[i];
260                value=e.getValue();
261                if(!addUDFs && (value instanceof UDF || value==null))continue;
262                if(doIt)sb.append(',');
263            doIt=true;
264            sb.append(StringUtil.escapeJS(e.getKey().getString(),'"',charsetEncoder));
265            sb.append(':');
266            _serialize(pc,test,value,sb,serializeQueryByColumns,done);
267        }
268        
269        if(struct instanceof Component){
270                Boolean remotingFetch;
271                Component cfc = (Component)struct;
272                boolean isPeristent=false;
273                isPeristent=ComponentProUtil.isPersistent(cfc);
274                        
275                Property[] props = cfc.getProperties(false);
276                ComponentScope scope = cfc.getComponentScope();
277                for(int i=0;i<props.length;i++) {
278                        if(!ignoreRemotingFetch) {
279                                remotingFetch=Caster.toBoolean(props[i].getDynamicAttributes().get(REMOTING_FETCH,null),null);
280                                if(remotingFetch==null){
281                                        if(isPeristent  && ORMUtil.isRelated(props[i])) continue;
282                                }
283                                else if(!remotingFetch.booleanValue()) continue;
284                        
285                        }
286                        Key key = KeyImpl.getInstance(props[i].getName());
287                value=scope.get(key,null);
288                if(!addUDFs && (value instanceof UDF || value==null))continue;
289                if(doIt)sb.append(',');
290                doIt=true;
291                sb.append(StringUtil.escapeJS(key.getString(),'"',charsetEncoder));
292                sb.append(':');
293                _serialize(pc,test,value,sb,serializeQueryByColumns,done);
294                }
295        }
296        
297        
298        sb.append('}');
299    }
300    
301    private static String castToJson(PageContext pc,Component cfc, String defaultValue) throws ConverterException {
302                Object o=cfc.get(TO_JSON,null);
303                if(!(o instanceof UDF)) return defaultValue;
304                UDF udf=(UDF) o;
305                if(udf.getReturnType()!=CFTypes.TYPE_VOID && udf.getFunctionArguments().length==0) {
306                        try {
307                                return Caster.toString(cfc.call(pc, TO_JSON, new Object[0]));
308                        } catch (PageException e) {
309                                e.printStackTrace();
310                                throw toConverterException(e);
311                        }
312                }
313                return defaultValue;
314    }
315    
316    
317
318    /**
319     * serialize a Map (as Struct)
320     * @param map Map to serialize
321     * @param sb
322     * @param serializeQueryByColumns 
323     * @param done 
324     * @throws ConverterException
325     */
326    private void _serializeMap(PageContext pc,Set test,Map map, StringBuilder sb, boolean serializeQueryByColumns, Set<Object> done) throws ConverterException {
327        sb.append(goIn());
328        sb.append("{");
329        
330        Iterator it=map.keySet().iterator();
331        boolean doIt=false;
332        while(it.hasNext()) {
333            Object key=it.next();
334            if(doIt)sb.append(',');
335            doIt=true;
336            sb.append(StringUtil.escapeJS(key.toString(),'"',charsetEncoder));
337            sb.append(':');
338            _serialize(pc,test,map.get(key),sb,serializeQueryByColumns,done);
339        }
340        
341        sb.append('}');
342    }
343    /**
344     * serialize a Component
345     * @param component Component to serialize
346     * @param sb
347     * @param serializeQueryByColumns 
348     * @param done 
349     * @throws ConverterException
350     */
351    private void _serializeComponent(PageContext pc,Set test,Component component, StringBuilder sb, boolean serializeQueryByColumns, Set<Object> done) throws ConverterException {
352        ComponentSpecificAccess cw = ComponentSpecificAccess.toComponentSpecificAccess(Component.ACCESS_PRIVATE,component);
353            _serializeStruct(pc,test,cw, sb, serializeQueryByColumns,false,done);
354    }
355    
356
357    private void _serializeUDF(PageContext pc,Set test,UDF udf, StringBuilder sb,boolean serializeQueryByColumns, Set<Object> done) throws ConverterException {
358                Struct sct=new StructImpl();
359                try {
360                        // Meta
361                        Struct meta = udf.getMetaData(pc);
362                        sct.setEL("Metadata", meta);
363                        
364                        // Parameters
365                        sct.setEL("MethodAttributes", meta.get("PARAMETERS"));
366                } 
367                catch (PageException e) {
368                        throw toConverterException(e);
369                }
370                
371                sct.setEL("Access", ComponentUtil.toStringAccess(udf.getAccess(),"public"));
372                sct.setEL("Output", Caster.toBoolean(udf.getOutput()));
373                sct.setEL("ReturnType", udf.getReturnTypeAsString());
374                try{
375                        sct.setEL("PagePath", udf.getPageSource().getResource().getAbsolutePath());
376                }catch(Throwable t){
377                ExceptionUtil.rethrowIfNecessary(t);
378        }
379                
380                _serializeStruct(pc,test,sct, sb, serializeQueryByColumns, true,done);
381                // TODO key SuperScope and next?
382        }
383
384    
385
386        /**
387         * serialize a Query
388         * @param query Query to serialize
389         * @param sb
390         * @param serializeQueryByColumns 
391         * @param done 
392         * @throws ConverterException
393         */
394        private void _serializeQuery(PageContext pc,Set test,Query query, StringBuilder sb, boolean serializeQueryByColumns, Set<Object> done) throws ConverterException {
395                
396                Collection.Key[] _keys = CollectionUtil.keys(query);
397                sb.append(goIn());
398                sb.append("{");
399                
400                /*
401
402{"DATA":[["a","b"],["c","d"]]}
403{"DATA":{"aaa":["a","c"],"bbb":["b","d"]}}
404                 * */
405                // Rowcount
406                if(serializeQueryByColumns){
407                        sb.append("\"ROWCOUNT\":");
408                        sb.append(Caster.toString(query.getRecordcount()));
409                        sb.append(',');
410                }
411                
412                // Columns
413                sb.append("\"COLUMNS\":[");
414                String[] cols = query.getColumns();
415                for(int i=0;i<cols.length;i++) {
416                        if(i>0)sb.append(",");
417                        sb.append(StringUtil.escapeJS(cols[i].toUpperCase(),'"',charsetEncoder));
418                }
419                sb.append("],");
420                
421                // Data
422                sb.append("\"DATA\":");
423                if(serializeQueryByColumns) {
424                        sb.append('{');
425                        boolean oDoIt=false;
426                        int len=query.getRecordcount();
427                        for(int i=0;i<_keys.length;i++) {
428                            if(oDoIt)sb.append(',');
429                            oDoIt=true;
430                            sb.append(goIn());
431                    sb.append(StringUtil.escapeJS(_keys[i].getString(),'"',charsetEncoder));
432                    sb.append(":[");
433                                boolean doIt=false;
434                                        for(int y=1;y<=len;y++) {
435                                            if(doIt)sb.append(',');
436                                            doIt=true;
437                                            try {
438                                                        _serialize(pc,test,query.getAt(_keys[i],y),sb,serializeQueryByColumns,done);
439                                                } catch (PageException e) {
440                                                        _serialize(pc,test,e.getMessage(),sb,serializeQueryByColumns,done);
441                                                }
442                                        }
443                                
444                                sb.append(']');
445                        }
446        
447                        sb.append('}');
448                }
449                else {
450                        sb.append('[');
451                        boolean oDoIt=false;
452                        int len=query.getRecordcount();
453                        for(int row=1;row<=len;row++) {
454                            if(oDoIt)sb.append(',');
455                            oDoIt=true;
456        
457                                sb.append("[");
458                                boolean doIt=false;
459                                        for(int col=0;col<_keys.length;col++) {
460                                            if(doIt)sb.append(',');
461                                            doIt=true;
462                                            try {
463                                                        _serialize(pc,test,query.getAt(_keys[col],row),sb,serializeQueryByColumns,done);
464                                                } catch (PageException e) {
465                                                        _serialize(pc,test,e.getMessage(),sb,serializeQueryByColumns,done);
466                                                }
467                                        }
468                                sb.append(']');
469                        }
470                        sb.append(']');
471                }
472                sb.append('}');
473        }
474        
475        /**
476         * serialize a Object to his xml Format represenation
477         * @param object Object to serialize
478         * @param sb StringBuilder to write data
479         * @param serializeQueryByColumns 
480         * @param done 
481         * @throws ConverterException
482         */
483        private void _serialize(PageContext pc,Set test,Object object, StringBuilder sb, boolean serializeQueryByColumns, Set<Object> done) throws ConverterException {
484                
485                // NULL
486                if(object==null || object==NULL) {
487                    sb.append(goIn());
488                    sb.append("null");
489                    return;
490                }
491                // String
492                if(object instanceof String || object instanceof StringBuilder) {
493                    sb.append(goIn());
494                    sb.append(StringUtil.escapeJS(object.toString(),'"',charsetEncoder));
495                    return;
496                }
497                // Character
498                if(object instanceof Character) {
499                    sb.append(goIn());
500                    sb.append(StringUtil.escapeJS(String.valueOf(((Character)object).charValue()),'"',charsetEncoder));
501                    return;
502                }
503                // Number
504                if(object instanceof Number) {
505                    sb.append(goIn());
506                    sb.append(Caster.toString(((Number)object)));
507                    return;
508                }
509                // Boolean
510                if(object instanceof Boolean) {
511                    sb.append(goIn());
512                    sb.append(Caster.toString(((Boolean)object).booleanValue()));
513                    return;
514                }
515                // DateTime
516                if(object instanceof DateTime) {
517                        _serializeDateTime((DateTime)object,sb);
518                    return;
519                }
520                // Date
521                if(object instanceof Date) {
522                        _serializeDate((Date)object,sb);
523                    return;
524                }
525        // XML
526        if(object instanceof Node) {
527                _serializeXML((Node)object,sb);
528                    return;
529        }
530        // Timespan
531        if(object instanceof TimeSpan) {
532                _serializeTimeSpan((TimeSpan) object,sb);
533                    return;
534        }
535                // File
536                if(object instanceof File) {
537                        _serialize(pc,test, ((File)object).getAbsolutePath(), sb, serializeQueryByColumns,done);
538                    return;
539                }
540                // String Converter
541                if(object instanceof ScriptConvertable) {
542                    sb.append(((ScriptConvertable)object).serialize());
543                    return;
544                }
545                Object raw = LazyConverter.toRaw(object);
546                if(done.contains(raw)){
547                        sb.append(goIn());
548                    sb.append("null");
549                    return;
550                }
551                
552                
553                done.add(raw);
554                try{
555                        // Component
556                        if(object instanceof Component) {
557                            _serializeComponent(pc,test,(Component)object,sb,serializeQueryByColumns,done);
558                                    return;
559                        }
560                        // UDF
561                        if(object instanceof UDF) {
562                            _serializeUDF(pc,test,(UDF)object,sb,serializeQueryByColumns,done);
563                                    return;
564                        }
565                        // Struct
566                        if(object instanceof Struct) {
567                                _serializeStruct(pc,test,(Struct)object,sb,serializeQueryByColumns,true,done);
568                                    return;
569                        }
570                        // Map
571                        if(object instanceof Map) {
572                            _serializeMap(pc,test,(Map)object,sb,serializeQueryByColumns,done);
573                                    return;
574                        }
575                                // Array
576                                if(object instanceof Array) {
577                                        _serializeArray(pc,test,(Array)object,sb,serializeQueryByColumns,done);
578                                    return;
579                                }
580                                // List
581                                if(object instanceof List) {
582                                        _serializeList(pc,test,(List)object,sb,serializeQueryByColumns,done);
583                                    return;
584                                }
585                        // Query
586                        if(object instanceof Query) {
587                            _serializeQuery(pc,test,(Query)object,sb,serializeQueryByColumns,done);
588                                    return;
589                        }
590                                // Native Array
591                                if(Decision.isNativeArray(object)){
592                                        if(object instanceof char[])
593                                                _serialize(pc,test,new String((char[])object), sb, serializeQueryByColumns,done);
594                                        else {
595                                                _serializeArray(pc,test,ArrayUtil.toReferenceType(object,ArrayUtil.OBJECT_EMPTY), sb, serializeQueryByColumns,done);
596                                        }
597                                    return;
598                                                
599                                }
600                                // ObjectWrap
601                                if(object instanceof ObjectWrap) {
602                                        try {
603                                                _serialize(pc,test,((ObjectWrap)object).getEmbededObject(), sb, serializeQueryByColumns,done);
604                                        } catch (PageException e) {
605                                                if(object instanceof JavaObject){
606                                                        _serializeClass(pc,test,((JavaObject)object).getClazz(),null,sb,serializeQueryByColumns,done);
607                                                }
608                                                else throw new ConverterException("can't serialize Object of type [ "+Caster.toClassName(object)+" ]");
609                                        }
610                                    return;
611                                }
612                                
613                                _serializeClass(pc,test,object.getClass(),object,sb,serializeQueryByColumns,done);
614                }
615                finally{
616                        done.remove(raw);
617                }
618        }
619
620        private void _serializeXML(Node node, StringBuilder sb) {
621        node=XMLCaster.toRawNode(node);
622        sb.append(goIn());
623            sb.append(StringUtil.escapeJS(XMLCaster.toString(node,""),'"',charsetEncoder));
624        }
625
626
627        private void _serializeTimeSpan(TimeSpan span, StringBuilder sb) {
628        
629                sb.append(goIn());
630                    sb.append("createTimeSpan(");
631                    sb.append(span.getDay());
632                    sb.append(',');
633                    sb.append(span.getHour());
634                    sb.append(',');
635                    sb.append(span.getMinute());
636                    sb.append(',');
637                    sb.append(span.getSecond());
638                    sb.append(')');
639                
640        }
641
642    /**
643         * serialize a Object to his literal Format
644         * @param object Object to serialize
645     * @param serializeQueryByColumns 
646         * @return serialized wddx package
647         * @throws ConverterException
648         */
649        public String serialize(PageContext pc,Object object, boolean serializeQueryByColumns) throws ConverterException {
650                StringBuilder sb=new StringBuilder();
651                _serialize(pc,null,object,sb,serializeQueryByColumns,new HashSet<Object>());
652                return sb.toString();
653        }
654
655        @Override
656        public void writeOut(PageContext pc, Object source, Writer writer) throws ConverterException, IOException {
657                writer.write(serialize(pc,source,false));
658                writer.flush();
659        }
660        
661        
662        /**
663         * @return return current blockquote
664         */
665        private String goIn() {
666            return "";
667        }
668
669
670}