/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ package com.liferay.portal.resiliency.spi.agent; import com.liferay.portal.kernel.io.BigEndianCodec; import com.liferay.portal.kernel.io.unsync.UnsyncByteArrayInputStream; import com.liferay.portal.kernel.io.unsync.UnsyncByteArrayOutputStream; import com.liferay.portal.kernel.model.Portlet; import com.liferay.portal.kernel.nio.intraband.Datagram; import com.liferay.portal.kernel.nio.intraband.mailbox.MailboxException; import com.liferay.portal.kernel.nio.intraband.mailbox.MailboxUtil; import com.liferay.portal.kernel.nio.intraband.test.MockIntraband; import com.liferay.portal.kernel.nio.intraband.test.MockRegistrationReference; import com.liferay.portal.kernel.resiliency.spi.agent.annotation.Direction; import com.liferay.portal.kernel.resiliency.spi.agent.annotation.DistributedRegistry; import com.liferay.portal.kernel.resiliency.spi.agent.annotation.MatchType; import com.liferay.portal.kernel.servlet.HttpHeaders; import com.liferay.portal.kernel.test.CaptureHandler; import com.liferay.portal.kernel.test.JDKLoggerTestUtil; import com.liferay.portal.kernel.test.ReflectionTestUtil; import com.liferay.portal.kernel.test.rule.AggregateTestRule; import com.liferay.portal.kernel.test.rule.CodeCoverageAssertor; import com.liferay.portal.kernel.test.rule.NewEnv; import com.liferay.portal.kernel.util.ClassLoaderPool; import com.liferay.portal.kernel.util.KeyValuePair; import com.liferay.portal.kernel.util.PropsKeys; import com.liferay.portal.kernel.util.PropsUtilAdvice; import com.liferay.portal.kernel.util.StringUtil; import com.liferay.portal.kernel.util.ThreadLocalDistributor; import com.liferay.portal.kernel.util.WebKeys; import com.liferay.portal.model.impl.PortletImpl; import com.liferay.portal.test.rule.AdviseWith; import com.liferay.portal.test.rule.AspectJNewEnvTestRule; import com.liferay.registry.BasicRegistryImpl; import com.liferay.registry.RegistryUtil; import java.io.EOFException; import java.io.IOException; import java.io.Serializable; import java.net.URL; import java.net.URLClassLoader; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Level; import java.util.logging.LogRecord; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.junit.Assert; import org.junit.Before; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpSession; /** * @author Shuyang Zhou */ public class SPIAgentSerializableTest { @ClassRule @Rule public static final AggregateTestRule aggregateTestRule = new AggregateTestRule( CodeCoverageAssertor.INSTANCE, AspectJNewEnvTestRule.INSTANCE); @Before public void setUp() { RegistryUtil.setRegistry(new BasicRegistryImpl()); Thread currentThread = Thread.currentThread(); _classLoader = new URLClassLoader( new URL[0], currentThread.getContextClassLoader()); ClassLoaderPool.register(_SERVLET_CONTEXT_NAME, _classLoader); } @Test public void testExtractDistributedRequestAttributes() { String distributedSerializable = "DISTRIBUTED_SERIALIZABLE"; DistributedRegistry.registerDistributed( distributedSerializable, Direction.DUPLEX, MatchType.EXACT); final String distributedNonserializable = "DISTRIBUTED_NONSERIALIZABLE"; DistributedRegistry.registerDistributed( distributedNonserializable, Direction.DUPLEX, MatchType.EXACT); MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest(); mockHttpServletRequest.setAttribute( distributedNonserializable, new Object() { @Override public String toString() { return distributedNonserializable; } }); mockHttpServletRequest.setAttribute( distributedSerializable, distributedSerializable); String nondistributed = "NONDISTRIBUTED"; mockHttpServletRequest.setAttribute(nondistributed, nondistributed); try (CaptureHandler captureHandler = JDKLoggerTestUtil.configureJDKLogger( SPIAgentSerializable.class.getName(), Level.OFF)) { // Without log List<LogRecord> logRecords = captureHandler.getLogRecords(); Map<String, Serializable> distributedRequestAttributes = SPIAgentSerializable.extractDistributedRequestAttributes( mockHttpServletRequest, Direction.DUPLEX); Assert.assertTrue(logRecords.isEmpty()); Assert.assertEquals( distributedRequestAttributes.toString(), 1, distributedRequestAttributes.size()); Assert.assertEquals( distributedSerializable, distributedRequestAttributes.get(distributedSerializable)); // With log, warn logRecords = captureHandler.resetLogLevel(Level.WARNING); distributedRequestAttributes = SPIAgentSerializable.extractDistributedRequestAttributes( mockHttpServletRequest, Direction.DUPLEX); Assert.assertEquals(logRecords.toString(), 1, logRecords.size()); LogRecord logRecord = logRecords.get(0); Assert.assertEquals( "Nonserializable distributed request attribute name " + distributedNonserializable + " with value " + distributedNonserializable, logRecord.getMessage()); Assert.assertEquals( distributedRequestAttributes.toString(), 1, distributedRequestAttributes.size()); Assert.assertEquals( distributedSerializable, distributedRequestAttributes.get(distributedSerializable)); // With log, debug logRecords = captureHandler.resetLogLevel(Level.FINE); distributedRequestAttributes = SPIAgentSerializable.extractDistributedRequestAttributes( mockHttpServletRequest, Direction.DUPLEX); Assert.assertEquals(logRecords.toString(), 2, logRecords.size()); logRecord = logRecords.get(0); Assert.assertEquals( "Nonserializable distributed request attribute name " + distributedNonserializable + " with value " + distributedNonserializable, logRecord.getMessage()); logRecord = logRecords.get(1); Assert.assertEquals( "Nondistributed request attribute name " + nondistributed + " with direction DUPLEX and value " + nondistributed, logRecord.getMessage()); Assert.assertEquals( distributedRequestAttributes.toString(), 1, distributedRequestAttributes.size()); Assert.assertEquals( distributedSerializable, distributedRequestAttributes.get(distributedSerializable)); } } @Test public void testExtractRequestHeaders() { final String nullHeaderName = "nullHeaderName"; MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest() { @Override public Enumeration<String> getHeaders(String name) { if (nullHeaderName.equals(name)) { return null; } return super.getHeaders(name); } }; mockHttpServletRequest.addHeader(HttpHeaders.ACCEPT_ENCODING, "x-zip"); mockHttpServletRequest.addHeader(HttpHeaders.COOKIE, "a=b"); mockHttpServletRequest.addHeader(nullHeaderName, nullHeaderName); Map<String, List<String>> headers = SPIAgentSerializable.extractRequestHeaders(mockHttpServletRequest); Assert.assertTrue(headers.isEmpty()); String emptyHeaderName = "emptyHeaderName"; mockHttpServletRequest.addHeader( emptyHeaderName, Collections.emptyList()); String headerName = "headerName"; List<String> headerValues = Arrays.asList("header1", "header2"); mockHttpServletRequest.addHeader(headerName, headerValues); headers = SPIAgentSerializable.extractRequestHeaders( mockHttpServletRequest); Assert.assertEquals(headers.toString(), 2, headers.size()); List<String> emptyHeaders = headers.get( StringUtil.toLowerCase(emptyHeaderName)); Assert.assertNotNull(emptyHeaders); Assert.assertTrue(emptyHeaders.isEmpty()); List<String> actualHeaderValues = headers.get( StringUtil.toLowerCase(headerName)); Assert.assertNotNull(actualHeaderValues); Assert.assertTrue(headerValues.equals(actualHeaderValues)); } @Test public void testExtractSessionAttributes() { try (CaptureHandler captureHandler = JDKLoggerTestUtil.configureJDKLogger( SPIAgentSerializable.class.getName(), Level.OFF)) { // Without log, no portlet session List<LogRecord> logRecords = captureHandler.getLogRecords(); MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest(); MockHttpSession mockHttpSession = new MockHttpSession(); String serializeableAttribute = "serializeableAttribute"; mockHttpSession.setAttribute( serializeableAttribute, serializeableAttribute); final String servletContextName1 = "servletContextName1"; String portletSessionAttributesName1 = WebKeys.PORTLET_SESSION_ATTRIBUTES.concat(servletContextName1); mockHttpSession.setAttribute( portletSessionAttributesName1, portletSessionAttributesName1); String servletContextName2 = "servletContextName2"; String portletSessionAttributesName2 = WebKeys.PORTLET_SESSION_ATTRIBUTES.concat(servletContextName2); mockHttpSession.setAttribute( portletSessionAttributesName2, portletSessionAttributesName2); final String nonserializableAttribute = "nonserializableAttribute"; mockHttpSession.setAttribute( nonserializableAttribute, new Object() { @Override public String toString() { return nonserializableAttribute; } }); Portlet portlet = new PortletImpl() { @Override public String getContextName() { return servletContextName1; } }; mockHttpServletRequest.setAttribute( WebKeys.SPI_AGENT_PORTLET, portlet); mockHttpServletRequest.setSession(mockHttpSession); Map<String, Serializable> sessionAttributes = SPIAgentSerializable.extractSessionAttributes( mockHttpServletRequest); Assert.assertTrue(logRecords.isEmpty()); Assert.assertEquals( sessionAttributes.toString(), 2, sessionAttributes.size()); Assert.assertEquals( serializeableAttribute, sessionAttributes.get(serializeableAttribute)); Assert.assertEquals( portletSessionAttributesName1, sessionAttributes.get(portletSessionAttributesName1)); // Without log, with empty portlet session MockHttpSession portletMockHttpSession = new MockHttpSession(); mockHttpServletRequest.setAttribute( WebKeys.PORTLET_SESSION, portletMockHttpSession); sessionAttributes = SPIAgentSerializable.extractSessionAttributes( mockHttpServletRequest); Assert.assertNull( mockHttpServletRequest.getAttribute(WebKeys.PORTLET_SESSION)); Assert.assertTrue(logRecords.isEmpty()); Assert.assertEquals( sessionAttributes.toString(), 2, sessionAttributes.size()); Assert.assertEquals( serializeableAttribute, sessionAttributes.get(serializeableAttribute)); Map<String, Serializable> portletSessionAttributes = (Map<String, Serializable>)sessionAttributes.get( portletSessionAttributesName1); Assert.assertNotNull(portletSessionAttributes); Assert.assertTrue(portletSessionAttributes.isEmpty()); // Without log, with nonempty portlet session portletMockHttpSession.setAttribute( serializeableAttribute, serializeableAttribute); portletMockHttpSession.setAttribute( nonserializableAttribute, new Object() { @Override public String toString() { return nonserializableAttribute; } }); mockHttpServletRequest.setAttribute( WebKeys.PORTLET_SESSION, portletMockHttpSession); sessionAttributes = SPIAgentSerializable.extractSessionAttributes( mockHttpServletRequest); Assert.assertNull( mockHttpServletRequest.getAttribute(WebKeys.PORTLET_SESSION)); Assert.assertTrue(logRecords.isEmpty()); Assert.assertEquals( sessionAttributes.toString(), 2, sessionAttributes.size()); Assert.assertEquals( serializeableAttribute, sessionAttributes.get(serializeableAttribute)); portletSessionAttributes = (Map<String, Serializable>)sessionAttributes.get( portletSessionAttributesName1); Assert.assertNotNull(portletSessionAttributes); Assert.assertEquals( portletSessionAttributes.toString(), 1, portletSessionAttributes.size()); Assert.assertEquals( serializeableAttribute, portletSessionAttributes.get(serializeableAttribute)); // With log, no portlet session logRecords = captureHandler.resetLogLevel(Level.WARNING); sessionAttributes = SPIAgentSerializable.extractSessionAttributes( mockHttpServletRequest); Assert.assertEquals(logRecords.toString(), 1, logRecords.size()); LogRecord logRecord = logRecords.get(0); Assert.assertEquals( "Nonserializable session attribute name " + nonserializableAttribute + " with value " + nonserializableAttribute, logRecord.getMessage()); Assert.assertEquals( sessionAttributes.toString(), 2, sessionAttributes.size()); Assert.assertEquals( serializeableAttribute, sessionAttributes.get(serializeableAttribute)); Assert.assertEquals( portletSessionAttributesName1, sessionAttributes.get(portletSessionAttributesName1)); // With log, with nonempty portlet session logRecords = captureHandler.resetLogLevel(Level.WARNING); mockHttpServletRequest.setAttribute( WebKeys.PORTLET_SESSION, portletMockHttpSession); sessionAttributes = SPIAgentSerializable.extractSessionAttributes( mockHttpServletRequest); Assert.assertNull( mockHttpServletRequest.getAttribute(WebKeys.PORTLET_SESSION)); Assert.assertEquals(logRecords.toString(), 2, logRecords.size()); logRecord = logRecords.get(0); Assert.assertEquals( "Nonserializable session attribute name " + nonserializableAttribute + " with value " + nonserializableAttribute, logRecord.getMessage()); logRecord = logRecords.get(1); Assert.assertEquals( "Nonserializable session attribute name " + nonserializableAttribute + " with value " + nonserializableAttribute, logRecord.getMessage()); Assert.assertEquals( sessionAttributes.toString(), 2, sessionAttributes.size()); Assert.assertEquals( serializeableAttribute, sessionAttributes.get(serializeableAttribute)); portletSessionAttributes = (Map<String, Serializable>)sessionAttributes.get( portletSessionAttributesName1); Assert.assertNotNull(portletSessionAttributes); Assert.assertEquals( portletSessionAttributes.toString(), 1, portletSessionAttributes.size()); Assert.assertEquals( serializeableAttribute, portletSessionAttributes.get(serializeableAttribute)); } } @AdviseWith( adviceClasses = {DeserializerAdvice.class, PropsUtilAdvice.class} ) @NewEnv(type = NewEnv.Type.CLASSLOADER) @Test public void testSerialization() throws IOException { // Unable to send PropsUtilAdvice.setProps( PropsKeys.INTRABAND_MAILBOX_REAPER_THREAD_ENABLED, Boolean.FALSE.toString()); PropsUtilAdvice.setProps( PropsKeys.INTRABAND_MAILBOX_STORAGE_LIFE, String.valueOf(Long.MAX_VALUE)); final AtomicLong receiptReference = new AtomicLong(); MockIntraband mockIntraband = new MockIntraband() { @Override protected Datagram processDatagram(Datagram datagram) { try { long receipt = ReflectionTestUtil.invoke( MailboxUtil.class, "depositMail", new Class<?>[] {ByteBuffer.class}, datagram.getDataByteBuffer()); receiptReference.set(receipt); byte[] data = new byte[8]; BigEndianCodec.putLong(data, 0, receipt); return Datagram.createResponseDatagram( datagram, ByteBuffer.wrap(data)); } catch (Exception e) { throw new RuntimeException(e); } } }; SPIAgentSerializable agentSerializable = new SPIAgentSerializable( _SERVLET_CONTEXT_NAME); IOException ioException = new IOException(); mockIntraband.setIOException(ioException); try { agentSerializable.writeTo( new MockRegistrationReference(mockIntraband), new UnsyncByteArrayOutputStream()); Assert.fail(); } catch (IOException ioe) { Throwable throwable = ioe.getCause(); Assert.assertSame(MailboxException.class, throwable.getClass()); Assert.assertSame(ioException, throwable.getCause()); } // Successfully send UnsyncByteArrayOutputStream unsyncByteArrayOutputStream = new UnsyncByteArrayOutputStream(); mockIntraband.setIOException(null); agentSerializable.writeTo( new MockRegistrationReference(mockIntraband), unsyncByteArrayOutputStream); long actualReceipt = BigEndianCodec.getLong( unsyncByteArrayOutputStream.unsafeGetByteArray(), 0); Assert.assertEquals(receiptReference.get(), actualReceipt); // Incomplete receipt try { SPIAgentSerializable.readFrom( new UnsyncByteArrayInputStream(new byte[7])); Assert.fail(); } catch (EOFException eofe) { } // No such receipt byte[] badReceiptData = new byte[8]; BigEndianCodec.putLong(badReceiptData, 0, actualReceipt + 1); try { SPIAgentSerializable.readFrom( new UnsyncByteArrayInputStream(badReceiptData)); Assert.fail(); } catch (IllegalArgumentException iae) { Assert.assertEquals( "No mail with receipt " + (actualReceipt + 1), iae.getMessage()); } // Class not found ClassLoader incapableClassLoader = new ClassLoader() { @Override public Class<?> loadClass(String name) throws ClassNotFoundException { if (name.equals(SPIAgentSerializable.class.getName())) { throw new ClassNotFoundException(); } return super.loadClass(name); } }; ClassLoader oldClassLoader = ClassLoaderPool.getClassLoader( _SERVLET_CONTEXT_NAME); ClassLoaderPool.register(_SERVLET_CONTEXT_NAME, incapableClassLoader); byte[] receiptData = new byte[8]; BigEndianCodec.putLong(receiptData, 0, actualReceipt); try { SPIAgentSerializable.readFrom( new UnsyncByteArrayInputStream(receiptData)); Assert.fail(); } catch (IOException ioe) { Throwable throwable = ioe.getCause(); Assert.assertSame( ClassNotFoundException.class, throwable.getClass()); } finally { ClassLoaderPool.register(_SERVLET_CONTEXT_NAME, oldClassLoader); } // Successfully receive unsyncByteArrayOutputStream = new UnsyncByteArrayOutputStream(); agentSerializable.writeTo( new MockRegistrationReference(mockIntraband), unsyncByteArrayOutputStream); actualReceipt = BigEndianCodec.getLong( unsyncByteArrayOutputStream.unsafeGetByteArray(), 0); Assert.assertEquals(receiptReference.get(), actualReceipt); BigEndianCodec.putLong(receiptData, 0, actualReceipt); SPIAgentSerializable receivedAgentSerializable = SPIAgentSerializable.readFrom( new UnsyncByteArrayInputStream(receiptData)); Assert.assertNotNull(receivedAgentSerializable); Assert.assertSame( _classLoader, DeserializerAdvice.getContextClassLoader()); } @Test public void testThreadLocalTransfer() throws Exception { ThreadLocalDistributor threadLocalDistributor = new ThreadLocalDistributor(); threadLocalDistributor.setThreadLocalSources( Arrays.asList( new KeyValuePair( SPIAgentSerializableTest.class.getName(), "_threadLocal"))); threadLocalDistributor.afterPropertiesSet(); Serializable[] serializables = ReflectionTestUtil.getFieldValue( threadLocalDistributor, "_threadLocalValues"); Assert.assertNotNull(serializables); Assert.assertEquals( Arrays.toString(serializables), 1, serializables.length); Assert.assertNull(serializables[0]); String threadLocalValue = "threadLocalValue"; _threadLocal.set(threadLocalValue); SPIAgentSerializable agentSerializable = new SPIAgentSerializable( _SERVLET_CONTEXT_NAME); Assert.assertNull(agentSerializable.threadLocalDistributors); agentSerializable.captureThreadLocals(); ThreadLocalDistributor[] threadLocalDistributors = agentSerializable.threadLocalDistributors; Assert.assertNotNull(threadLocalDistributors); Assert.assertEquals( Arrays.toString(threadLocalDistributors), 1, threadLocalDistributors.length); Assert.assertSame(threadLocalDistributor, threadLocalDistributors[0]); serializables = ReflectionTestUtil.getFieldValue( threadLocalDistributor, "_threadLocalValues"); Assert.assertNotNull(serializables); Assert.assertEquals( Arrays.toString(serializables), 1, serializables.length); Assert.assertEquals(threadLocalValue, serializables[0]); _threadLocal.remove(); agentSerializable.restoreThreadLocals(); Assert.assertEquals(threadLocalValue, _threadLocal.get()); _threadLocal.remove(); } @Aspect public static class DeserializerAdvice { public static ClassLoader getContextClassLoader() { return _contextClassLoader; } @Around( "execution(public * " + "com.liferay.portal.kernel.io.Deserializer.readObject())" ) public Object readObject(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { Thread currentThread = Thread.currentThread(); _contextClassLoader = currentThread.getContextClassLoader(); return proceedingJoinPoint.proceed(); } private static ClassLoader _contextClassLoader; } private static final String _SERVLET_CONTEXT_NAME = "SERVLET_CONTEXT_NAME"; private static final ThreadLocal<String> _threadLocal = new ThreadLocal<>(); private ClassLoader _classLoader; }