001    /**
002     * Copyright (c) 2000-2012 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 Short) {
189                            writeByte(SerializationConstants.TC_SHORT);
190                            writeShort((Short)serializable);
191    
192                            return;
193                    }
194                    else if (serializable instanceof Character) {
195                            writeByte(SerializationConstants.TC_CHARACTER);
196                            writeChar((Character)serializable);
197    
198                            return;
199                    }
200                    else if (serializable instanceof Byte) {
201                            writeByte(SerializationConstants.TC_BYTE);
202                            writeByte((Byte)serializable);
203    
204                            return;
205                    }
206                    else if (serializable instanceof Double) {
207                            writeByte(SerializationConstants.TC_DOUBLE);
208                            writeDouble((Double)serializable);
209    
210                            return;
211                    }
212                    else if (serializable instanceof Float) {
213                            writeByte(SerializationConstants.TC_FLOAT);
214                            writeFloat((Float)serializable);
215    
216                            return;
217                    }
218                    else {
219                            writeByte(SerializationConstants.TC_CONTEXT_NAME);
220                    }
221    
222                    ClassLoader classLoader = serializable.getClass().getClassLoader();
223    
224                    String contextName = ClassLoaderPool.getContextName(classLoader);
225    
226                    writeString(contextName);
227    
228                    try {
229                            ObjectOutputStream objectOutputStream = new ObjectOutputStream(
230                                    new BufferOutputStream());
231    
232                            objectOutputStream.writeObject(serializable);
233    
234                            objectOutputStream.close();
235                    }
236                    catch (IOException ioe) {
237                            throw new RuntimeException(
238                                    "Unable to write ordinary serializable object " + serializable,
239                                    ioe);
240                    }
241            }
242    
243            public void writeShort(short s) {
244                    BigEndianCodec.putShort(getBuffer(2), index, s);
245    
246                    index += 2;
247            }
248    
249            public void writeString(String s) {
250                    int length = s.length();
251    
252                    boolean asciiCode = true;
253    
254                    for (int i = 0; i < length; i++) {
255                            char c = s.charAt(i);
256    
257                            if ((c == 0) || (c > 127)) {
258                                    asciiCode = false;
259                                    break;
260                            }
261                    }
262    
263                    BigEndianCodec.putBoolean(buffer, index++, asciiCode);
264    
265                    if (asciiCode) {
266                            byte[] buffer = getBuffer(length + 4);
267    
268                            BigEndianCodec.putInt(buffer, index, length);
269    
270                            index += 4;
271    
272                            for (int i = 0; i < length; i++) {
273                                    char c = s.charAt(i);
274    
275                                    buffer[index++] = (byte)c;
276                            }
277                    }
278                    else {
279                            byte[] buffer = getBuffer(length * 2 + 4);
280    
281                            BigEndianCodec.putInt(buffer, index, length);
282    
283                            index += 4;
284    
285                            for (int i = 0; i < length; i++) {
286                                    char c = s.charAt(i);
287    
288                                    BigEndianCodec.putChar(buffer, index, c);
289    
290                                    index += 2;
291                            }
292                    }
293            }
294    
295            public void writeTo(OutputStream outputStream) throws IOException {
296                    outputStream.write(buffer, 0, index);
297    
298                    if (buffer.length <= THREADLOCAL_BUFFER_SIZE_LIMIT) {
299                            BufferQueue bufferQueue = bufferQueueThreadLocal.get();
300    
301                            bufferQueue.enqueue(buffer);
302                    }
303    
304                    buffer = null;
305            }
306    
307            /**
308             * Returns the required buffer length. This method is final so JIT can
309             * perform an inline expansion.
310             *
311             * @param  ensureExtraSpace the extra byte space required to meet the
312             *         buffer's minimum length
313             * @return the buffer value
314             */
315            protected final byte[] getBuffer(int ensureExtraSpace) {
316                    int minSize = index + ensureExtraSpace;
317    
318                    if (minSize < 0) {
319    
320                            // Cannot create byte[] with length longer than Integer.MAX_VALUE
321    
322                            throw new OutOfMemoryError();
323                    }
324    
325                    int oldSize = buffer.length;
326    
327                    if (minSize > oldSize) {
328                            int newSize = oldSize << 1;
329    
330                            if (newSize < minSize) {
331    
332                                    // Overflow and insufficient growth protection
333    
334                                    newSize = minSize;
335                            }
336    
337                            buffer = Arrays.copyOf(buffer, newSize);
338                    }
339    
340                    return buffer;
341            }
342    
343            /**
344             * Softens the local thread's pooled buffer memory.
345             *
346             * <p>
347             * Technically, we should soften each pooled buffer individually to achieve
348             * the best garbage collection (GC) interaction. However, that increases
349             * complexity of pooled buffer access and also burdens the GC's {@link
350             * java.lang.ref.SoftReference} process, hurting performance.
351             * </p>
352             *
353             * <p>
354             * Here, the entire ThreadLocal BufferQueue is softened. For threads that do
355             * serializing often, its BufferQueue will most likely stay valid. For
356             * threads that do serializing only occasionally, its BufferQueue will most
357             * likely be released by GC.
358             * </p>
359             */
360            protected static final ThreadLocal<BufferQueue> bufferQueueThreadLocal =
361                    new SoftReferenceThreadLocal<BufferQueue>() {
362    
363                    @Override
364                    protected BufferQueue initialValue() {
365                            return new BufferQueue();
366                    }
367    
368            };
369    
370            protected static final int THREADLOCAL_BUFFER_COUNT_LIMIT;
371    
372            protected static final int THREADLOCAL_BUFFER_COUNT_MIN = 8;
373    
374            protected static final int THREADLOCAL_BUFFER_SIZE_LIMIT;
375    
376            protected static final int THREADLOCAL_BUFFER_SIZE_MIN = 16 * 1024;
377    
378            static {
379                    int threadLocalBufferCountLimit = GetterUtil.getInteger(
380                            System.getProperty(
381                                    Serializer.class.getName() +
382                                            ".thread.local.buffer.count.limit"));
383    
384                    if (threadLocalBufferCountLimit < THREADLOCAL_BUFFER_COUNT_MIN) {
385                            threadLocalBufferCountLimit = THREADLOCAL_BUFFER_COUNT_MIN;
386                    }
387    
388                    THREADLOCAL_BUFFER_COUNT_LIMIT = threadLocalBufferCountLimit;
389    
390                    int threadLocalBufferSizeLimit = GetterUtil.getInteger(
391                            System.getProperty(
392                                    Serializer.class.getName() +
393                                            ".thread.local.buffer.size.limit"));
394    
395                    if (threadLocalBufferSizeLimit < THREADLOCAL_BUFFER_SIZE_MIN) {
396                            threadLocalBufferSizeLimit = THREADLOCAL_BUFFER_SIZE_MIN;
397                    }
398    
399                    THREADLOCAL_BUFFER_SIZE_LIMIT = threadLocalBufferSizeLimit;
400            }
401    
402            protected byte[] buffer;
403            protected int index;
404    
405            protected static class BufferNode {
406    
407                    public BufferNode(byte[] buffer) {
408                            this.buffer = buffer;
409                    }
410    
411                    protected byte[] buffer;
412                    protected BufferNode next;
413    
414            }
415    
416            /**
417             * Represents a descending <code>byte[]</code> queue ordered by array length.
418             *
419             * <p>
420             * The queue is small enough to simply use a linear scan search for
421             * maintaining its order. The entire queue data is held by a
422             * {@link java.lang.ref.SoftReference}, so when necessary, GC can release the whole
423             * buffer cache.
424             * </p>
425             */
426            protected static class BufferQueue {
427    
428                    public void enqueue(byte[] buffer) {
429                            BufferNode bufferNode = new BufferNode(buffer);
430    
431                            if (headBufferNode == null) {
432                                    headBufferNode = bufferNode;
433    
434                                    count = 1;
435    
436                                    return;
437                            }
438    
439                            BufferNode previousBufferNode = null;
440                            BufferNode currentBufferNode = headBufferNode;
441    
442                            while ((currentBufferNode != null) &&
443                                       (currentBufferNode.buffer.length >
444                                                    bufferNode.buffer.length)) {
445    
446                                    previousBufferNode = currentBufferNode;
447    
448                                    currentBufferNode = currentBufferNode.next;
449                            }
450    
451                            if (previousBufferNode == null) {
452                                    bufferNode.next = headBufferNode;
453    
454                                    headBufferNode = bufferNode;
455                            }
456                            else {
457                                    bufferNode.next = currentBufferNode;
458    
459                                    previousBufferNode.next = bufferNode;
460                            }
461    
462                            if (++count > THREADLOCAL_BUFFER_COUNT_LIMIT) {
463                                    if (previousBufferNode == null) {
464                                            previousBufferNode = headBufferNode;
465                                    }
466    
467                                    currentBufferNode = previousBufferNode.next;
468    
469                                    while (currentBufferNode.next != null) {
470                                            previousBufferNode = currentBufferNode;
471                                            currentBufferNode = currentBufferNode.next;
472                                    }
473    
474                                    // Dereference
475    
476                                    previousBufferNode.next = null;
477    
478                                    // Help GC
479    
480                                    currentBufferNode.buffer = null;
481                                    currentBufferNode.next = null;
482                            }
483                    }
484    
485                    public byte[] dequeue() {
486                            if (headBufferNode == null) {
487                                    return new byte[THREADLOCAL_BUFFER_SIZE_MIN];
488                            }
489    
490                            BufferNode bufferNode = headBufferNode;
491    
492                            headBufferNode = headBufferNode.next;
493    
494                            // Help GC
495    
496                            bufferNode.next = null;
497    
498                            return bufferNode.buffer;
499                    }
500    
501                    protected int count;
502                    protected BufferNode headBufferNode;
503    
504            }
505    
506            protected class BufferOutputStream extends OutputStream {
507    
508                    @Override
509                    public void write(byte[] bytes) {
510                            write(bytes, 0, bytes.length);
511                    }
512    
513                    @Override
514                    public void write(byte[] bytes, int offset, int length) {
515                            byte[] buffer = getBuffer(length);
516    
517                            System.arraycopy(bytes, offset, buffer, index, length);
518    
519                            index += length;
520                    }
521    
522                    @Override
523                    public void write(int b) {
524                            getBuffer(1)[index++] = (byte)b;
525                    }
526    
527            }
528    
529    }