001    /**
002     * Copyright (c) 2000-2013 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.kernel.io;
016    
017    import com.liferay.portal.kernel.memory.SoftReferenceThreadLocal;
018    import com.liferay.portal.kernel.util.ClassLoaderPool;
019    import com.liferay.portal.kernel.util.GetterUtil;
020    
021    import java.io.IOException;
022    import java.io.ObjectOutputStream;
023    import java.io.OutputStream;
024    import java.io.Serializable;
025    
026    import java.nio.ByteBuffer;
027    
028    import java.util.Arrays;
029    
030    /**
031     * Serializes data in a ClassLoader-aware manner.
032     *
033     * <p>
034     * The Serializer can perform better than {@link java.io.ObjectOutputStream} and
035     * {@link java.io.DataOutputStream}, with respect to encoding primary types,
036     * because it uses a more compact format (containing no BlockHeader) and simpler
037     * call stack involving {@link BigEndianCodec}, as compared to using an
038     * OutputStream wrapper on top of {@link java.io.Bits}.
039     * </p>
040     *
041     * <p>
042     * For Strings, the UTF encoding for ObjectOutputStream and DataOutputStream has
043     * a 2^16=64K length limitation, which is often too restrictive. Serializer has
044     * a 2^32=4G String length limitation, which is generally more than enough. For
045     * pure ASCII character Strings, the encoding performance is almost the same, if
046     * not better, than ObjectOutputStream and DataOutputStream. For Strings
047     * containing non-ASCII characters, the Serializer encodes each char to two
048     * bytes rather than performing UTF encoding. There is a trade-off between
049     * CPU/memory performance and compression rate.
050     * </p>
051     *
052     * <p>
053     * UTF encoding uses more CPU cycles to detect the unicode range for each char
054     * and the resulting output is variable length, which increases the memory
055     * burden when preparing the decoding buffer. Whereas, encoding each char to two
056     * bytes allows for better CPU/memory performance. Although inefficient with
057     * compression rates in comparison to UTF encoding, the char to two byte
058     * approach significantly simplifies the encoder's logic and the output length
059     * is predictably based on the length of the String, so the decoder can manage
060     * its decoding buffer efficiently. On average, a system uses more ASCII String
061     * scheming than non-ASCII String scheming. In most cases, when all system
062     * internal Strings are ASCII Strings and only Strings holding user input
063     * information can have non-ASCII characters, this Serializer performs best. In
064     * other cases, developers should consider using {@link
065     * java.io.ObjectOutputStream} or {@link java.io.DataOutputStream}.
066     * </p>
067     *
068     * <p>
069     * For ordinary Objects, all primary type wrappers are encoded to their raw
070     * values with one byte type headers. This is much more efficient than
071     * ObjectOutputStream's serialization format for primary type wrappers. Strings
072     * are output in the same way as {@link #writeString(java.lang.String)}, but
073     * also with one byte type headers. Objects are serialized by a new
074     * ObjectOutputStream, so no reference handler can be used across Object
075     * serialization. This is done intentionally to isolate each object. The
076     * Serializer is highly optimized for serializing primary types, but is not as
077     * good as ObjectOutputStream for serializing complex objects.
078     * </p>
079     *
080     * <p>
081     * On object serialization, the Serializer uses the {@link
082     * com.liferay.portal.kernel.util.ClassLoaderPool} to look up the servlet
083     * context name corresponding to the object's ClassLoader. The servlet context
084     * name is written to the serialization stream. On object deserialization, the
085     * {@link Deserializer} uses the ClassLoaderPool to look up the ClassLoader
086     * corresponding to the servlet context name read from the deserialization
087     * stream. ObjectOutputStream and ObjectInputStream lack these features, making
088     * Serializer and Deserializer better choices for ClassLoader-aware Object
089     * serialization/deserialization, especially when plugins are involved.
090     * </p>
091     *
092     * @author Shuyang Zhou
093     * @see    Deserializer
094     */
095    public class Serializer {
096    
097            public Serializer() {
098                    BufferQueue bufferQueue = bufferQueueThreadLocal.get();
099    
100                    buffer = bufferQueue.dequeue();
101            }
102    
103            public ByteBuffer toByteBuffer() {
104                    ByteBuffer byteBuffer = ByteBuffer.wrap(Arrays.copyOf(buffer, index));
105    
106                    if (buffer.length <= THREADLOCAL_BUFFER_SIZE_LIMIT) {
107                            BufferQueue bufferQueue = bufferQueueThreadLocal.get();
108    
109                            bufferQueue.enqueue(buffer);
110                    }
111    
112                    buffer = null;
113    
114                    return byteBuffer;
115            }
116    
117            public void writeBoolean(boolean b) {
118                    BigEndianCodec.putBoolean(getBuffer(1), index++, b);
119            }
120    
121            public void writeByte(byte b) {
122                    getBuffer(1)[index++] = b;
123            }
124    
125            public void writeChar(char c) {
126                    BigEndianCodec.putChar(getBuffer(2), index, c);
127    
128                    index += 2;
129            }
130    
131            public void writeDouble(double d) {
132                    BigEndianCodec.putDouble(getBuffer(8), index, d);
133    
134                    index += 8;
135            }
136    
137            public void writeFloat(float f) {
138                    BigEndianCodec.putFloat(getBuffer(4), index, f);
139    
140                    index += 4;
141            }
142    
143            public void writeInt(int i) {
144                    BigEndianCodec.putInt(getBuffer(4), index, i);
145    
146                    index += 4;
147            }
148    
149            public void writeLong(long l) {
150                    BigEndianCodec.putLong(getBuffer(8), index, l);
151    
152                    index += 8;
153            }
154    
155            public void writeObject(Serializable serializable) {
156    
157                    // The if block is ordered by frequency for better performance
158    
159                    if (serializable == null) {
160                            writeByte(SerializationConstants.TC_NULL);
161    
162                            return;
163                    }
164                    else if (serializable instanceof Long) {
165                            writeByte(SerializationConstants.TC_LONG);
166                            writeLong((Long)serializable);
167    
168                            return;
169                    }
170                    else if (serializable instanceof String) {
171                            writeByte(SerializationConstants.TC_STRING);
172                            writeString((String)serializable);
173    
174                            return;
175                    }
176                    else if (serializable instanceof Integer) {
177                            writeByte(SerializationConstants.TC_INTEGER);
178                            writeInt((Integer)serializable);
179    
180                            return;
181                    }
182                    else if (serializable instanceof Boolean) {
183                            writeByte(SerializationConstants.TC_BOOLEAN);
184                            writeBoolean((Boolean)serializable);
185    
186                            return;
187                    }
188                    else if (serializable instanceof Class) {
189                            Class<?> clazz = (Class<?>)serializable;
190    
191                            ClassLoader classLoader = clazz.getClassLoader();
192    
193                            String contextName = ClassLoaderPool.getContextName(classLoader);
194    
195                            writeByte(SerializationConstants.TC_CLASS);
196                            writeString(contextName);
197                            writeString(clazz.getName());
198    
199                            return;
200                    }
201                    else if (serializable instanceof Short) {
202                            writeByte(SerializationConstants.TC_SHORT);
203                            writeShort((Short)serializable);
204    
205                            return;
206                    }
207                    else if (serializable instanceof Character) {
208                            writeByte(SerializationConstants.TC_CHARACTER);
209                            writeChar((Character)serializable);
210    
211                            return;
212                    }
213                    else if (serializable instanceof Byte) {
214                            writeByte(SerializationConstants.TC_BYTE);
215                            writeByte((Byte)serializable);
216    
217                            return;
218                    }
219                    else if (serializable instanceof Double) {
220                            writeByte(SerializationConstants.TC_DOUBLE);
221                            writeDouble((Double)serializable);
222    
223                            return;
224                    }
225                    else if (serializable instanceof Float) {
226                            writeByte(SerializationConstants.TC_FLOAT);
227                            writeFloat((Float)serializable);
228    
229                            return;
230                    }
231                    else {
232                            writeByte(SerializationConstants.TC_OBJECT);
233                    }
234    
235                    try {
236                            ObjectOutputStream objectOutputStream =
237                                    new AnnotatedObjectOutputStream(new BufferOutputStream());
238    
239                            objectOutputStream.writeObject(serializable);
240    
241                            objectOutputStream.close();
242                    }
243                    catch (IOException ioe) {
244                            throw new RuntimeException(
245                                    "Unable to write ordinary serializable object " + serializable,
246                                    ioe);
247                    }
248            }
249    
250            public void writeShort(short s) {
251                    BigEndianCodec.putShort(getBuffer(2), index, s);
252    
253                    index += 2;
254            }
255    
256            public void writeString(String s) {
257                    int length = s.length();
258    
259                    boolean asciiCode = true;
260    
261                    for (int i = 0; i < length; i++) {
262                            char c = s.charAt(i);
263    
264                            if ((c == 0) || (c > 127)) {
265                                    asciiCode = false;
266                                    break;
267                            }
268                    }
269    
270                    if (asciiCode) {
271                            byte[] buffer = getBuffer(length + 5);
272    
273                            BigEndianCodec.putBoolean(buffer, index++, asciiCode);
274    
275                            BigEndianCodec.putInt(buffer, index, length);
276    
277                            index += 4;
278    
279                            for (int i = 0; i < length; i++) {
280                                    char c = s.charAt(i);
281    
282                                    buffer[index++] = (byte)c;
283                            }
284                    }
285                    else {
286                            byte[] buffer = getBuffer(length * 2 + 5);
287    
288                            BigEndianCodec.putBoolean(buffer, index++, asciiCode);
289    
290                            BigEndianCodec.putInt(buffer, index, length);
291    
292                            index += 4;
293    
294                            for (int i = 0; i < length; i++) {
295                                    char c = s.charAt(i);
296    
297                                    BigEndianCodec.putChar(buffer, index, c);
298    
299                                    index += 2;
300                            }
301                    }
302            }
303    
304            public void writeTo(OutputStream outputStream) throws IOException {
305                    outputStream.write(buffer, 0, index);
306    
307                    if (buffer.length <= THREADLOCAL_BUFFER_SIZE_LIMIT) {
308                            BufferQueue bufferQueue = bufferQueueThreadLocal.get();
309    
310                            bufferQueue.enqueue(buffer);
311                    }
312    
313                    buffer = null;
314            }
315    
316            /**
317             * Returns the required buffer length. This method is final so JIT can
318             * perform an inline expansion.
319             *
320             * @param  ensureExtraSpace the extra byte space required to meet the
321             *         buffer's minimum length
322             * @return the buffer value
323             */
324            protected final byte[] getBuffer(int ensureExtraSpace) {
325                    int minSize = index + ensureExtraSpace;
326    
327                    if (minSize < 0) {
328    
329                            // Cannot create byte[] with length longer than Integer.MAX_VALUE
330    
331                            throw new OutOfMemoryError();
332                    }
333    
334                    int oldSize = buffer.length;
335    
336                    if (minSize > oldSize) {
337                            int newSize = oldSize << 1;
338    
339                            if (newSize < minSize) {
340    
341                                    // Overflow and insufficient growth protection
342    
343                                    newSize = minSize;
344                            }
345    
346                            buffer = Arrays.copyOf(buffer, newSize);
347                    }
348    
349                    return buffer;
350            }
351    
352            protected static final int THREADLOCAL_BUFFER_COUNT_LIMIT;
353    
354            protected static final int THREADLOCAL_BUFFER_COUNT_MIN = 8;
355    
356            protected static final int THREADLOCAL_BUFFER_SIZE_LIMIT;
357    
358            protected static final int THREADLOCAL_BUFFER_SIZE_MIN = 16 * 1024;
359    
360            static {
361                    int threadLocalBufferCountLimit = GetterUtil.getInteger(
362                            System.getProperty(
363                                    Serializer.class.getName() +
364                                            ".thread.local.buffer.count.limit"));
365    
366                    if (threadLocalBufferCountLimit < THREADLOCAL_BUFFER_COUNT_MIN) {
367                            threadLocalBufferCountLimit = THREADLOCAL_BUFFER_COUNT_MIN;
368                    }
369    
370                    THREADLOCAL_BUFFER_COUNT_LIMIT = threadLocalBufferCountLimit;
371    
372                    int threadLocalBufferSizeLimit = GetterUtil.getInteger(
373                            System.getProperty(
374                                    Serializer.class.getName() +
375                                            ".thread.local.buffer.size.limit"));
376    
377                    if (threadLocalBufferSizeLimit < THREADLOCAL_BUFFER_SIZE_MIN) {
378                            threadLocalBufferSizeLimit = THREADLOCAL_BUFFER_SIZE_MIN;
379                    }
380    
381                    THREADLOCAL_BUFFER_SIZE_LIMIT = threadLocalBufferSizeLimit;
382            }
383    
384            /**
385             * Softens the local thread's pooled buffer memory.
386             *
387             * <p>
388             * Technically, we should soften each pooled buffer individually to achieve
389             * the best garbage collection (GC) interaction. However, that increases
390             * complexity of pooled buffer access and also burdens the GC's {@link
391             * java.lang.ref.SoftReference} process, hurting performance.
392             * </p>
393             *
394             * <p>
395             * Here, the entire ThreadLocal BufferQueue is softened. For threads that do
396             * serializing often, its BufferQueue will most likely stay valid. For
397             * threads that do serializing only occasionally, its BufferQueue will most
398             * likely be released by GC.
399             * </p>
400             */
401            protected static final ThreadLocal<BufferQueue> bufferQueueThreadLocal =
402                    new SoftReferenceThreadLocal<BufferQueue>() {
403    
404                    @Override
405                    protected BufferQueue initialValue() {
406                            return new BufferQueue();
407                    }
408    
409            };
410    
411            protected byte[] buffer;
412            protected int index;
413    
414            protected static class BufferNode {
415    
416                    public BufferNode(byte[] buffer) {
417                            this.buffer = buffer;
418                    }
419    
420                    protected byte[] buffer;
421                    protected BufferNode next;
422    
423            }
424    
425            /**
426             * Represents a descending <code>byte[]</code> queue ordered by array length.
427             *
428             * <p>
429             * The queue is small enough to simply use a linear scan search for
430             * maintaining its order. The entire queue data is held by a
431             * {@link java.lang.ref.SoftReference}, so when necessary, GC can release the whole
432             * buffer cache.
433             * </p>
434             */
435            protected static class BufferQueue {
436    
437                    public void enqueue(byte[] buffer) {
438                            BufferNode bufferNode = new BufferNode(buffer);
439    
440                            if (headBufferNode == null) {
441                                    headBufferNode = bufferNode;
442    
443                                    count = 1;
444    
445                                    return;
446                            }
447    
448                            BufferNode previousBufferNode = null;
449                            BufferNode currentBufferNode = headBufferNode;
450    
451                            while ((currentBufferNode != null) &&
452                                       (currentBufferNode.buffer.length >
453                                                    bufferNode.buffer.length)) {
454    
455                                    previousBufferNode = currentBufferNode;
456    
457                                    currentBufferNode = currentBufferNode.next;
458                            }
459    
460                            if (previousBufferNode == null) {
461                                    bufferNode.next = headBufferNode;
462    
463                                    headBufferNode = bufferNode;
464                            }
465                            else {
466                                    bufferNode.next = currentBufferNode;
467    
468                                    previousBufferNode.next = bufferNode;
469                            }
470    
471                            if (++count > THREADLOCAL_BUFFER_COUNT_LIMIT) {
472                                    if (previousBufferNode == null) {
473                                            previousBufferNode = headBufferNode;
474                                    }
475    
476                                    currentBufferNode = previousBufferNode.next;
477    
478                                    while (currentBufferNode.next != null) {
479                                            previousBufferNode = currentBufferNode;
480                                            currentBufferNode = currentBufferNode.next;
481                                    }
482    
483                                    // Dereference
484    
485                                    previousBufferNode.next = null;
486    
487                                    // Help GC
488    
489                                    currentBufferNode.buffer = null;
490                                    currentBufferNode.next = null;
491                            }
492                    }
493    
494                    public byte[] dequeue() {
495                            if (headBufferNode == null) {
496                                    return new byte[THREADLOCAL_BUFFER_SIZE_MIN];
497                            }
498    
499                            BufferNode bufferNode = headBufferNode;
500    
501                            headBufferNode = headBufferNode.next;
502    
503                            // Help GC
504    
505                            bufferNode.next = null;
506    
507                            return bufferNode.buffer;
508                    }
509    
510                    protected int count;
511                    protected BufferNode headBufferNode;
512    
513            }
514    
515            protected class BufferOutputStream extends OutputStream {
516    
517                    @Override
518                    public void write(byte[] bytes) {
519                            write(bytes, 0, bytes.length);
520                    }
521    
522                    @Override
523                    public void write(byte[] bytes, int offset, int length) {
524                            byte[] buffer = getBuffer(length);
525    
526                            System.arraycopy(bytes, offset, buffer, index, length);
527    
528                            index += length;
529                    }
530    
531                    @Override
532                    public void write(int b) {
533                            getBuffer(1)[index++] = (byte)b;
534                    }
535    
536            }
537    
538    }