NIFI-2366 - Fixed ID generation semantics in clustered environment

- added SnippetUtilsTest
- renamed TypeOneUUIDGenerator to ComponentIdGenerator

- changed lsb part of ComponentIdGenerator back to long
- Fixed 'isCopy' condition for clustered environments

This closes #718.
This commit is contained in:
Oleg Zhurakousky 2016-07-25 16:28:38 -04:00 committed by Mark Payne
parent 8412d2662a
commit 1bf10944ea
11 changed files with 460 additions and 147 deletions

View File

@ -0,0 +1,102 @@
/*
* 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.nifi.util;
import java.security.SecureRandom;
import java.util.UUID;
/**
* IMPORTANT: This component is not part of public API!
* ====================================================
* <p>
* This component generates type-one UUID. It is used for generating ID of all
* NiFi components. Giving the 128-bit UUID structure which consists of Least
* Significant Bits (LSB) and Most Significant Bits (MSB) this component
* provides support for generating and maintaining INCEPTION ID of the component
* (MSB) as well as its INSTANCE ID (LSB).
* </p>
* <p>
* It is also important to understand that while this component does seed itself
* from current time which could be extracted from the resulting UUID via call
* to {@link UUID#timestamp()} operation, one should not be relying on such time
* as the exact time when such ID was generated since in the event the same time
* is passed to one of the {@link #generateId()} operation it will be
* incremented by 1 since the goal of this component to only ensure uniqueness
* and type-one semantics where each UUID generated by this component is
* comparable and each subsequent ID is > then previous ID.
* </p>
* <p>
* For more details on how it is interacted with please see
* org.apache.nifi.web.util.SnippetUtils as well as
* org.apache.nifi.web.util.SnippetUtilsTest which contain additional
* documentation on its usage as well as ID generation contracts defined in
* NiFi.
* </p>
*/
public class ComponentIdGenerator {
public static final Object lock = new Object();
private static long lastTime;
private static long clockSequence = 0;
private static final SecureRandom randomGenerator = new SecureRandom();
/**
* Will generate unique time based UUID where the next UUID is always
* greater then the previous.
*/
public final static UUID generateId() {
return generateId(System.currentTimeMillis());
}
/**
*
*/
public final static UUID generateId(long currentTime) {
return generateId(currentTime, randomGenerator.nextLong(), true);
}
/**
*
*/
public final static UUID generateId(long msb, long lsb, boolean ensureUnique) {
long time;
synchronized (lock) {
if (ensureUnique && msb <= lastTime) {
msb = ++lastTime;
}
lastTime = msb;
}
time = msb;
// low Time
time = msb << 32;
// mid Time
time |= ((msb & 0xFFFF00000000L) >> 16);
// hi Time
time |= 0x1000 | ((msb >> 48) & 0x0FFF);
long clockSequenceHi = clockSequence;
clockSequenceHi <<= 48;
lsb = clockSequenceHi | lsb;
return new UUID(time, lsb);
}
}

View File

@ -1,76 +0,0 @@
/*
* 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.nifi.util;
import java.util.Random;
import java.util.UUID;
public class TypeOneUUIDGenerator {
public static final Object lock = new Object();
private static long lastTime;
private static long clockSequence = 0;
private static final Random randomGenerator = new Random();
/**
* Will generate unique time based UUID where the next UUID is always
* greater then the previous.
*/
public final static UUID generateId() {
return generateId(System.currentTimeMillis());
}
/**
*
*/
public final static UUID generateId(long currentTime) {
return generateId(currentTime, Math.abs(randomGenerator.nextInt()));
}
/**
*
*/
public final static UUID generateId(long currentTime, int lsbInt) {
long time;
synchronized (lock) {
if (currentTime == lastTime) {
++clockSequence;
} else {
lastTime = currentTime;
clockSequence = 0;
}
}
time = currentTime;
// low Time
time = currentTime << 32;
// mid Time
time |= ((currentTime & 0xFFFF00000000L) >> 16);
// hi Time
time |= 0x1000 | ((currentTime >> 48) & 0x0FFF);
long clockSequenceHi = clockSequence;
clockSequenceHi <<= 48;
long lsb = clockSequenceHi | lsbInt;
return new UUID(time, lsb);
}
}

