/*
 * Copyright 2002-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.jms.remoting;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Enumeration;
import javax.jms.CompletionListener;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageProducer;
import javax.jms.ObjectMessage;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSession;
import javax.jms.Session;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import org.springframework.jms.support.converter.MessageConversionException;
import org.springframework.jms.support.converter.SimpleMessageConverter;
import org.springframework.remoting.RemoteTimeoutException;
import org.springframework.tests.sample.beans.ITestBean;
import org.springframework.tests.sample.beans.TestBean;

import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*;

/**
 * @author Juergen Hoeller
 * @author Stephane Nicoll
 */
public class JmsInvokerTests {

	@Rule
	public final ExpectedException thrown = ExpectedException.none();

	private QueueConnectionFactory mockConnectionFactory;

	private QueueConnection mockConnection;

	private QueueSession mockSession;

	private Queue mockQueue;


	@Before
	public void setUpMocks() throws Exception {
		mockConnectionFactory = mock(QueueConnectionFactory.class);
		mockConnection = mock(QueueConnection.class);
		mockSession = mock(QueueSession.class);
		mockQueue = mock(Queue.class);

		given(mockConnectionFactory.createConnection()).willReturn(mockConnection);
		given(mockConnection.createSession(false, Session.AUTO_ACKNOWLEDGE)).willReturn(mockSession);
	}


	@Test
	public void testJmsInvokerProxyFactoryBeanAndServiceExporter() throws Throwable {
		doTestJmsInvokerProxyFactoryBeanAndServiceExporter(false);
	}

	@Test
	public void testJmsInvokerProxyFactoryBeanAndServiceExporterWithDynamicQueue() throws Throwable {
		given(mockSession.createQueue("myQueue")).willReturn(mockQueue);
		doTestJmsInvokerProxyFactoryBeanAndServiceExporter(true);
	}

	@Test
	public void receiveTimeoutExpired() {
		JmsInvokerProxyFactoryBean pfb = new JmsInvokerProxyFactoryBean() {
			@Override
			protected Message doExecuteRequest(Session session, Queue queue, Message requestMessage) throws JMSException {
				return null; // faking no message received
			}
		};
		pfb.setServiceInterface(ITestBean.class);
		pfb.setConnectionFactory(this.mockConnectionFactory);
		pfb.setQueue(this.mockQueue);
		pfb.setReceiveTimeout(1500);
		pfb.afterPropertiesSet();
		ITestBean proxy = (ITestBean) pfb.getObject();

		thrown.expect(RemoteTimeoutException.class);
		thrown.expectMessage("1500 ms");
		thrown.expectMessage("getAge");
		proxy.getAge();
	}

	private void doTestJmsInvokerProxyFactoryBeanAndServiceExporter(boolean dynamicQueue) throws Throwable {
		TestBean target = new TestBean("myname", 99);

		final JmsInvokerServiceExporter exporter = new JmsInvokerServiceExporter();
		exporter.setServiceInterface(ITestBean.class);
		exporter.setService(target);
		exporter.setMessageConverter(new MockSimpleMessageConverter());
		exporter.afterPropertiesSet();

		JmsInvokerProxyFactoryBean pfb = new JmsInvokerProxyFactoryBean() {
			@Override
			protected Message doExecuteRequest(Session session, Queue queue, Message requestMessage) throws JMSException {
				Session mockExporterSession = mock(Session.class);
				ResponseStoringProducer mockProducer = new ResponseStoringProducer();
				given(mockExporterSession.createProducer(requestMessage.getJMSReplyTo())).willReturn(mockProducer);
				exporter.onMessage(requestMessage, mockExporterSession);
				assertTrue(mockProducer.closed);
				return mockProducer.response;
			}
		};
		pfb.setServiceInterface(ITestBean.class);
		pfb.setConnectionFactory(this.mockConnectionFactory);
		if (dynamicQueue) {
			pfb.setQueueName("myQueue");
		}
		else {
			pfb.setQueue(this.mockQueue);
		}
		pfb.setMessageConverter(new MockSimpleMessageConverter());

		pfb.afterPropertiesSet();
		ITestBean proxy = (ITestBean) pfb.getObject();

		assertEquals("myname", proxy.getName());
		assertEquals(99, proxy.getAge());
		proxy.setAge(50);
		assertEquals(50, proxy.getAge());
		proxy.setStringArray(new String[] {"str1", "str2"});
		assertTrue(Arrays.equals(new String[] {"str1", "str2"}, proxy.getStringArray()));

		try {
			proxy.exceptional(new IllegalStateException());
			fail("Should have thrown IllegalStateException");
		}
		catch (IllegalStateException ex) {
			// expected
		}
		try {
			proxy.exceptional(new IllegalAccessException());
			fail("Should have thrown IllegalAccessException");
		}
		catch (IllegalAccessException ex) {
			// expected
		}
	}


