Skip to content

Commit

Permalink
[JBTM-3294] adding version HTTP headers version handling to coordinat…
Browse files Browse the repository at this point in the history
…or API
  • Loading branch information
ochaloup committed Feb 10, 2021
1 parent c07088e commit 7b355fe
Show file tree
Hide file tree
Showing 19 changed files with 1,458 additions and 215 deletions.
1 change: 1 addition & 0 deletions pom.xml
Expand Up @@ -497,6 +497,7 @@
<version.org.eclipse.microprofile.openapi>1.1.2</version.org.eclipse.microprofile.openapi>
<version.smallrye-converter-api>1.0.10</version.smallrye-converter-api>
<version.io.mashona>1.0.0.Beta1</version.io.mashona>
<version.hamcrest>1.3</version.hamcrest>

<!-- Maven plugin versions -->
<version.org.codehaus.mojo.jboss-maven-plugin>1.5.0</version.org.codehaus.mojo.jboss-maven-plugin>
Expand Down
Expand Up @@ -60,7 +60,6 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
Expand Down Expand Up @@ -744,9 +743,8 @@ private URI enlistCompensator(URI uri, Long timelimit, String linkHeader, String
String recoveryUrl = null;
try {
recoveryUrl = response.getHeaderString(LRA_HTTP_RECOVERY_HEADER);
String url = URLDecoder.decode(recoveryUrl, StandardCharsets.UTF_8.name());
return new URI(url);
} catch (URISyntaxException | UnsupportedEncodingException e) {
return new URI(recoveryUrl);
} catch (URISyntaxException e) {
LRALogger.logger.infof(e,"join %s returned an invalid recovery URI '%s': %s", lraId, recoveryUrl, responseEntity);
throwGenericLRAException(null, Response.Status.SERVICE_UNAVAILABLE.getStatusCode(),
"join " + lraId + " returned an invalid recovery URI '" + recoveryUrl + "' : " + responseEntity, e);
Expand Down

Large diffs are not rendered by default.

@@ -1,18 +1,61 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2020, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/

package io.narayana.lra.coordinator.api;

import io.narayana.lra.LRAConstants;
import io.narayana.lra.coordinator.internal.APIVersion;
import org.eclipse.microprofile.openapi.annotations.Components;
import org.eclipse.microprofile.openapi.annotations.OpenAPIDefinition;
import org.eclipse.microprofile.openapi.annotations.enums.ParameterIn;
import org.eclipse.microprofile.openapi.annotations.headers.Header;
import org.eclipse.microprofile.openapi.annotations.info.Contact;
import org.eclipse.microprofile.openapi.annotations.info.Info;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

// mark the war as a JAX-RS archive
@ApplicationPath("/")
@OpenAPIDefinition(
info = @Info(title = "LRA Coordinator", version = JaxRsActivator.LRA_API_VERSION),
tags = @Tag(name = "LRA Coordinator")
info = @Info(title = "LRA Coordinator", version = JaxRsActivator.LRA_API_VERSION_STRING,
contact = @Contact(name = "Narayana", url = "https://narayana.io")),
components = @Components(
parameters = {
@Parameter(name = LRAConstants.LRA_API_VERSION_HEADER_NAME, in = ParameterIn.HEADER,
description = "API version string in format [major].[minor]-[prerelease]. Major and minor are required and to be numbers, prerelease part is optional.")
},
headers = {
@Header(name = LRAConstants.LRA_API_VERSION_HEADER_NAME, description = "Narayana LRA API version that processed the request")
}
)
)
public class JaxRsActivator extends Application {
static final String LRA_API_VERSION = "1.0-RC1";
/**
* The LRA API version supported for the release.
* Any bigger version is considered as unimplemented and unknown.
* Any lower version is considered as older, implemented but deprecated and in case not supported.
*/
public static final String LRA_API_VERSION_STRING = "1.0-RC1";
public static final APIVersion LRA_API_VERSION = APIVersion.instanceOf(LRA_API_VERSION_STRING);
}
Expand Up @@ -112,11 +112,11 @@ public LRARecord() {
});

if (parseException[0] != null) {
String errorMsg = lraId + ": Invalid link URI: " + parseException[0];
String errorMsg = lra.getId() + ": Invalid link URI: " + parseException[0];
throw new WebApplicationException(errorMsg, parseException[0],
Response.status(BAD_REQUEST).entity(errorMsg).build());
} else if (compensateURI == null && afterURI == null) {
String errorMsg = lraId + ": Invalid link URI: missing compensator";
String errorMsg = lra.getId() + ": Invalid link URI: missing compensator or after LRA callback";
throw new WebApplicationException(errorMsg, Response.status(BAD_REQUEST).entity(errorMsg).build());
}
} else {
Expand Down Expand Up @@ -155,7 +155,7 @@ String getParticipantPath() {

static String cannonicalForm(String linkStr) throws URISyntaxException {
if (!linkStr.contains(">;")) {
return cannonicalURI(new URI(linkStr)).toASCIIString();
return new URI(linkStr).toASCIIString();
}

SortedMap<String, String> lm = new TreeMap<>();
Expand Down
Expand Up @@ -276,8 +276,9 @@ public LRAData endLRA(URI lraId, boolean compensate, boolean fromHierarchy) {
LongRunningAction transaction = getTransaction(lraId);

if (transaction.getLRAStatus() != LRAStatus.Active && !transaction.isRecovering() && transaction.isTopLevel()) {
throw new WebApplicationException(Response.status(Response.Status.PRECONDITION_FAILED)
.entity(String.format("%s: LRA is closing or closed: endLRA", lraId)).build());
String errorMsg = String.format("%s: LRA is closing or closed: endLRA", lraId);
throw new WebApplicationException(errorMsg, Response.status(Response.Status.PRECONDITION_FAILED)
.entity(errorMsg).build());
}

transaction.finishLRA(compensate);
Expand Down Expand Up @@ -305,14 +306,16 @@ public int leave(URI lraId, String compensatorUrl) {

try {
if (!transaction.forgetParticipant(compensatorUrl)) {
if (LRALogger.logger.isInfoEnabled()) {
LRALogger.logger.infof("LRAServicve.forget %s failed%n", lraId);
}
String errorMsg = String.format("LRAService.forget %s failed on participant compensator url '%s'",
lraId, compensatorUrl);
throw new WebApplicationException(errorMsg, Response.status(Response.Status.BAD_REQUEST)
.entity(errorMsg).build());
}

return Response.Status.OK.getStatusCode();
} catch (Exception e) {
return Response.Status.BAD_REQUEST.getStatusCode();
String errorMsg = String.format("LRAService.forget %s failed on finding participant '%s'", lraId, compensatorUrl);
throw new WebApplicationException(errorMsg, e, Response.status(Response.Status.BAD_REQUEST)
.entity(errorMsg).build());
}
}

Expand Down Expand Up @@ -408,7 +411,7 @@ public int renewTimeLimit(URI lraId, Long timelimit) {
LongRunningAction lra = lras.get(lraId);

if (lra == null) {
return Response.Status.PRECONDITION_FAILED.getStatusCode();
return NOT_FOUND.getStatusCode();
}

return lra.setTimeLimit(timelimit);
Expand Down
@@ -0,0 +1,125 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2020, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/

package io.narayana.lra.coordinator.internal;

import io.narayana.lra.coordinator.api.JaxRsActivator;

import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* <p>
* The LRA API version. The most probably provided in header {@code io.narayana.lra.LRAConstants#LRA_API_VERSION_HEADER_NAME}.
* The supported format is {@code #expectedFormat}.
* </p>
* <p>
* The <code>major</code> and <code>minor</code> parts are numbers, the <code>preRelease</code> part is an arbitrary string.
* Two instances of the {@link APIVersion} may be compared.
* There is taken into account only the <code>major</code> and <code>minor</code> parts,
* the <code>preRelease</code> is ignored.
* But two {@link APIVersion} instances are {@link Object#equals(Object)} only if all three parts are the same.
* </p>
*/
public class APIVersion implements Comparable<APIVersion> {
private static final String expectedFormat = "major.minor-preRelease";
private static final Pattern versionPattern = Pattern.compile("^(\\d+)\\.(\\d+)(?:-(.+))?");

private final int major, minor;
private final String preRelease;

/**
* Parsing the version string and returns a {@link APIVersion} instance.
* The expected version format is {@code #expectedFormat}.
* If <code>null</code> is provided as String to parse then the most up-to-date
* LRA API version is taken from {@link JaxRsActivator#LRA_API_VERSION}.
*
* @param versionString version string to be parsed; when null or empty the most up-to-date
* {@link JaxRsActivator#LRA_API_VERSION} is returned
* @return instance of the {@link APIVersion} class based on the parsed String
* @throws IllegalArgumentException thrown when version string has a wrong format
*/
public static APIVersion instanceOf(String versionString) {
Matcher versionMatcher = versionPattern.matcher(versionString);
if(versionString == null || versionString.isEmpty()) {
return JaxRsActivator.LRA_API_VERSION;
}
if(!versionMatcher.matches()) {
throw new IllegalArgumentException("Cannot parse provided version string " + versionString
+ " as it does not match the expected format '" + expectedFormat + "'");
}
try {
int major = Integer.valueOf(versionMatcher.group(1));
int minor = Integer.valueOf(versionMatcher.group(2));
String preRelease = versionMatcher.group(3);
return new APIVersion(major, minor, preRelease);
} catch (NumberFormatException nfe) {
throw new IllegalArgumentException("The version string " + versionString + " matches the expected format " + expectedFormat
+ " but the major.minor cannot be converted to numbers", nfe);
}
}

public APIVersion(int major, int minor, String preRelease) {
this.major = major;
this.minor = minor;
this.preRelease = preRelease;
}

@Override
public int compareTo(APIVersion anotherVersion) {
int result = Integer.compare(major, anotherVersion.major);
if (result == 0) {
result = Integer.compare(minor, anotherVersion.minor);
}
return result;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder()
.append(major).append(".").append(minor);
if (preRelease != null) {
sb.append("-").append(preRelease);
}
return sb.toString();
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
APIVersion that = (APIVersion) o;
return major == that.major &&
minor == that.minor &&
((preRelease == null && that.preRelease == null)
|| (preRelease != null && preRelease.equals(that.preRelease)));
}

@Override
public int hashCode() {
if (preRelease == null) {
return Objects.hash(major, minor);
} else {
return Objects.hash(major, minor, preRelease);
}
}
}
Expand Up @@ -21,15 +21,7 @@
*/
package io.narayana.lra.coordinator;

import com.arjuna.ats.arjuna.recovery.RecoveryModule;
import io.narayana.lra.Current;
import io.narayana.lra.client.NarayanaLRAClient;
import io.narayana.lra.client.internal.proxy.nonjaxrs.LRAParticipantRegistry;
import io.narayana.lra.coordinator.api.Coordinator;
import io.narayana.lra.LRAData;
import io.narayana.lra.coordinator.domain.model.LongRunningAction;
import io.narayana.lra.coordinator.domain.service.LRAService;
import io.narayana.lra.coordinator.internal.LRARecoveryModule;
import io.narayana.lra.filter.ServerLRAFilter;
import io.narayana.lra.logging.LRALogger;
import org.eclipse.microprofile.lra.annotation.LRAStatus;
Expand Down Expand Up @@ -72,20 +64,8 @@
@RunWith(Arquillian.class)
@RunAsClient
public class LRACoordinatorRecovery1TestCase extends TestBase {
private static final Package[] coordinatorPackages = {
RecoveryModule.class.getPackage(),
Coordinator.class.getPackage(),
LRAData.class.getPackage(),
LRAStatus.class.getPackage(),
LRALogger.class.getPackage(),
NarayanaLRAClient.class.getPackage(),
Current.class.getPackage(),
LRAService.class.getPackage(),
LRARecoveryModule.class.getPackage(),
LongRunningAction.class.getPackage()
};

private static final Package[] participantPackages = {
static final Package[] participantPackages = {
LRAListener.class.getPackage(),
LRA.class.getPackage(),
ServerLRAFilter.class.getPackage(),
Expand Down
Expand Up @@ -21,15 +21,7 @@
*/
package io.narayana.lra.coordinator;

import com.arjuna.ats.arjuna.recovery.RecoveryModule;
import io.narayana.lra.Current;
import io.narayana.lra.client.NarayanaLRAClient;
import io.narayana.lra.client.internal.proxy.nonjaxrs.LRAParticipantRegistry;
import io.narayana.lra.coordinator.api.Coordinator;
import io.narayana.lra.LRAData;
import io.narayana.lra.coordinator.domain.model.LongRunningAction;
import io.narayana.lra.coordinator.domain.service.LRAService;
import io.narayana.lra.coordinator.internal.LRARecoveryModule;
import io.narayana.lra.filter.ServerLRAFilter;
import io.narayana.lra.logging.LRALogger;
import org.eclipse.microprofile.lra.annotation.LRAStatus;
Expand Down Expand Up @@ -76,19 +68,6 @@ public class LRACoordinatorRecovery2TestCase extends TestBase {
private static final Long LONG_TIMEOUT = TimeoutValueAdjuster.adjustTimeout(600000L); // 10 minutes
private static final Long SHORT_TIMEOUT = 10000L; // 10 seconds

private static final Package[] coordinatorPackages = {
RecoveryModule.class.getPackage(),
Coordinator.class.getPackage(),
LRAData.class.getPackage(),
LRAStatus.class.getPackage(),
LRALogger.class.getPackage(),
NarayanaLRAClient.class.getPackage(),
Current.class.getPackage(),
LRAService.class.getPackage(),
LRARecoveryModule.class.getPackage(),
LongRunningAction.class.getPackage()
};

private static final Package[] participantPackages = {
LRAListener.class.getPackage(),
LRA.class.getPackage(),
Expand Down

0 comments on commit 7b355fe

Please sign in to comment.