View File

@ -93,4 +93,9 @@ public class ComponentDTO {
return id.equals(((ComponentDTO) obj).getId()); return id.equals(((ComponentDTO) obj).getId());
} }
@Override
public String toString() {
return this.getClass().getSimpleName() + ":" + this.getId();
}
} }

View File

@ -203,19 +203,87 @@ public class FlowSnippetDTO {
UUID id = UUID.fromString(componentDto.getId()); UUID id = UUID.fromString(componentDto.getId());
id = new UUID(id.getMostSignificantBits(), 0); id = new UUID(id.getMostSignificantBits(), 0);
componentDto.setId(id.toString()); componentDto.setId(id.toString());
id = UUID.fromString(componentDto.getParentGroupId());
id = new UUID(id.getMostSignificantBits(), 0);
componentDto.setParentGroupId(id.toString());
if (componentDto instanceof ConnectionDTO) { if (componentDto instanceof ConnectionDTO) {
ConnectionDTO connectionDTO = (ConnectionDTO) componentDto; ConnectionDTO connectionDTO = (ConnectionDTO) componentDto;
ConnectableDTO cdto = connectionDTO.getSource(); ConnectableDTO cdto = connectionDTO.getSource();
id = UUID.fromString(cdto.getId()); id = UUID.fromString(cdto.getId());
id = new UUID(id.getMostSignificantBits(), 0); id = new UUID(id.getMostSignificantBits(), 0);
cdto.setId(id.toString()); cdto.setId(id.toString());
id = UUID.fromString(cdto.getGroupId());
id = new UUID(id.getMostSignificantBits(), 0);
cdto.setGroupId(id.toString());
cdto = connectionDTO.getDestination(); cdto = connectionDTO.getDestination();
id = UUID.fromString(cdto.getId()); id = UUID.fromString(cdto.getId());
id = new UUID(id.getMostSignificantBits(), 0); id = new UUID(id.getMostSignificantBits(), 0);
cdto.setId(id.toString()); cdto.setId(id.toString());
id = UUID.fromString(cdto.getGroupId());
id = new UUID(id.getMostSignificantBits(), 0);
cdto.setGroupId(id.toString());
}
if (componentDto instanceof ProcessGroupDTO) {
FlowSnippetDTO fsDTO = ((ProcessGroupDTO) componentDto).getContents();
this.removeInstanceIdentifierIfNecessary(fsDTO.getConnections());
fsDTO.connections = this.orderedById(fsDTO.getConnections());
this.removeInstanceIdentifierIfNecessary(fsDTO.getControllerServices());
fsDTO.controllerServices = this.orderedById(fsDTO.getControllerServices());
this.removeInstanceIdentifierIfNecessary(fsDTO.getFunnels());
fsDTO.funnels = this.orderedById(fsDTO.getFunnels());
this.removeInstanceIdentifierIfNecessary(fsDTO.getInputPorts());
fsDTO.inputPorts = this.orderedById(fsDTO.getInputPorts());
this.removeInstanceIdentifierIfNecessary(fsDTO.getLabels());
fsDTO.labels = this.orderedById(fsDTO.getLabels());
this.removeInstanceIdentifierIfNecessary(fsDTO.getOutputPorts());
fsDTO.outputPorts = this.orderedById(fsDTO.getOutputPorts());
this.removeInstanceIdentifierIfNecessary(fsDTO.getProcessGroups());
fsDTO.processGroups = this.orderedById(fsDTO.getProcessGroups());
this.removeInstanceIdentifierIfNecessary(fsDTO.getProcessors());
fsDTO.processors = this.orderedById(fsDTO.getProcessors());
this.removeInstanceIdentifierIfNecessary(fsDTO.getRemoteProcessGroups());
fsDTO.remoteProcessGroups = this.orderedById(fsDTO.getRemoteProcessGroups());
} else if (componentDto instanceof RemoteProcessGroupDTO) {
RemoteProcessGroupContentsDTO contentsDTO = ((RemoteProcessGroupDTO) componentDto).getContents();
for (RemoteProcessGroupPortDTO portDTO : contentsDTO.getInputPorts()) {
id = UUID.fromString(portDTO.getId());
id = new UUID(id.getMostSignificantBits(), 0);
portDTO.setId(new UUID(id.getMostSignificantBits(), 0).toString());
}
for (RemoteProcessGroupPortDTO portDTO : contentsDTO.getOutputPorts()) {
id = UUID.fromString(portDTO.getId());
portDTO.setId(new UUID(id.getMostSignificantBits(), 0).toString());
}
contentsDTO.setInputPorts(this.orderedRemotePortsById(contentsDTO.getInputPorts()));
contentsDTO.setOutputPorts(this.orderedRemotePortsById(contentsDTO.getOutputPorts()));
} }
} }
} }
} }
private <T extends RemoteProcessGroupPortDTO> Set<T> orderedRemotePortsById(Set<T> dtos) {
TreeSet<T> components = new TreeSet<>(new Comparator<RemoteProcessGroupPortDTO>() {
@Override
public int compare(RemoteProcessGroupPortDTO c1, RemoteProcessGroupPortDTO c2) {
return UUID.fromString(c1.getId()).compareTo(UUID.fromString(c2.getId()));
}
});
components.addAll(dtos);
return components;
}
} }

