| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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.apache.sling.jcr.contentloader.internal; |
| |
| import java.text.ParseException; |
| import java.util.*; |
| |
| import javax.jcr.*; |
| import javax.jcr.nodetype.NodeType; |
| |
| import junitx.util.PrivateAccessor; |
| import org.apache.sling.commons.testing.jcr.RepositoryProvider; |
| import org.apache.sling.jcr.api.SlingRepository; |
| import org.apache.sling.jcr.contentloader.ContentImportListener; |
| import org.apache.sling.jcr.contentloader.ContentReader; |
| import org.jmock.Expectations; |
| import org.jmock.Mockery; |
| import org.jmock.integration.junit4.JUnit4Mockery; |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.rules.ExpectedException; |
| |
| import static org.junit.Assert.*; |
| |
| public class DefaultContentCreatorTest { |
| |
| static final String DEFAULT_NAME = "default-name"; |
| final Mockery mockery = new JUnit4Mockery(); |
| DefaultContentCreator contentCreator; |
| |
| Session session; |
| Node parentNode; |
| Property prop; |
| |
| @Rule |
| public ExpectedException thrown = ExpectedException.none(); |
| |
| @Before |
| public void setup() throws Exception { |
| final SlingRepository repo = RepositoryProvider.instance().getRepository(); |
| session = repo.loginAdministrative(null); |
| contentCreator = new DefaultContentCreator(null); |
| contentCreator.init(ImportOptionsFactory.createImportOptions(true, true, true, false, false), |
| new HashMap<String, ContentReader>(), null, null); |
| parentNode = session.getRootNode().addNode(getClass().getSimpleName()).addNode(uniqueId()); |
| } |
| |
| @After |
| public void cleanup() throws RepositoryException { |
| if(session != null) { |
| session.save(); |
| session.logout(); |
| session = null; |
| } |
| } |
| |
| @Test |
| public void willRewriteUndefinedPropertyType() throws RepositoryException { |
| parentNode = mockery.mock(Node.class); |
| prop = mockery.mock(Property.class); |
| contentCreator.init(ImportOptionsFactory.createImportOptions(true, true, true, false, false), |
| new HashMap<String, ContentReader>(), null, null); |
| |
| contentCreator.prepareParsing(parentNode, null); |
| this.mockery.checking(new Expectations() {{ |
| allowing(parentNode).isNodeType("mix:versionable"); will(returnValue(Boolean.FALSE)); |
| allowing(parentNode).getParent(); will(returnValue(null)); |
| oneOf (parentNode).hasProperty("foo"); will(returnValue(Boolean.TRUE)); |
| oneOf (parentNode).setProperty(with(equal("foo")), with(equal("bar"))); |
| }}); |
| contentCreator.createProperty("foo", PropertyType.UNDEFINED, "bar"); |
| } |
| |
| @Test |
| public void willNotRewriteUndefinedPropertyType() throws RepositoryException { |
| parentNode = mockery.mock(Node.class); |
| prop = mockery.mock(Property.class); |
| contentCreator.init(ImportOptionsFactory.createImportOptions(false, false, true, false, false), |
| new HashMap<String, ContentReader>(), null, null); |
| |
| contentCreator.prepareParsing(parentNode, null); |
| this.mockery.checking(new Expectations() {{ |
| oneOf (parentNode).hasProperty("foo"); will(returnValue(Boolean.TRUE)); |
| oneOf (parentNode).getProperty("foo"); will(returnValue(prop)); |
| oneOf (prop).isNew(); will(returnValue(Boolean.FALSE)); |
| }}); |
| contentCreator.createProperty("foo", PropertyType.UNDEFINED, "bar"); |
| } |
| |
| @Test |
| public void testDoesNotCreateProperty() throws RepositoryException { |
| final String propertyName = "foo"; |
| prop = mockery.mock(Property.class); |
| parentNode = mockery.mock(Node.class); |
| |
| this.mockery.checking(new Expectations(){{ |
| oneOf(parentNode).hasProperty(propertyName); will(returnValue(true)); |
| oneOf(parentNode).getProperty(propertyName); will(returnValue(prop)); |
| oneOf(prop).isNew(); will(returnValue(false)); |
| }}); |
| contentCreator.init(ImportOptionsFactory.createImportOptions(false, false, false, false, false), |
| new HashMap<String, ContentReader>(), null, null); |
| contentCreator.prepareParsing(parentNode, null); |
| //By calling this method we expect that it will returns on first if-statement |
| contentCreator.createProperty("foo", PropertyType.REFERENCE, "bar"); |
| //Checking that |
| mockery.assertIsSatisfied(); |
| } |
| |
| @Test |
| public void testCreateReferenceProperty() throws RepositoryException { |
| final String propertyName = "foo"; |
| final String propertyValue = "bar"; |
| final String rootNodeName = uniqueId(); |
| final String uuid = "1b8c88d37f0000020084433d3af4941f"; |
| final Session session = mockery.mock(Session.class); |
| final ContentImportListener listener = mockery.mock(ContentImportListener.class); |
| parentNode = mockery.mock(Node.class); |
| prop = mockery.mock(Property.class); |
| |
| this.mockery.checking(new Expectations(){{ |
| oneOf(session).itemExists(with(any(String.class))); will(returnValue(true)); |
| oneOf(session).getItem(with(any(String.class))); will(returnValue(parentNode)); |
| |
| exactly(2).of(parentNode).getPath(); will(returnValue("/" + rootNodeName)); |
| oneOf(parentNode).isNode(); will(returnValue(true)); |
| oneOf(parentNode).isNodeType("mix:referenceable"); will(returnValue(true)); |
| oneOf(parentNode).getIdentifier(); will(returnValue(uuid)); |
| oneOf(parentNode).getSession(); will(returnValue(session)); |
| oneOf(parentNode).hasProperty(with(any(String.class))); |
| oneOf(parentNode).setProperty(propertyName, uuid, PropertyType.REFERENCE); |
| oneOf(parentNode).getProperty(with(any(String.class))); |
| oneOf(listener).onCreate(with(any(String.class))); |
| }}); |
| |
| contentCreator.init(ImportOptionsFactory.createImportOptions(false, false, false, false, false), |
| new HashMap<String, ContentReader>(), null, listener); |
| contentCreator.prepareParsing(parentNode,null); |
| contentCreator.createProperty(propertyName, PropertyType.REFERENCE, propertyValue); |
| //The only way I found how to test this method is to check numbers of methods calls |
| mockery.assertIsSatisfied(); |
| } |
| |
| @Test |
| public void testCreateFalseCheckedOutPreperty() throws RepositoryException { |
| parentNode = mockery.mock(Node.class); |
| |
| this.mockery.checking(new Expectations(){{ |
| oneOf(parentNode).hasProperty(with(any(String.class))); |
| }}); |
| |
| contentCreator.init(ImportOptionsFactory.createImportOptions(false, false, false, false, false), |
| new HashMap<String, ContentReader>(), null, null); |
| contentCreator.prepareParsing(parentNode, null); |
| |
| int numberOfVersionablesNodes = contentCreator.getVersionables().size(); |
| contentCreator.createProperty("jcr:isCheckedOut", PropertyType.UNDEFINED, "false"); |
| //Checking that list of versionables was changed |
| assertEquals(numberOfVersionablesNodes + 1, contentCreator.getVersionables().size()); |
| mockery.assertIsSatisfied(); |
| } |
| |
| @Test |
| public void testCreateTrueCheckedOutPreperty() throws RepositoryException { |
| parentNode = mockery.mock(Node.class); |
| |
| this.mockery.checking(new Expectations(){{ |
| oneOf(parentNode).hasProperty(with(any(String.class))); |
| }}); |
| |
| contentCreator.init(ImportOptionsFactory.createImportOptions(false, false, false, false, false), |
| new HashMap<String, ContentReader>(), null, null); |
| contentCreator.prepareParsing(parentNode, null); |
| |
| int numberOfVersionablesNodes = contentCreator.getVersionables().size(); |
| contentCreator.createProperty("jcr:isCheckedOut", PropertyType.UNDEFINED, "true"); |
| //Checking that list of versionables doesn't changed |
| assertEquals(numberOfVersionablesNodes, contentCreator.getVersionables().size()); |
| mockery.assertIsSatisfied(); |
| } |
| |
| @Test |
| public void testCreateDateProperty() throws RepositoryException, ParseException { |
| final String propertyName = "dateProp"; |
| final String propertyValue = "2012-10-01T09:45:00.000+02:00"; |
| final ContentImportListener listener = mockery.mock(ContentImportListener.class); |
| parentNode = mockery.mock(Node.class); |
| prop = mockery.mock(Property.class); |
| |
| this.mockery.checking(new Expectations(){{ |
| oneOf(parentNode).hasProperty(with(any(String.class))); |
| oneOf(parentNode).setProperty(with(any(String.class)), with(any(Calendar.class))); |
| oneOf(parentNode).getProperty(with(any(String.class))); will(returnValue(prop)); |
| oneOf(prop).getPath(); will(returnValue("")); |
| oneOf(listener).onCreate(with(any(String.class))); |
| }}); |
| |
| contentCreator.init(ImportOptionsFactory.createImportOptions(false, false, false, false, false), |
| new HashMap<String, ContentReader>(), null, listener); |
| contentCreator.prepareParsing(parentNode, null); |
| |
| contentCreator.createProperty(propertyName, PropertyType.DATE, propertyValue); |
| mockery.assertIsSatisfied(); |
| } |
| |
| @Test |
| public void testCreatePropertyWithNewPropertyType() throws RepositoryException, ParseException { |
| final String propertyName = "foo"; |
| final String propertyValue = "bar"; |
| final Integer propertyType = -1; |
| final ContentImportListener listener = mockery.mock(ContentImportListener.class); |
| parentNode = mockery.mock(Node.class); |
| prop = mockery.mock(Property.class); |
| |
| this.mockery.checking(new Expectations(){{ |
| oneOf(parentNode).hasProperty(with(any(String.class))); |
| oneOf(parentNode).getProperty(propertyName); will(returnValue(prop)); |
| oneOf(parentNode).setProperty(propertyName, propertyValue, propertyType); |
| oneOf(prop).getPath(); will(returnValue("")); |
| oneOf(listener).onCreate(with(any(String.class))); |
| }}); |
| |
| contentCreator.init(ImportOptionsFactory.createImportOptions(false, false, false, false, false), |
| new HashMap<String, ContentReader>(), null, listener); |
| contentCreator.prepareParsing(parentNode, null); |
| |
| contentCreator.createProperty(propertyName, propertyType, propertyValue); |
| mockery.assertIsSatisfied(); |
| } |
| |
| //----- DefaultContentCreator#createNode(String name, String primaryNodeType, String[] mixinNodeTypes)-------// |
| |
| @Test |
| public void createNodeWithoutNameAndTwoInStack() throws RepositoryException { |
| contentCreator.init(ImportOptionsFactory.createImportOptions(true, true, true, false, false), |
| new HashMap<String, ContentReader>(), null, null); |
| //Making parentNodeStack.size() == 1 |
| contentCreator.prepareParsing(parentNode, DEFAULT_NAME); |
| //Making parentNodeStack.size() == 2 |
| contentCreator.createNode(uniqueId(), null, null); |
| |
| thrown.expect(RepositoryException.class); |
| contentCreator.createNode(null, null, null); |
| } |
| |
| @Test |
| public void createNodeWithoutProvidedNames() throws RepositoryException, NoSuchFieldException { |
| @SuppressWarnings("unchecked") |
| Stack<Node> nodesStack = (Stack<Node>)PrivateAccessor.getField(contentCreator, "parentNodeStack"); |
| |
| contentCreator.init(ImportOptionsFactory.createImportOptions(true, true, true, false, false), |
| new HashMap<String, ContentReader>(), null, null); |
| |
| contentCreator.prepareParsing(parentNode, null); |
| //Contains parentNode only |
| assertEquals(1, nodesStack.size()); |
| contentCreator.createNode(null, null, null); |
| //Node has not been created since any name not provided |
| assertEquals(1, nodesStack.size()); |
| } |
| |
| @Test |
| public void createNodeWithOverwrite() throws RepositoryException { |
| final String newNodeName = uniqueId(); |
| final String propertyName = uniqueId(); |
| final String propertyValue = uniqueId(); |
| final ContentImportListener listener = mockery.mock(ContentImportListener.class); |
| |
| this.mockery.checking(new Expectations(){{ |
| oneOf(listener).onCreate(with(any(String.class))); |
| }}); |
| |
| contentCreator.init(ImportOptionsFactory.createImportOptions(true, false, true, false, false), |
| new HashMap<String, ContentReader>(), null, listener); |
| contentCreator.prepareParsing(parentNode, DEFAULT_NAME); |
| |
| Node nodeToOverwrite = parentNode.addNode(newNodeName); |
| nodeToOverwrite.setProperty(propertyName, propertyValue); |
| |
| assertTrue(parentNode.getNode(newNodeName).hasProperty(propertyName)); |
| contentCreator.createNode(newNodeName, null, null); |
| //If node was overwritten(as we expect) it will not contain this property |
| assertFalse(parentNode.getNode(newNodeName).hasProperty(propertyName)); |
| mockery.assertIsSatisfied(); |
| } |
| |
| @Test |
| public void addMixinsToExistingNode() throws RepositoryException, NoSuchFieldException { |
| final String newNodeName = uniqueId(); |
| final String[] mixins = {"mix:versionable"}; |
| @SuppressWarnings("unchecked") |
| final List<Node> versionables = (List<Node>) PrivateAccessor.getField(contentCreator, "versionables"); |
| |
| contentCreator.init(ImportOptionsFactory.createImportOptions(false, false, false, true, false), |
| new HashMap<String, ContentReader>(), null, null); |
| contentCreator.prepareParsing(parentNode, DEFAULT_NAME); |
| |
| Node newNode = parentNode.addNode(newNodeName); |
| assertEquals(0, versionables.size()); |
| assertFalse(newNode.isNodeType(mixins[0])); |
| contentCreator.createNode(newNodeName, null, mixins); |
| assertEquals(1, versionables.size()); |
| assertTrue(newNode.isNodeType(mixins[0])); |
| } |
| |
| @Test |
| public void createNodeWithPrimaryType() throws RepositoryException { |
| final String newNodeName = uniqueId(); |
| final List<String> createdNodes = new ArrayList<String>(); |
| final ContentImportListener listener = mockery.mock(ContentImportListener.class); |
| |
| this.mockery.checking(new Expectations(){{ |
| oneOf(listener).onCreate(with(any(String.class))); |
| }}); |
| |
| contentCreator.init(ImportOptionsFactory.createImportOptions(true, false, true, false, false), |
| new HashMap<String, ContentReader>(), createdNodes, listener); |
| contentCreator.prepareParsing(parentNode, DEFAULT_NAME); |
| |
| int createdNodesSize = createdNodes.size(); |
| contentCreator.createNode(newNodeName, NodeType.NT_UNSTRUCTURED, null); |
| assertEquals(createdNodesSize + 1, createdNodes.size()); |
| Node createdNode = parentNode.getNode(newNodeName); |
| assertNotNull(createdNode); |
| assertTrue(createdNode.getPrimaryNodeType().isNodeType(NodeType.NT_UNSTRUCTURED)); |
| mockery.assertIsSatisfied(); |
| } |
| |
| //----- DefaultContentCreator#createProperty(String name, int propertyType, String[] values)-------// |
| |
| @Test |
| public void propertyDoesntOverwritten() throws RepositoryException { |
| final String newPropertyName = uniqueId(); |
| final String newPropertyValue = uniqueId(); |
| contentCreator.init(ImportOptionsFactory.createImportOptions(false, false, false, false, false), |
| new HashMap<String, ContentReader>(), null, null); |
| contentCreator.prepareParsing(parentNode, DEFAULT_NAME); |
| |
| parentNode.setProperty(newPropertyName, newPropertyValue); |
| session.save(); |
| contentCreator.createProperty(newPropertyName, PropertyType.REFERENCE, new String[]{"bar1", "bar2"}); |
| //Checking that property is old |
| assertTrue(!parentNode.getProperty(newPropertyName).isNew()); |
| assertEquals(newPropertyValue, parentNode.getProperty(newPropertyName).getString()); |
| } |
| |
| |
| @Test |
| public void createReferenceProperty() throws RepositoryException, NoSuchFieldException { |
| final String propName = uniqueId(); |
| final String[] propValues = {uniqueId(), uniqueId()}; |
| final ContentImportListener listener = mockery.mock(ContentImportListener.class); |
| @SuppressWarnings("unchecked") |
| final Map<String, String[]> delayedMultipleReferences = |
| (Map<String, String[]>)PrivateAccessor.getField(contentCreator, "delayedMultipleReferences"); |
| this.mockery.checking(new Expectations(){{ |
| oneOf(listener).onCreate(with(any(String.class))); |
| }}); |
| |
| contentCreator.init(ImportOptionsFactory.createImportOptions(false, false, false, false, false), |
| new HashMap<String, ContentReader>(), null, listener); |
| contentCreator.prepareParsing(parentNode, DEFAULT_NAME); |
| |
| contentCreator.createProperty(propName, PropertyType.REFERENCE, propValues); |
| assertTrue(parentNode.hasProperty(propName)); |
| assertTrue(parentNode.getProperty(propName).isNew()); |
| |
| String referencesKey = parentNode.getPath() + "/" + propName; |
| String[] uuidsOrPaths = delayedMultipleReferences.get(referencesKey); |
| assertNotNull(uuidsOrPaths); |
| assertEquals(propValues.length, uuidsOrPaths.length); |
| mockery.assertIsSatisfied(); |
| } |
| |
| @Test |
| public void createDateProperty() throws RepositoryException, ParseException { |
| final String propName = "dateProp"; |
| final String[] propValues = {"2012-10-01T09:45:00.000+02:00", "2011-02-13T09:45:00.000+02:00"}; |
| final ContentImportListener listener = mockery.mock(ContentImportListener.class); |
| |
| this.mockery.checking(new Expectations(){{ |
| oneOf(listener).onCreate(with(any(String.class))); |
| }}); |
| parentNode.addMixin("mix:versionable"); |
| |
| contentCreator.init(ImportOptionsFactory.createImportOptions(false, false, true, false, false), |
| new HashMap<String, ContentReader>(), null, listener); |
| contentCreator.prepareParsing(parentNode, null); |
| |
| assertFalse(parentNode.hasProperty(propName)); |
| contentCreator.createProperty(propName, PropertyType.DATE, propValues); |
| assertTrue(parentNode.hasProperty(propName)); |
| |
| Property dateProp = parentNode.getProperty(propName); |
| assertTrue(dateProp.isNew()); |
| assertEquals(propValues.length, dateProp.getValues().length); |
| |
| mockery.assertIsSatisfied(); |
| } |
| |
| @Test |
| public void createUndefinedProperty() throws RepositoryException { |
| final String propName = uniqueId(); |
| final ContentImportListener listener = mockery.mock(ContentImportListener.class); |
| |
| this.mockery.checking(new Expectations(){{ |
| oneOf(listener).onCreate(with(any(String.class))); |
| }}); |
| |
| contentCreator.init(ImportOptionsFactory.createImportOptions(false, false, false, false, false), |
| new HashMap<String, ContentReader>(), null, listener); |
| contentCreator.prepareParsing(parentNode, null); |
| |
| assertFalse(parentNode.hasProperty(propName)); |
| contentCreator.createProperty(propName, PropertyType.UNDEFINED, new String[]{}); |
| assertTrue(parentNode.hasProperty(propName)); |
| mockery.assertIsSatisfied(); |
| } |
| |
| @Test |
| public void createOtherProperty() throws RepositoryException { |
| final String propName = uniqueId(); |
| |
| contentCreator.init(ImportOptionsFactory.createImportOptions(false, false, false, false, false), |
| new HashMap<String, ContentReader>(), null, null); |
| contentCreator.prepareParsing(parentNode, null); |
| |
| assertFalse(parentNode.hasProperty(propName)); |
| contentCreator.createProperty(propName, PropertyType.STRING, new String[]{}); |
| assertTrue(parentNode.hasProperty(propName)); |
| } |
| |
| //------DefaultContentCreator#finishNode()------// |
| |
| @Test |
| public void finishNodeWithMultipleProperty() throws RepositoryException, NoSuchFieldException { |
| final String propName = uniqueId(); |
| final String underTestNodeName = uniqueId(); |
| @SuppressWarnings("unchecked") |
| final Map<String, List<String>> delayedMultipleReferences = |
| (Map<String, List<String>>) PrivateAccessor.getField(contentCreator, "delayedMultipleReferences"); |
| final ContentImportListener listener = mockery.mock(ContentImportListener.class); |
| |
| this.mockery.checking(new Expectations(){{ |
| exactly(3).of(listener).onCreate(with(any(String.class))); |
| }}); |
| |
| contentCreator.init(ImportOptionsFactory.createImportOptions(false, false, false, false, false), |
| new HashMap<String, ContentReader>(), null, listener); |
| contentCreator.prepareParsing(parentNode, null); |
| |
| contentCreator.createProperty(propName, PropertyType.REFERENCE, new String[]{underTestNodeName}); |
| contentCreator.createNode(underTestNodeName, null, null); |
| assertEquals(1, delayedMultipleReferences.size()); |
| |
| Node underTest = parentNode.getNode(underTestNodeName); |
| underTest.addMixin("mix:referenceable"); |
| |
| contentCreator.finishNode(); |
| assertEquals(0, delayedMultipleReferences.size()); |
| mockery.assertIsSatisfied(); |
| } |
| |
| @Test |
| public void finishNodeWithSingleProperty() throws RepositoryException, NoSuchFieldException { |
| final String propName = uniqueId(); |
| final String underTestNodeName = uniqueId(); |
| final ContentImportListener listener = mockery.mock(ContentImportListener.class); |
| |
| this.mockery.checking(new Expectations(){{ |
| exactly(2).of(listener).onCreate(with(any(String.class))); |
| }}); |
| |
| contentCreator.init(ImportOptionsFactory.createImportOptions(false, false, false, false, false), |
| new HashMap<String, ContentReader>(), null, listener); |
| contentCreator.prepareParsing(parentNode, null); |
| |
| contentCreator.createProperty(propName, PropertyType.REFERENCE, underTestNodeName); |
| contentCreator.createNode(underTestNodeName, null, null); |
| |
| Node underTest = parentNode.getNode(underTestNodeName); |
| underTest.addMixin("mix:referenceable"); |
| |
| contentCreator.finishNode(); |
| assertEquals(underTest.getIdentifier(), parentNode.getProperty(propName).getString()); |
| mockery.assertIsSatisfied(); |
| } |
| |
| @Test |
| public void finishNodeWithoutProperties() throws RepositoryException, NoSuchFieldException { |
| final String propName = uniqueId(); |
| final String underTestNodeName = uniqueId(); |
| |
| contentCreator.init(ImportOptionsFactory.createImportOptions(false, false, false, false, false), |
| new HashMap<String, ContentReader>(), null, null); |
| contentCreator.prepareParsing(parentNode, null); |
| |
| contentCreator.createProperty(propName, PropertyType.UNDEFINED, underTestNodeName); |
| contentCreator.createNode(underTestNodeName, null, null); |
| |
| contentCreator.finishNode(); |
| assertEquals(underTestNodeName, parentNode.getProperty(propName).getString()); |
| } |
| |
| @Test |
| public void finishNotReferenceableNode() throws RepositoryException, NoSuchFieldException { |
| final String propName = uniqueId(); |
| final String underTestNodeName = uniqueId(); |
| |
| contentCreator.init(ImportOptionsFactory.createImportOptions(false, false, false, false, false), |
| new HashMap<String, ContentReader>(), null, null); |
| contentCreator.prepareParsing(parentNode, null); |
| |
| contentCreator.createProperty(propName, PropertyType.REFERENCE, underTestNodeName); |
| contentCreator.createNode(underTestNodeName, null, null); |
| |
| contentCreator.finishNode(); |
| //False since it doesn't have referenceable mixin |
| assertFalse(parentNode.hasProperty(propName)); |
| } |
| |
| private final String uniqueId() { |
| return getClass().getSimpleName() + UUID.randomUUID(); |
| } |
| } |