/** * Tries to claim the given token {@code entry}. If the claim fails an {@link UnableToClaimTokenException} should be * thrown. Otherwise the given {@code resultSet} should be updated to reflect the claim. * * @param resultSet the updatable query result of an executed {@link PreparedStatement} * @param entry the entry extracted from the given result set * @return the claimed tracking token * * @throws UnableToClaimTokenException if the token cannot be claimed because another node currently owns the token * @throws SQLException when an exception occurs while claiming the token entry */ protected TrackingToken claimToken(ResultSet resultSet, AbstractTokenEntry<?> entry) throws SQLException { if (!entry.claim(nodeId, claimTimeout)) { throw new UnableToClaimTokenException( format("Unable to claim token '%s[%s]'. It is owned by '%s'", entry.getProcessorName(), entry.getSegment(), entry.getOwner())); } resultSet.updateString(schema.ownerColum(), entry.getOwner()); resultSet.updateString(schema.timestampColumn(), entry.timestampAsString()); resultSet.updateRow(); return entry.getToken(serializer); }
/** * Inserts a new token entry via the given updatable {@code resultSet}. * * @param resultSet the updatable result set to add the entry to * @param token the token of the entry to insert * @param processorName the name of the processor to insert a token for * @param segment the segment of the processor to insert a token for * @return the tracking token of the inserted entry * * @throws SQLException when an exception occurs while inserting a token entry */ protected TrackingToken insertTokenEntry(ResultSet resultSet, TrackingToken token, String processorName, int segment) throws SQLException { AbstractTokenEntry<?> entry = new GenericTokenEntry<>(token, serializer, contentType, processorName, segment); entry.claim(nodeId, claimTimeout); resultSet.moveToInsertRow(); resultSet.updateObject(schema.tokenColumn(), token == null ? null : entry.getSerializedToken().getData()); resultSet.updateString(schema.tokenTypeColumn(), token == null ? null : entry.getSerializedToken().getType().getName()); resultSet.updateString(schema.timestampColumn(), entry.timestampAsString()); resultSet.updateString(schema.ownerColum(), entry.getOwner()); resultSet.updateString(schema.processorNameColumn(), processorName); resultSet.updateInt(schema.segmentColumn(), segment); resultSet.insertRow(); return token; }
private boolean expired(TemporalAmount claimTimeout) { return timestamp().plus(claimTimeout).isBefore(clock.instant()); }
/** * If the given {@code resultSet} has no items this method should insert a new token entry. If a token already * exists it should be attempted to replace the token in the entry with the given {@code token} and claim ownership. * * @param resultSet the updatable query result set of an executed {@link PreparedStatement} * @param token the token for the new or updated entry * @param processorName the name of the processor owning the token * @param segment the segment of the processor owning the token * @throws UnableToClaimTokenException if the token cannot be claimed because another node currently owns the token * @throws SQLException when an exception occurs while updating the result set */ protected void insertOrUpdateToken(ResultSet resultSet, TrackingToken token, String processorName, int segment) throws SQLException { if (resultSet.next()) { AbstractTokenEntry<?> entry = readTokenEntry(resultSet); entry.updateToken(token, serializer); resultSet.updateObject(schema.tokenColumn(), entry.getSerializedToken().getData()); resultSet.updateString(schema.tokenTypeColumn(), entry.getSerializedToken().getType().getName()); resultSet.updateString(schema.timestampColumn(), entry.timestampAsString()); claimToken(resultSet, entry); } else { insertTokenEntry(resultSet, token, processorName, segment); } }
private Document tokenEntryToDocument(AbstractTokenEntry<?> tokenEntry) { return new Document("processorName", tokenEntry.getProcessorName()) .append("segment", tokenEntry.getSegment()) .append("owner", tokenEntry.getOwner()) .append("timestamp", tokenEntry.timestamp().toEpochMilli()) .append("token", tokenEntry.getSerializedToken() == null ? null : tokenEntry.getSerializedToken().getData()) .append("tokenType", tokenEntry.getSerializedToken() == null ? null : tokenEntry.getSerializedToken() .getContentType().getName()); }
private void updateOrInsertTokenEntry(TrackingToken token, String processorName, int segment) { AbstractTokenEntry<?> tokenEntry = new GenericTokenEntry<>(token, serializer, contentType, processorName, segment); tokenEntry.claim(nodeId, claimTimeout); Bson update = combine(set("owner", nodeId), set("timestamp", tokenEntry.timestamp().toEpochMilli()), set("token", tokenEntry.getSerializedToken().getData()), set("tokenType", tokenEntry.getSerializedToken().getType().getName())); UpdateResult updateResult = mongoTemplate.trackingTokensCollection() .updateOne(claimableTokenEntryFilter(processorName, segment), update); if (updateResult.getModifiedCount() == 0) { try { mongoTemplate.trackingTokensCollection() .insertOne(tokenEntryToDocument(tokenEntry)); } catch (MongoWriteException exception) { if (ErrorCategory.fromErrorCode(exception.getError().getCode()) == ErrorCategory.DUPLICATE_KEY) { throw new UnableToClaimTokenException(format("Unable to claim token '%s[%s]'", processorName, segment)); } } } }
/** * Returns the token, deserializing it with given {@code serializer} * * @param serializer The serialize to deserialize the token with * @return the deserialized token stored in this entry */ public TrackingToken getToken(Serializer serializer) { return token == null ? null : serializer.deserialize(getSerializedToken()); }
/** * {@inheritDoc} */ @Override public TrackingToken fetchToken(String processorName, int segment) throws UnableToClaimTokenException { AbstractTokenEntry<?> tokenEntry = loadOrInsertTokenEntry(processorName, segment); return tokenEntry.getToken(serializer); }
private AbstractTokenEntry<?> loadOrInsertTokenEntry(String processorName, int segment) { Document document = mongoTemplate.trackingTokensCollection() .findOneAndUpdate(claimableTokenEntryFilter(processorName, segment), combine(set("owner", nodeId), set("timestamp", clock.millis())), new FindOneAndUpdateOptions() .returnDocument(ReturnDocument.AFTER)); if (document == null) { try { AbstractTokenEntry<?> tokenEntry = new GenericTokenEntry<>(null, serializer, contentType, processorName, segment); tokenEntry.claim(nodeId, claimTimeout); mongoTemplate.trackingTokensCollection() .insertOne(tokenEntryToDocument(tokenEntry)); return tokenEntry; } catch (MongoWriteException exception) { if (ErrorCategory.fromErrorCode(exception.getError().getCode()) == ErrorCategory.DUPLICATE_KEY) { throw new UnableToClaimTokenException(format("Unable to claim token '%s[%s]'", processorName, segment)); } } } return documentToTokenEntry(document); }
/** * Returns the serialized token. * * @return the serialized token stored in this entry */ public SerializedObject<T> getSerializedToken() { if (token == null) { return null; } return new SimpleSerializedObject<>(token, (Class<T>) token.getClass(), getTokenType()); }
/** * Check if given {@code owner} may claim this token. * * @param owner The name of the current node, to register as owner. This name must be unique for multiple * instances of the same logical processor * @param claimTimeout The time after which a claim may be 'stolen' from its current owner * @return {@code true} if the claim may be made, {@code false} otherwise */ public boolean mayClaim(String owner, TemporalAmount claimTimeout) { return this.owner == null || owner.equals(this.owner) || expired(claimTimeout); }
/** * Attempt to claim ownership of this token. When successful, this method returns {@code true}, otherwise * {@code false}. When a claim fails, this token should not be used, as it is already being used in another process. * <p> * If a claim exists, but it is older than given {@code claimTimeout}, the claim may be 'stolen'. * * @param owner The name of the current node, to register as owner. This name must be unique for multiple * instances of the same logical processor * @param claimTimeout The time after which a claim may be 'stolen' from its current owner * @return {@code true} if the claim succeeded, otherwise false */ public boolean claim(String owner, TemporalAmount claimTimeout) { if (!mayClaim(owner, claimTimeout)) { return false; } this.timestamp = formatInstant(clock.instant()); this.owner = owner; return true; }
private Document tokenEntryToDocument(AbstractTokenEntry<?> tokenEntry) { return new Document("processorName", tokenEntry.getProcessorName()) .append("segment", tokenEntry.getSegment()) .append("owner", tokenEntry.getOwner()) .append("timestamp", tokenEntry.timestamp().toEpochMilli()) .append("token", tokenEntry.getSerializedToken() == null ? null : tokenEntry.getSerializedToken().getData()) .append("tokenType", tokenEntry.getSerializedToken() == null ? null : tokenEntry.getSerializedToken() .getType().getName()); }
private void updateOrInsertTokenEntry(TrackingToken token, String processorName, int segment) { AbstractTokenEntry<?> tokenEntry = new GenericTokenEntry<>(token, serializer, contentType, processorName, segment); tokenEntry.claim(nodeId, claimTimeout); Bson update = combine(set("owner", nodeId), set("timestamp", tokenEntry.timestamp().toEpochMilli()), set("token", tokenEntry.getSerializedToken().getData()), set("tokenType", tokenEntry.getSerializedToken().getType().getName())); UpdateResult updateResult = mongoTemplate.trackingTokensCollection() .updateOne(claimableTokenEntryFilter(processorName, segment), update); if (updateResult.getModifiedCount() == 0) { try { mongoTemplate.trackingTokensCollection() .insertOne(tokenEntryToDocument(tokenEntry)); } catch (MongoWriteException exception) { if (ErrorCategory.fromErrorCode(exception.getError().getCode()) == ErrorCategory.DUPLICATE_KEY) { throw new UnableToClaimTokenException(format("Unable to claim token '%s[%s]'", processorName, segment)); } } } }
/** * If the given {@code resultSet} has no items this method should insert a new token entry. If a token already * exists it should be attempted to replace the token in the entry with the given {@code token} and claim ownership. * * @param resultSet the updatable query result set of an executed {@link PreparedStatement} * @param token the token for the new or updated entry * @param processorName the name of the processor owning the token * @param segment the segment of the processor owning the token * @throws UnableToClaimTokenException if the token cannot be claimed because another node currently owns the token * @throws SQLException when an exception occurs while updating the result set */ protected void insertOrUpdateToken(ResultSet resultSet, TrackingToken token, String processorName, int segment) throws SQLException { if (resultSet.next()) { AbstractTokenEntry<?> entry = readTokenEntry(resultSet); entry.updateToken(token, serializer); resultSet.updateObject(schema.tokenColumn(), entry.getSerializedToken().getData()); resultSet.updateString(schema.tokenTypeColumn(), entry.getSerializedToken().getType().getName()); resultSet.updateString(schema.timestampColumn(), entry.timestampAsString()); claimToken(resultSet, entry); } else { insertTokenEntry(resultSet, token, processorName, segment); } }
/** * Returns the token, deserializing it with given {@code serializer} * * @param serializer The serialize to deserialize the token with * @return the deserialized token stored in this entry */ public TrackingToken getToken(Serializer serializer) { return token == null ? null : serializer.deserialize(getSerializedToken()); }
/** * {@inheritDoc} */ @Override public TrackingToken fetchToken(String processorName, int segment) throws UnableToClaimTokenException { AbstractTokenEntry<?> tokenEntry = loadOrInsertTokenEntry(processorName, segment); return tokenEntry.getToken(serializer); }
private AbstractTokenEntry<?> loadOrInsertTokenEntry(String processorName, int segment) { Document document = mongoTemplate.trackingTokensCollection() .findOneAndUpdate(claimableTokenEntryFilter(processorName, segment), combine(set("owner", nodeId), set("timestamp", clock.millis())), new FindOneAndUpdateOptions() .returnDocument(ReturnDocument.AFTER)); if (document == null) { try { AbstractTokenEntry<?> tokenEntry = new GenericTokenEntry<>(null, serializer, contentType, processorName, segment); tokenEntry.claim(nodeId, claimTimeout); mongoTemplate.trackingTokensCollection() .insertOne(tokenEntryToDocument(tokenEntry)); return tokenEntry; } catch (MongoWriteException exception) { if (ErrorCategory.fromErrorCode(exception.getError().getCode()) == ErrorCategory.DUPLICATE_KEY) { throw new UnableToClaimTokenException(format("Unable to claim token '%s[%s]'", processorName, segment)); } } } return documentToTokenEntry(document); }
/** * Returns the serialized token. * * @return the serialized token stored in this entry */ public SerializedObject<T> getSerializedToken() { if (token == null) { return null; } return new SimpleSerializedObject<>(token, (Class<T>) token.getClass(), getTokenType()); }
/** * Check if given {@code owner} may claim this token. * * @param owner The name of the current node, to register as owner. This name must be unique for multiple * instances of the same logical processor * @param claimTimeout The time after which a claim may be 'stolen' from its current owner * @return {@code true} if the claim may be made, {@code false} otherwise */ public boolean mayClaim(String owner, TemporalAmount claimTimeout) { return this.owner == null || owner.equals(this.owner) || expired(claimTimeout); }