View File

@ -41,7 +41,7 @@ import org.apache.nifi.cluster.protocol.NodeIdentifier;
import org.apache.nifi.events.EventReporter; import org.apache.nifi.events.EventReporter;
import org.apache.nifi.reporting.Severity; import org.apache.nifi.reporting.Severity;
import org.apache.nifi.util.FormatUtils; import org.apache.nifi.util.FormatUtils;
import org.apache.nifi.util.TypeOneUUIDGenerator; import org.apache.nifi.util.ComponentIdGenerator;
import org.apache.nifi.web.security.ProxiedEntitiesUtils; import org.apache.nifi.web.security.ProxiedEntitiesUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -219,7 +219,7 @@ public class ThreadPoolRequestReplicator implements RequestReplicator {
final boolean indicateReplicated, final boolean performVerification) { final boolean indicateReplicated, final boolean performVerification) {
final Map<String, String> updatedHeaders = new HashMap<>(headers); final Map<String, String> updatedHeaders = new HashMap<>(headers);
updatedHeaders.put(RequestReplicator.CLUSTER_ID_GENERATION_SEED_HEADER, TypeOneUUIDGenerator.generateId().toString()); updatedHeaders.put(RequestReplicator.CLUSTER_ID_GENERATION_SEED_HEADER, ComponentIdGenerator.generateId().toString());
if (indicateReplicated) { if (indicateReplicated) {
updatedHeaders.put(RequestReplicator.REPLICATION_INDICATOR_HEADER, "true"); updatedHeaders.put(RequestReplicator.REPLICATION_INDICATOR_HEADER, "true");
} }

View File

@ -186,6 +186,7 @@ import org.apache.nifi.stream.io.StreamUtils;
import org.apache.nifi.util.FormatUtils; import org.apache.nifi.util.FormatUtils;
import org.apache.nifi.util.NiFiProperties; import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.util.ReflectionUtils; import org.apache.nifi.util.ReflectionUtils;
import org.apache.nifi.util.ComponentIdGenerator;
import org.apache.nifi.web.ResourceNotFoundException; import org.apache.nifi.web.ResourceNotFoundException;
import org.apache.nifi.web.api.dto.ConnectableDTO; import org.apache.nifi.web.api.dto.ConnectableDTO;
import org.apache.nifi.web.api.dto.ConnectionDTO; import org.apache.nifi.web.api.dto.ConnectionDTO;
@ -515,9 +516,10 @@ public class FlowController implements EventAccess, ControllerServiceProvider, R
this.snippetManager = new SnippetManager(); this.snippetManager = new SnippetManager();
rootGroup = new StandardProcessGroup(UUID.randomUUID().toString(), this, processScheduler, properties, encryptor, this, this.variableRegistry); rootGroup = new StandardProcessGroup(ComponentIdGenerator.generateId().toString(), this, processScheduler,
properties, encryptor, this, this.variableRegistry);
rootGroup.setName(DEFAULT_ROOT_GROUP_NAME); rootGroup.setName(DEFAULT_ROOT_GROUP_NAME);
instanceId = UUID.randomUUID().toString(); instanceId = ComponentIdGenerator.generateId().toString();
controllerServiceProvider = new StandardControllerServiceProvider(this, processScheduler, bulletinRepository, stateManagerProvider, this.variableRegistry); controllerServiceProvider = new StandardControllerServiceProvider(this, processScheduler, bulletinRepository, stateManagerProvider, this.variableRegistry);

View File

@ -22,7 +22,6 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -43,9 +42,7 @@ import org.apache.nifi.web.api.dto.ProcessorConfigDTO;
import org.apache.nifi.web.api.dto.ProcessorDTO; import org.apache.nifi.web.api.dto.ProcessorDTO;
import org.apache.nifi.web.api.dto.PropertyDescriptorDTO; import org.apache.nifi.web.api.dto.PropertyDescriptorDTO;
import org.apache.nifi.web.api.dto.RelationshipDTO; import org.apache.nifi.web.api.dto.RelationshipDTO;
import org.apache.nifi.web.api.dto.RemoteProcessGroupContentsDTO;
import org.apache.nifi.web.api.dto.RemoteProcessGroupDTO; import org.apache.nifi.web.api.dto.RemoteProcessGroupDTO;
import org.apache.nifi.web.api.dto.RemoteProcessGroupPortDTO;
import org.apache.nifi.web.api.dto.TemplateDTO; import org.apache.nifi.web.api.dto.TemplateDTO;
import org.w3c.dom.Element; import org.w3c.dom.Element;
@ -282,45 +279,6 @@ public class TemplateUtils {
remoteProcessGroupDTO.setName(null); remoteProcessGroupDTO.setName(null);
remoteProcessGroupDTO.setTargetSecure(null); remoteProcessGroupDTO.setTargetSecure(null);
remoteProcessGroupDTO.setTransmitting(null); remoteProcessGroupDTO.setTransmitting(null);
// if this remote process group has contents
if (remoteProcessGroupDTO.getContents() != null) {
RemoteProcessGroupContentsDTO contents = remoteProcessGroupDTO.getContents();
// scrub any remote input ports
if (contents.getInputPorts() != null) {
scrubRemotePorts(contents.getInputPorts());
}
// scrub and remote output ports
if (contents.getOutputPorts() != null) {
scrubRemotePorts(contents.getOutputPorts());
}
}
}
}
/**
* Remove unnecessary fields in remote ports prior to saving.
*
* @param remotePorts ports
*/
private static void scrubRemotePorts(final Set<RemoteProcessGroupPortDTO> remotePorts) {
for (final Iterator<RemoteProcessGroupPortDTO> remotePortIter = remotePorts.iterator(); remotePortIter.hasNext();) {
final RemoteProcessGroupPortDTO remotePortDTO = remotePortIter.next();
// if the flow is not connected to this remote port, remove it
if (remotePortDTO.isConnected() == null || !remotePortDTO.isConnected().booleanValue()) {
remotePortIter.remove();
continue;
}
remotePortDTO.setExists(null);
remotePortDTO.setTargetRunning(null);
remotePortDTO.setConnected(null);
remotePortDTO.setExists(null);
remotePortDTO.setTargetRunning(null);
remotePortDTO.setTransmitting(null);
} }
} }
} }