	private static class ResponseStoringProducer implements MessageProducer {

		public Message response;

		public boolean closed = false;

		@Override
		public void setDisableMessageID(boolean b) throws JMSException {
		}

		@Override
		public boolean getDisableMessageID() throws JMSException {
			return false;
		}

		@Override
		public void setDisableMessageTimestamp(boolean b) throws JMSException {
		}

		@Override
		public boolean getDisableMessageTimestamp() throws JMSException {
			return false;
		}

		@Override
		public void setDeliveryMode(int i) throws JMSException {
		}

		@Override
		public int getDeliveryMode() throws JMSException {
			return 0;
		}

		@Override
		public void setPriority(int i) throws JMSException {
		}

		@Override
		public int getPriority() throws JMSException {
			return 0;
		}

		@Override
		public void setTimeToLive(long l) throws JMSException {
		}

		@Override
		public long getTimeToLive() throws JMSException {
			return 0;
		}

		@Override
		public void setDeliveryDelay(long deliveryDelay) throws JMSException {
		}

		@Override
		public long getDeliveryDelay() throws JMSException {
			return 0;
		}

		@Override
		public Destination getDestination() throws JMSException {
			return null;
		}

		@Override
		public void close() throws JMSException {
			this.closed = true;
		}

		@Override
		public void send(Message message) throws JMSException {
			this.response = message;
		}

		@Override
		public void send(Message message, int i, int i1, long l) throws JMSException {
		}

		@Override
		public void send(Destination destination, Message message) throws JMSException {
		}

		@Override
		public void send(Destination destination, Message message, int i, int i1, long l) throws JMSException {
		}

		@Override
		public void send(Message message, CompletionListener completionListener) throws JMSException {
		}

		@Override
		public void send(Message message, int deliveryMode, int priority, long timeToLive, CompletionListener completionListener) throws JMSException {
		}

		@Override
		public void send(Destination destination, Message message, CompletionListener completionListener) throws JMSException {
		}

