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                    BigEndianCodec.putBoolean(buffer, index++, asciiCode);
271    
272                    if (asciiCode) {
273                            byte[] buffer = getBuffer(length + 4);
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 + 4);
287    
288                            BigEndianCodec.putInt(buffer, index, length);
289    
290                            index += 4;
291    
292                            for (int i = 0; i < length; i++) {
293                                    char c = s.charAt(i);
294    
295                                    BigEndianCodec.putChar(buffer, index, c);
296    
297                                    index += 2;
298                            }
299                    }
300            }
301    
302            public void writeTo(OutputStream outputStream) throws IOException {
303                    outputStream.write(buffer, 0, index);
304    
305                    if (buffer.length <= THREADLOCAL_BUFFER_SIZE_LIMIT) {
306                            BufferQueue bufferQueue = bufferQueueThreadLocal.get();
307    
308                            bufferQueue.enqueue(buffer);
309                    }
310    
311                    buffer = null;
312            }
313    
314            /**
315             * Returns the required buffer length. This method is final so JIT can
316             * perform an inline expansion.
317             *
318             * @param  ensureExtraSpace the extra byte space required to meet the
319             *         buffer's minimum length
320             * @return the buffer value
321             */
322            protected final byte[] getBuffer(int ensureExtraSpace) {
323                    int minSize = index + ensureExtraSpace;
324    
325                    if (minSize < 0) {
326    
327                            // Cannot create byte[] with length longer than Integer.MAX_VALUE
328    
329                            throw new OutOfMemoryError();
330                    }
331    
332                    int oldSize = buffer.length;
333    
334                    if (minSize > oldSize) {
335                            int newSize = oldSize << 1;
336    
337                            if (newSize < minSize) {
338    
339                                    // Overflow and insufficient growth protection
340    
341                                    newSize = minSize;
342                            }
343    
344                            buffer = Arrays.copyOf(buffer, newSize);
345                    }
346    
347                    return buffer;
348            }
349    
350            /**
351             * Softens the local thread's pooled buffer memory.
352             *
353             * <p>
354             * Technically, we should soften each pooled buffer individually to achieve
355             * the best garbage collection (GC) interaction. However, that increases
356             * complexity of pooled buffer access and also burdens the GC's {@link
357             * java.lang.ref.SoftReference} process, hurting performance.
358             * </p>
359             *
360             * <p>
361             * Here, the entire ThreadLocal BufferQueue is softened. For threads that do
362             * serializing often, its BufferQueue will most likely stay valid. For
363             * threads that do serializing only occasionally, its BufferQueue will most
364             * likely be released by GC.
365             * </p>
366             */
367            protected static final ThreadLocal<BufferQueue> bufferQueueThreadLocal =
368                    new SoftReferenceThreadLocal<BufferQueue>() {
369    
370                    @Override
371                    protected BufferQueue initialValue() {
372                            return new BufferQueue();
373                    }
374    
375            };
376    
377            protected static final int THREADLOCAL_BUFFER_COUNT_LIMIT;
378    
379            protected static final int THREADLOCAL_BUFFER_COUNT_MIN = 8;
380    
381            protected static final int THREADLOCAL_BUFFER_SIZE_LIMIT;
382    
383            protected static final int THREADLOCAL_BUFFER_SIZE_MIN = 16 * 1024;
384    
385            static {
386                    int threadLocalBufferCountLimit = GetterUtil.getInteger(
387                            System.getProperty(
388                                    Serializer.class.getName() +
389                                            ".thread.local.buffer.count.limit"));
390    
391                    if (threadLocalBufferCountLimit < THREADLOCAL_BUFFER_COUNT_MIN) {
392                            threadLocalBufferCountLimit = THREADLOCAL_BUFFER_COUNT_MIN;
393                    }
394    
395                    THREADLOCAL_BUFFER_COUNT_LIMIT = threadLocalBufferCountLimit;
396    
397                    int threadLocalBufferSizeLimit = GetterUtil.getInteger(
398                            System.getProperty(
399                                    Serializer.class.getName() +
400                                            ".thread.local.buffer.size.limit"));
401    
402                    if (threadLocalBufferSizeLimit < THREADLOCAL_BUFFER_SIZE_MIN) {
403                            threadLocalBufferSizeLimit = THREADLOCAL_BUFFER_SIZE_MIN;
404                    }
405    
406                    THREADLOCAL_BUFFER_SIZE_LIMIT = threadLocalBufferSizeLimit;
407            }
408    
409            protected byte[] buffer;
410            protected int index;
411    
412            protected static class BufferNode {
413    
414                    public BufferNode(byte[] buffer) {
415                            this.buffer = buffer;
416                    }
417    
418                    protected byte[] buffer;
419                    protected BufferNode next;
420    
421            }
422    
423            /**
424             * Represents a descending <code>byte[]</code> queue ordered by array length.
425             *
426             * <p>
427             * The queue is small enough to simply use a linear scan search for
428             * maintaining its order. The entire queue data is held by a
429             * {@link java.lang.ref.SoftReference}, so when necessary, GC can release the whole
430             * buffer cache.
431             * </p>
432             */
433            protected static class BufferQueue {
434    
435                    public void enqueue(byte[] buffer) {
436                            BufferNode bufferNode = new BufferNode(buffer);
437    
438                            if (headBufferNode == null) {
439                                    headBufferNode = bufferNode;
440    
441                                    count = 1;
442    
443                                    return;
444                            }
445    
446                            BufferNode previousBufferNode = null;
447                            BufferNode currentBufferNode = headBufferNode;
448    
449                            while ((currentBufferNode != null) &&
450                                       (currentBufferNode.buffer.length >
451                                                    bufferNode.buffer.length)) {
452    
453                                    previousBufferNode = currentBufferNode;
454    
455                                    currentBufferNode = currentBufferNode.next;
456                            }
457    
458                            if (previousBufferNode == null) {
459                                    bufferNode.next = headBufferNode;
460    
461                                    headBufferNode = bufferNode;
462                            }
463                            else {
464                                    bufferNode.next = currentBufferNode;
465    
466                                    previousBufferNode.next = bufferNode;
467                            }
468    
469                            if (++count > THREADLOCAL_BUFFER_COUNT_LIMIT) {
470                                    if (previousBufferNode == null) {
471                                            previousBufferNode = headBufferNode;
472                                    }
473    
474                                    currentBufferNode = previousBufferNode.next;
475    
476                                    while (currentBufferNode.next != null) {
477                                            previousBufferNode = currentBufferNode;
478                                            currentBufferNode = currentBufferNode.next;
479                                    }
480    
481                                    // Dereference
482    
483                                    previousBufferNode.next = null;
484    
485                                    // Help GC
486    
487                                    currentBufferNode.buffer = null;
488                                    currentBufferNode.next = null;
489                            }
490                    }
491    
492                    public byte[] dequeue() {
493                            if (headBufferNode == null) {
494                                    return new byte[THREADLOCAL_BUFFER_SIZE_MIN];
495                            }
496    
497                            BufferNode bufferNode = headBufferNode;
498    
499                            headBufferNode = headBufferNode.next;
500    
501                            // Help GC
502    
503                            bufferNode.next = null;
504    
505                            return bufferNode.buffer;
506                    }
507    
508                    protected int count;
509                    protected BufferNode headBufferNode;
510    
511            }
512    
513            protected class BufferOutputStream extends OutputStream {
514    
515                    @Override
516                    public void write(byte[] bytes) {
517                            write(bytes, 0, bytes.length);
518                    }
519    
520                    @Override
521                    public void write(byte[] bytes, int offset, int length) {
522                            byte[] buffer = getBuffer(length);
523    
524                            System.arraycopy(bytes, offset, buffer, index, length);
525    
526                            index += length;
527                    }
528    
529                    @Override
530                    public void write(int b) {
531                            getBuffer(1)[index++] = (byte)b;
532                    }
533    
534            }
535    
536    }