/* * Copyright 2015 Red Hat, Inc. and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * 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.jbpm.services.task.jaxb; import static org.jbpm.services.task.commands.SetTaskPropertyCommand.DESCRIPTION_PROPERTY; import static org.jbpm.services.task.commands.SetTaskPropertyCommand.EXPIRATION_DATE_PROPERTY; import static org.jbpm.services.task.commands.SetTaskPropertyCommand.FAULT_PROPERTY; import static org.jbpm.services.task.commands.SetTaskPropertyCommand.OUTPUT_PROPERTY; import static org.jbpm.services.task.commands.SetTaskPropertyCommand.PRIORITY_PROPERTY; import static org.jbpm.services.task.commands.SetTaskPropertyCommand.SKIPPABLE_PROPERTY; import static org.jbpm.services.task.commands.SetTaskPropertyCommand.SUB_TASK_STRATEGY_PROPERTY; import static org.jbpm.services.task.commands.SetTaskPropertyCommand.TASK_NAMES_PROPERTY; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.io.ByteArrayInputStream; import java.io.StringWriter; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.Map.Entry; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElements; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.transform.stream.StreamSource; import org.jbpm.services.task.admin.listener.internal.GetCurrentTxTasksCommand; import org.jbpm.services.task.commands.AddContentFromUserCommand; import org.jbpm.services.task.commands.CompositeCommand; import org.jbpm.services.task.commands.SetTaskPropertyCommand; import org.jbpm.services.task.commands.TaskCommand; import org.jbpm.services.task.commands.TaskContext; import org.jbpm.services.task.commands.UserGroupCallbackTaskCommand; import org.jbpm.services.task.impl.model.FaultDataImpl; import org.jbpm.services.task.impl.model.I18NTextImpl; import org.jbpm.services.task.impl.model.xml.JaxbContent; import org.jbpm.services.task.impl.model.xml.JaxbTask; import org.jbpm.services.task.jaxb.AbstractTaskSerializationTest.TestType; import org.junit.Assume; import org.junit.Test; import org.kie.api.task.model.I18NText; import org.kie.internal.task.api.TaskInstanceService; import org.kie.internal.task.api.UserGroupCallback; import org.kie.internal.task.api.model.AccessType; import org.kie.internal.task.api.model.SubTasksStrategy; import org.reflections.Reflections; import org.reflections.scanners.FieldAnnotationsScanner; import org.reflections.scanners.MethodAnnotationsScanner; import org.reflections.scanners.SubTypesScanner; import org.reflections.scanners.TypeAnnotationsScanner; import org.reflections.util.ClasspathHelper; public class JaxbTaskSerializationTest extends AbstractTaskSerializationTest { private Class<?>[] jaxbClasses = { JaxbTask.class, JaxbContent.class }; public TestType getType() { return TestType.JAXB; } protected static Reflections reflections = new Reflections(ClasspathHelper.forPackage("org.jbpm.services.task"), ClasspathHelper.forPackage("org.jbpm.services.task.commands"), new TypeAnnotationsScanner(), new FieldAnnotationsScanner(), new MethodAnnotationsScanner(), new SubTypesScanner()); @Override public <T> T testRoundTrip(T input) throws Exception { String xmlStr = convertJaxbObjectToString(input); logger.debug(xmlStr); if( input instanceof JAXBElement ) { return (T) convertStringToJaxbElement(xmlStr, ((JAXBElement) input).getValue().getClass()); } return (T) convertStringToJaxbObject(xmlStr); } @Test public void taskCmdUniqueRootElementTest() throws Exception { Set<String> uniqueRootElemSet = new HashSet<String>(); for (Class<?> jaxbClass : reflections.getTypesAnnotatedWith(XmlRootElement.class) ) { XmlRootElement xmlRootElemAnno = jaxbClass.getAnnotation(XmlRootElement.class); assertTrue( xmlRootElemAnno.name() + " is not a unique @XmlRootElement value!", uniqueRootElemSet.add(xmlRootElemAnno.name())); } } @Test public void taskCommandSubTypesCanBeSerialized() throws Exception { for (Class<?> jaxbClass : reflections.getSubTypesOf(TaskCommand.class)) { if (jaxbClass.equals(UserGroupCallbackTaskCommand.class) || jaxbClass.equals(GetCurrentTxTasksCommand.class)) { continue; } addClassesToSerializationContext(jaxbClass); Constructor<?> construct = jaxbClass.getConstructor(new Class[] {}); try { Object jaxbInst = construct.newInstance(new Object[] {}); testRoundTrip(jaxbInst); } catch (Exception e) { logger.warn("Testing failed for" + jaxbClass.getName()); throw e; } } } @Test public void compositeCommandXmlElementsAnnoTest() throws Exception { Field [] comCmdFields = CompositeCommand.class.getDeclaredFields(); for( Field field : comCmdFields ) { XmlElements xmlElemsAnno = field.getAnnotation(XmlElements.class); if( xmlElemsAnno != null ) { Set<Class<? extends TaskCommand>> taskCmdSubTypes = reflections.getSubTypesOf(TaskCommand.class); Set<Class<? extends UserGroupCallbackTaskCommand>> userGrpTaskCmdSubTypes = reflections.getSubTypesOf(UserGroupCallbackTaskCommand.class); taskCmdSubTypes.addAll(userGrpTaskCmdSubTypes); Class [] exclTaskCmds = { UserGroupCallbackTaskCommand.class, CompositeCommand.class, GetCurrentTxTasksCommand.class }; taskCmdSubTypes.removeAll(Arrays.asList(exclTaskCmds)); for( XmlElement xmlElemAnno : xmlElemsAnno.value() ) { Class xmlElemAnnoType = xmlElemAnno.type(); assertTrue( xmlElemAnnoType.getName() + " does not extend the " + TaskCommand.class.getSimpleName() + " class!", taskCmdSubTypes.contains(xmlElemAnnoType)); } for( XmlElement xmlElemAnno : xmlElemsAnno.value() ) { Class xmlElemAnnoType = xmlElemAnno.type(); taskCmdSubTypes.remove(xmlElemAnnoType); } if( ! taskCmdSubTypes.isEmpty() ) { System.out.println("##### " + taskCmdSubTypes.iterator().next().getCanonicalName()); fail( "(" + taskCmdSubTypes.iterator().next().getSimpleName() + ") Not all " + TaskCommand.class.getSimpleName() + " sub types have been added to the @XmlElements in the CompositeCommand." + field.getName() + " field."); } } else { assertFalse( "TaskCommand fields need to be annotated with @XmlElements annotations!", TaskCommand.class.equals(field.getType()) ); if( field.getType().isArray() ) { Class arrElemType = field.getType().getComponentType(); if( arrElemType != null ) { assertFalse( "TaskCommand fields (CompositeCommand." + field.getName() + ") need to be annotated with @XmlElements annotations!", TaskCommand.class.equals(arrElemType) ); } } else if( Collection.class.isAssignableFrom(field.getType()) ) { ParameterizedType fieldGenericType = (ParameterizedType) field.getGenericType(); Type listType = fieldGenericType.getActualTypeArguments()[0]; if( listType != null ) { assertFalse( "TaskCommand fields (CompositeCommand." + field.getName() + ") need to be annotated with @XmlElements annotations!", TaskCommand.class.equals(listType) ); } } } } } public String convertJaxbObjectToString(Object object) throws JAXBException { Marshaller marshaller = JAXBContext.newInstance(jaxbClasses).createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); StringWriter stringWriter = new StringWriter(); marshaller.marshal(object, stringWriter); String output = stringWriter.toString(); return output; } public Object convertStringToJaxbObject(String xmlStr) throws JAXBException { Unmarshaller unmarshaller = JAXBContext.newInstance(jaxbClasses).createUnmarshaller(); ByteArrayInputStream xmlStrInputStream = new ByteArrayInputStream(xmlStr.getBytes()); Object jaxbObj = unmarshaller.unmarshal(xmlStrInputStream); return jaxbObj; } public Object convertStringToJaxbElement(String xmlStr, Class actualClass) throws JAXBException { Unmarshaller unmarshaller = JAXBContext.newInstance(jaxbClasses).createUnmarshaller(); ByteArrayInputStream xmlStrInputStream = new ByteArrayInputStream(xmlStr.getBytes()); Object jaxbObj = unmarshaller.unmarshal(new StreamSource(xmlStrInputStream), actualClass); return jaxbObj; } @Override public void addClassesToSerializationContext(Class<?>... extraClass) { List<Class<?>> newJaxbClasses = new ArrayList<Class<?>>(); newJaxbClasses.addAll(Arrays.asList(jaxbClasses)); newJaxbClasses.addAll(Arrays.asList(extraClass)); jaxbClasses = newJaxbClasses.toArray(new Class[newJaxbClasses.size()]); } @Test public void uniqueRootElementTest() throws Exception { Set<String> idSet = new HashSet<String>(); HashMap<String, Class> idClassMap = new HashMap<String, Class>(); for (Class<?> jaxbClass : reflections.getTypesAnnotatedWith(XmlRootElement.class)) { XmlRootElement rootElemAnno = jaxbClass.getAnnotation(XmlRootElement.class); String id = rootElemAnno.name(); if ("##default".equals(id)) { continue; } String otherClass = (idClassMap.get(id) == null ? "null" : idClassMap.get(id).getName()); assertTrue("ID '" + id + "' used in both " + jaxbClass.getName() + " and " + otherClass, idSet.add(id)); idClassMap.put(id, jaxbClass); String className = jaxbClass.getSimpleName(); if( ! className.endsWith("Command") ) { continue; } String idName = id.replace("-", ""); assertEquals( "XML root element name should match class name in " + jaxbClass.getName() + "!", className.toLowerCase(), idName.toLowerCase()); } } @Test public void setTaskPropertyCommandTest() throws Exception { SetTaskPropertyCommand cmd; int taskId = 1; String userId = "user"; FaultDataImpl faultData = new FaultDataImpl(); faultData.setAccessType(AccessType.Inline); faultData.setContent("skinned shins".getBytes()); faultData.setFaultName("Whoops!"); faultData.setType("skates"); List<I18NText> textList = new ArrayList<I18NText>(); I18NText text = new I18NTextImpl("nl-NL", "Stroopwafel!"); textList.add(text); Object [][] testData = { { FAULT_PROPERTY, faultData }, { OUTPUT_PROPERTY, new Object() }, { PRIORITY_PROPERTY, 23 }, { TASK_NAMES_PROPERTY, textList }, { EXPIRATION_DATE_PROPERTY, new Date() }, { DESCRIPTION_PROPERTY, new ArrayList<I18NText>() }, { SKIPPABLE_PROPERTY, false }, { SUB_TASK_STRATEGY_PROPERTY, SubTasksStrategy.EndParentOnAllSubTasksEnd } }; TaskContext mockContext = mock(TaskContext.class); TaskInstanceService mockTaskService = mock(TaskInstanceService.class); UserGroupCallback mockUserGroupCallback = mock(UserGroupCallback.class); when(mockContext.getTaskInstanceService()).thenReturn(mockTaskService); when(mockContext.getUserGroupCallback()).thenReturn(mockUserGroupCallback); when(mockUserGroupCallback.existsUser(anyString())).thenReturn(false); for( Object [] data : testData ) { int property = (Integer) data[0]; cmd = new SetTaskPropertyCommand(taskId, userId, property, data[1]); cmd.execute(mockContext); } } @Test public void contentCommandTest() throws Exception { addClassesToSerializationContext(AddContentFromUserCommand.class); AddContentFromUserCommand cmd = new AddContentFromUserCommand(23l, "user"); cmd.getOutputContentMap().put("one", "two"); cmd.getOutputContentMap().put("thr", new Integer(4)); AddContentFromUserCommand copyCmd = testRoundTrip(cmd); assertEquals( "task id", cmd.getTaskId(), copyCmd.getTaskId()); assertEquals( "user id", cmd.getUserId(), copyCmd.getUserId()); for( Entry<String, Object> entry : cmd.getOutputContentMap().entrySet() ) { String key = entry.getKey(); Object copyVal = copyCmd.getOutputContentMap().get(key); assertEquals( "entry: " + key, entry.getValue(), copyVal ); } } }