		@Override
		public void send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive, CompletionListener completionListener) throws JMSException {
		}
	}


	private static class MockObjectMessage implements ObjectMessage {

		private Serializable serializable;

		private Destination replyTo;

		public MockObjectMessage(Serializable serializable) {
			this.serializable = serializable;
		}

		@Override
		public void setObject(Serializable serializable) throws JMSException {
			this.serializable = serializable;
		}

		@Override
		public Serializable getObject() throws JMSException {
			return serializable;
		}

		@Override
		public String getJMSMessageID() throws JMSException {
			return null;
		}

		@Override
		public void setJMSMessageID(String string) throws JMSException {
		}

		@Override
		public long getJMSTimestamp() throws JMSException {
			return 0;
		}

		@Override
		public void setJMSTimestamp(long l) throws JMSException {
		}

		@Override
		public byte[] getJMSCorrelationIDAsBytes() throws JMSException {
			return new byte[0];
		}

		@Override
		public void setJMSCorrelationIDAsBytes(byte[] bytes) throws JMSException {
		}

		@Override
		public void setJMSCorrelationID(String string) throws JMSException {
		}

		@Override
		public String getJMSCorrelationID() throws JMSException {
			return null;
		}

		@Override
		public Destination getJMSReplyTo() throws JMSException {
			return replyTo;
		}

		@Override
		public void setJMSReplyTo(Destination destination) throws JMSException {
			this.replyTo = destination;
		}

		@Override
		public Destination getJMSDestination() throws JMSException {
			return null;
		}

		@Override
		public void setJMSDestination(Destination destination) throws JMSException {
		}

		@Override
		public int getJMSDeliveryMode() throws JMSException {
			return 0;
		}

		@Override
		public void setJMSDeliveryMode(int i) throws JMSException {
		}

		@Override
		public boolean getJMSRedelivered() throws JMSException {
			return false;
		}

		@Override
		public void setJMSRedelivered(boolean b) throws JMSException {
		}

		@Override
		public String getJMSType() throws JMSException {
			return null;
		}

		@Override
		public void setJMSType(String string) throws JMSException {
		}

		@Override
		public long getJMSExpiration() throws JMSException {
			return 0;
		}

		@Override
		public void setJMSExpiration(long l) throws JMSException {
		}

		@Override
		public int getJMSPriority() throws JMSException {
			return 0;
		}

		@Override
		public void setJMSPriority(int i) throws JMSException {
		}

		@Override
		public long getJMSDeliveryTime() throws JMSException {
			return 0;
		}

		@Override
		public void setJMSDeliveryTime(long deliveryTime) throws JMSException {
		}

		@Override
		public <T> T getBody(Class<T> c) throws JMSException {
			return null;
		}

		@Override
		public boolean isBodyAssignableTo(Class c) throws JMSException {
			return false;
		}

		@Override
		public void clearProperties() throws JMSException {
		}

		@Override
		public boolean propertyExists(String string) throws JMSException {
			return false;
		}

		@Override
		public boolean getBooleanProperty(String string) throws JMSException {
			return false;
		}

		@Override
		public byte getByteProperty(String string) throws JMSException {
			return 0;
		}

		@Override
		public short getShortProperty(String string) throws JMSException {
			return 0;
		}

		@Override
		public int getIntProperty(String string) throws JMSException {
			return 0;
		}

		@Override
		public long getLongProperty(String string) throws JMSException {
			return 0;
		}

		@Override
		public float getFloatProperty(String string) throws JMSException {
			return 0;
		}

		@Override
		public double getDoubleProperty(String string) throws JMSException {
			return 0;
		}

		@Override
		public String getStringProperty(String string) throws JMSException {
			return null;
		}

		@Override
		public Object getObjectProperty(String string) throws JMSException {
			return null;
		}

		@Override
		public Enumeration getPropertyNames() throws JMSException {
			return null;
		}

		@Override
		public void setBooleanProperty(String string, boolean b) throws JMSException {
		}

		@Override
		public void setByteProperty(String string, byte b) throws JMSException {
		}

		@Override
		public void setShortProperty(String string, short i) throws JMSException {
		}

		@Override
		public void setIntProperty(String string, int i) throws JMSException {
		}

		@Override
		public void setLongProperty(String string, long l) throws JMSException {
		}

		@Override
		public void setFloatProperty(String string, float v) throws JMSException {
		}

		@Override
		public void setDoubleProperty(String string, double v) throws JMSException {
		}

		@Override
		public void setStringProperty(String string, String string1) throws JMSException {
		}

		@Override
		public void setObjectProperty(String string, Object object) throws JMSException {
		}

		@Override
		public void acknowledge() throws JMSException {
		}

		@Override
		public void clearBody() throws JMSException {
		}
	}


	private static class MockSimpleMessageConverter extends SimpleMessageConverter {

		@Override
		public Message toMessage(Object object, Session session) throws JMSException, MessageConversionException {
			return new MockObjectMessage((Serializable) object);
		}
	}

}