View File

@ -33,7 +33,7 @@ import javax.xml.bind.JAXBElement;
import javax.xml.bind.Unmarshaller; import javax.xml.bind.Unmarshaller;
import javax.xml.transform.stream.StreamSource; import javax.xml.transform.stream.StreamSource;
import org.apache.nifi.util.TypeOneUUIDGenerator; import org.apache.nifi.util.ComponentIdGenerator;
import org.apache.nifi.web.api.dto.FlowSnippetDTO; import org.apache.nifi.web.api.dto.FlowSnippetDTO;
import org.apache.nifi.web.api.dto.ProcessorDTO; import org.apache.nifi.web.api.dto.ProcessorDTO;
import org.apache.nifi.web.api.dto.TemplateDTO; import org.apache.nifi.web.api.dto.TemplateDTO;
@ -55,7 +55,7 @@ public class TemplateSerializerTest {
for (int i = 4; i > 0; i--) { for (int i = 4; i > 0; i--) {
ProcessorDTO procDTO = new ProcessorDTO(); ProcessorDTO procDTO = new ProcessorDTO();
procDTO.setType("Processor" + i + ".class"); procDTO.setType("Processor" + i + ".class");
procDTO.setId(TypeOneUUIDGenerator.generateId().toString()); procDTO.setId(ComponentIdGenerator.generateId().toString());
procs.add(procDTO); procs.add(procDTO);
} }
snippet.setProcessors(procs); snippet.setProcessors(procs);
@ -86,7 +86,7 @@ public class TemplateSerializerTest {
// add new Processor // add new Processor
ProcessorDTO procDTO = new ProcessorDTO(); ProcessorDTO procDTO = new ProcessorDTO();
procDTO.setType("ProcessorNew" + ".class"); procDTO.setType("ProcessorNew" + ".class");
procDTO.setId(TypeOneUUIDGenerator.generateId().toString()); procDTO.setId(ComponentIdGenerator.generateId().toString());
deserProcs.add(procDTO); deserProcs.add(procDTO);
// Serialize modified template // Serialize modified template

View File

@ -41,7 +41,7 @@ import org.apache.nifi.remote.exception.NotAuthorizedException;
import org.apache.nifi.remote.protocol.ResponseCode; import org.apache.nifi.remote.protocol.ResponseCode;
import org.apache.nifi.remote.protocol.http.HttpHeaders; import org.apache.nifi.remote.protocol.http.HttpHeaders;
import org.apache.nifi.util.NiFiProperties; import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.util.TypeOneUUIDGenerator; import org.apache.nifi.util.ComponentIdGenerator;
import org.apache.nifi.authorization.AuthorizableLookup; import org.apache.nifi.authorization.AuthorizableLookup;
import org.apache.nifi.authorization.AuthorizeAccess; import org.apache.nifi.authorization.AuthorizeAccess;
import org.apache.nifi.web.NiFiServiceFacade; import org.apache.nifi.web.NiFiServiceFacade;
@ -205,14 +205,15 @@ public abstract class ApplicationResource {
if (seed.isPresent()) { if (seed.isPresent()) {
try { try {
UUID seedId = UUID.fromString(seed.get()); UUID seedId = UUID.fromString(seed.get());
uuid = new UUID(seedId.getMostSignificantBits(), Math.abs(seed.get().hashCode())); uuid = new UUID(seedId.getMostSignificantBits(), seed.get().hashCode());
} catch (Exception e) { } catch (Exception e) {
logger.warn("Provided 'seed' does not represent UUID. Will not be able to extract most significant bits for ID generation."); logger.warn("Provided 'seed' does not represent UUID. Will not be able to extract most significant bits for ID generation.");
uuid = UUID.nameUUIDFromBytes(seed.get().getBytes(StandardCharsets.UTF_8)); uuid = UUID.nameUUIDFromBytes(seed.get().getBytes(StandardCharsets.UTF_8));
} }
} else { } else {
uuid = TypeOneUUIDGenerator.generateId(); uuid = ComponentIdGenerator.generateId();
} }
return uuid.toString(); return uuid.toString();
} }

View File

@ -16,6 +16,22 @@
*/ */
package org.apache.nifi.web.util; package org.apache.nifi.web.util;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.authorization.AccessPolicy; import org.apache.nifi.authorization.AccessPolicy;
import org.apache.nifi.authorization.RequestAction; import org.apache.nifi.authorization.RequestAction;
@ -36,7 +52,7 @@ import org.apache.nifi.controller.service.ControllerServiceNode;
import org.apache.nifi.controller.service.ControllerServiceState; import org.apache.nifi.controller.service.ControllerServiceState;
import org.apache.nifi.groups.ProcessGroup; import org.apache.nifi.groups.ProcessGroup;
import org.apache.nifi.groups.RemoteProcessGroup; import org.apache.nifi.groups.RemoteProcessGroup;
import org.apache.nifi.util.TypeOneUUIDGenerator; import org.apache.nifi.util.ComponentIdGenerator;
import org.apache.nifi.web.api.dto.AccessPolicyDTO; import org.apache.nifi.web.api.dto.AccessPolicyDTO;
import org.apache.nifi.web.api.dto.ConnectableDTO; import org.apache.nifi.web.api.dto.ConnectableDTO;
import org.apache.nifi.web.api.dto.ConnectionDTO; import org.apache.nifi.web.api.dto.ConnectionDTO;
@ -58,19 +74,6 @@ import org.apache.nifi.web.dao.AccessPolicyDAO;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
/** /**
* Template utilities. * Template utilities.
*/ */
@ -78,6 +81,8 @@ public final class SnippetUtils {
private static final Logger logger = LoggerFactory.getLogger(SnippetUtils.class); private static final Logger logger = LoggerFactory.getLogger(SnippetUtils.class);
private static final SecureRandom randomGenerator = new SecureRandom();
private FlowController flowController; private FlowController flowController;
private DtoFactory dtoFactory; private DtoFactory dtoFactory;
private AccessPolicyDAO accessPolicyDAO; private AccessPolicyDAO accessPolicyDAO;
@ -771,15 +776,44 @@ public final class SnippetUtils {
} }
/** /**
* Generates a new id for the current id that is specified. If no seed is found, a new random id will be created. * Generates a new type 1 id (UUID) for the current id that is specified. If
* seed is provided, it will be incorporated into generation logic of the
* new ID.
* The contract of this method is as follows:
* - The 'currentId' must never be null and it must be String representation
* of type-one UUID.
* - If seed is provided, the new ID will be generated from the 'msb' extracted from
* the 'currentId' and the 'lsb' extracted from the UUID generated via
* UUID.nameUUIDFromBytes(currentId + seed).
* - If seed is NOT provided and 'isCopy' flag is set the new ID will be generated from
* the 'msb' extracted from the 'currentId' and random integer as 'lsb'. In this case
* the new ID will always be > the previous ID essentially resulting in the new ID for
* the component that being copied (e.g., copy/paste).
* - If seed is NOT provided and 'isCopy' flag is NOT set the new ID will be generated from
* the 'msb' extracted from the 'currentId' and random integer as 'lsb'.
*/ */
private String generateId(final String currentId, final String seed, boolean isCopy) { private String generateId(final String currentId, final String seed, boolean isCopy) {
long msb = UUID.fromString(currentId).getMostSignificantBits(); long msb = UUID.fromString(currentId).getMostSignificantBits();
int lsb = StringUtils.isBlank(seed)
? Math.abs(new Random().nextInt())
: Math.abs(seed.hashCode());
return isCopy ? TypeOneUUIDGenerator.generateId(msb, lsb).toString() : new UUID(msb, lsb).toString(); UUID uuid;
if (StringUtils.isBlank(seed)) {
long lsb = randomGenerator.nextLong();
if (isCopy) {
uuid = ComponentIdGenerator.generateId(msb, lsb, true); // will increment msb if necessary
} else {
// since msb is extracted from type-one UUID, the type-one semantics will be preserved
uuid = new UUID(msb, lsb);
}
} else {
UUID seedId = UUID.nameUUIDFromBytes((currentId + seed).getBytes(StandardCharsets.UTF_8));
if (isCopy) {
// will ensure the type-one semantics for new UUID generated from msb extracted from seedId
uuid = ComponentIdGenerator.generateId(seedId.getMostSignificantBits(), seedId.getLeastSignificantBits(), false);
} else {
uuid = new UUID(msb, seedId.getLeastSignificantBits());
}
}
return uuid.toString();
} }
/* setters */ /* setters */

View File

@ -0,0 +1,219 @@
/*
* 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.nifi.web.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import org.apache.nifi.util.ComponentIdGenerator;
import org.junit.Test;
public class SnippetUtilsTest {
/*
* This test validates condition where component is being replicated across
* the cluster
*/
@Test
public void validateWithSameSeedSameInceptionIdSameInstanceId() throws Exception {
Method generateIdMethod = SnippetUtils.class.getDeclaredMethod("generateId", String.class, String.class,
boolean.class);
generateIdMethod.setAccessible(true);
SnippetUtils utils = new SnippetUtils();
String currentId = ComponentIdGenerator.generateId().toString();
String seed = ComponentIdGenerator.generateId().toString();
String id1 = (String) generateIdMethod.invoke(utils, currentId, seed, true);
String id2 = (String) generateIdMethod.invoke(utils, currentId, seed, true);
String id3 = (String) generateIdMethod.invoke(utils, currentId, seed, true);
assertEquals(id1, id2);
assertEquals(id2, id3);
}
/*
* This test validates condition where components that are being copy/pasted from
* one another are now replicated across the cluster. Such components will
* have different inception id (msb) yet different instance id (lsb). The id
* of these components must be different yet their msb must be the same.
*/
@Test
public void validateWithSameSeedSameInceptionIdNotSameInstanceIdIsCopySet() throws Exception {
Method generateIdMethod = SnippetUtils.class.getDeclaredMethod("generateId", String.class, String.class,
boolean.class);
generateIdMethod.setAccessible(true);
SnippetUtils utils = new SnippetUtils();
String seed = ComponentIdGenerator.generateId().toString();
UUID rootId = ComponentIdGenerator.generateId();
String id1 = (String) generateIdMethod.invoke(utils,
new UUID(rootId.getMostSignificantBits(), ComponentIdGenerator.generateId().getLeastSignificantBits()).toString(),
seed, true);
String id2 = (String) generateIdMethod.invoke(utils,
new UUID(rootId.getMostSignificantBits(), ComponentIdGenerator.generateId().getLeastSignificantBits()).toString(),
seed, true);
String id3 = (String) generateIdMethod.invoke(utils,
new UUID(rootId.getMostSignificantBits(), ComponentIdGenerator.generateId().getLeastSignificantBits()).toString(),
seed, true);
assertNotEquals(id1, id2);
assertNotEquals(id2, id3);
UUID uuid1 = UUID.fromString(id1);
UUID uuid2 = UUID.fromString(id2);
UUID uuid3 = UUID.fromString(id3);
// below simply validates that generated UUID is type-one, since timestamp() operation will result
// in exception if generated UUID is not type-one
uuid1.timestamp();
uuid2.timestamp();
uuid3.timestamp();
assertNotEquals(uuid1.getMostSignificantBits(), uuid2.getMostSignificantBits());
assertNotEquals(uuid2.getMostSignificantBits(), uuid3.getMostSignificantBits());
}
/*
* This test validates condition where components that are being re-created
* from template are now replicated across the cluster. Such components will
* have the same inception id (msb) yet different instance id (lsb). The id
* of these components must be different yet their msb must be the same.
*/
@Test
public void validateWithSameSeedSameInceptionIdNotSameInstanceIdIsCopyNotSet() throws Exception {
Method generateIdMethod = SnippetUtils.class.getDeclaredMethod("generateId", String.class, String.class,
boolean.class);
generateIdMethod.setAccessible(true);
SnippetUtils utils = new SnippetUtils();
String seed = ComponentIdGenerator.generateId().toString();
UUID rootId = ComponentIdGenerator.generateId();
String id1 = (String) generateIdMethod.invoke(utils,
new UUID(rootId.getMostSignificantBits(), ComponentIdGenerator.generateId().getLeastSignificantBits()).toString(),
seed, false);
String id2 = (String) generateIdMethod.invoke(utils,
new UUID(rootId.getMostSignificantBits(), ComponentIdGenerator.generateId().getLeastSignificantBits()).toString(),
seed, false);
String id3 = (String) generateIdMethod.invoke(utils,
new UUID(rootId.getMostSignificantBits(), ComponentIdGenerator.generateId().getLeastSignificantBits()).toString(),
seed, false);
assertNotEquals(id1, id2);
assertNotEquals(id2, id3);
UUID uuid1 = UUID.fromString(id1);
UUID uuid2 = UUID.fromString(id2);
UUID uuid3 = UUID.fromString(id3);
// below simply validates that generated UUID is type-one, since timestamp() operation will result
// in exception if generated UUID is not type-one
uuid1.timestamp();
uuid2.timestamp();
uuid3.timestamp();
assertEquals(uuid1.getMostSignificantBits(), uuid2.getMostSignificantBits());
assertEquals(uuid2.getMostSignificantBits(), uuid3.getMostSignificantBits());
}
/*
* This test validates condition where components are being copied from one
* another. The ids of each components must be completely different (msb and
* lsb) yet each subsequent msb must be > then previous component's msb.
*/
@Test
public void validateWithoutSeedSameCurrentIdIsCopySet() throws Exception {
Method generateIdMethod = SnippetUtils.class.getDeclaredMethod("generateId", String.class, String.class,
boolean.class);
generateIdMethod.setAccessible(true);
boolean isCopy = true;
SnippetUtils utils = new SnippetUtils();
String currentId = ComponentIdGenerator.generateId().toString();
UUID id1 = UUID.fromString((String) generateIdMethod.invoke(utils, currentId, null, isCopy));
UUID id2 = UUID.fromString((String) generateIdMethod.invoke(utils, currentId, null, isCopy));
UUID id3 = UUID.fromString((String) generateIdMethod.invoke(utils, currentId, null, isCopy));
// below simply validates that generated UUID is type-one, since timestamp() operation will result
// in exception if generated UUID is not type-one
id1.timestamp();
id2.timestamp();
id3.timestamp();
assertTrue(id1.getMostSignificantBits() < id2.getMostSignificantBits());
assertTrue(id2.getMostSignificantBits() < id3.getMostSignificantBits());
}
/*
* This test validates condition where new components are being created from
* existing components such as from imported templates. In this case their
* instance id (lsb) is irrelevant and new instance id would have to be
* generated every time yet its inception id (msb) must remain the same.
*/
@Test
public void validateWithoutSeedSameCurrentIdIsCopyNotSet() throws Exception {
Method generateIdMethod = SnippetUtils.class.getDeclaredMethod("generateId", String.class, String.class,
boolean.class);
generateIdMethod.setAccessible(true);
boolean isCopy = false;
SnippetUtils utils = new SnippetUtils();
String currentId = ComponentIdGenerator.generateId().toString();
UUID id1 = UUID.fromString((String) generateIdMethod.invoke(utils, currentId, null, isCopy));
UUID id2 = UUID.fromString((String) generateIdMethod.invoke(utils, currentId, null, isCopy));
UUID id3 = UUID.fromString((String) generateIdMethod.invoke(utils, currentId, null, isCopy));
// below simply validates that generated UUID is type-one, since timestamp() operation will result
// in exception if generated UUID is not type-one
id1.timestamp();
id2.timestamp();
id3.timestamp();
assertEquals(id1.getMostSignificantBits(), id2.getMostSignificantBits());
assertEquals(id2.getMostSignificantBits(), id3.getMostSignificantBits());
}
/*
* This test simply validates that generated IDs are comparable and
* sequentially correct where each subsequent ID is > previous ID.
*/
@Test
public void validateIdOrdering() throws Exception {
UUID seed = ComponentIdGenerator.generateId();
UUID currentId1 = ComponentIdGenerator.generateId();
UUID currentId2 = ComponentIdGenerator.generateId();
UUID currentId3 = ComponentIdGenerator.generateId();
UUID id1 = new UUID(currentId1.getMostSignificantBits(),
UUID.nameUUIDFromBytes((currentId1.toString() + seed.toString()).getBytes(StandardCharsets.UTF_8))
.getLeastSignificantBits());
UUID id2 = new UUID(currentId2.getMostSignificantBits(),
UUID.nameUUIDFromBytes((currentId2.toString() + seed.toString()).getBytes(StandardCharsets.UTF_8))
.getLeastSignificantBits());
UUID id3 = new UUID(currentId3.getMostSignificantBits(),
UUID.nameUUIDFromBytes((currentId3.toString() + seed.toString()).getBytes(StandardCharsets.UTF_8))
.getLeastSignificantBits());
List<UUID> list = new ArrayList<>();
list.add(id2);
list.add(id3);
list.add(id1);
Collections.sort(list);
assertEquals(id1, list.get(0));
assertEquals(id2, list.get(1));
assertEquals(id3, list.get(2));
}
}