/*
Copyright 2011-2014 Red Hat, Inc
This file is part of PressGang CCMS.
PressGang CCMS 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 3 of the License, or
(at your option) any later version.
PressGang CCMS 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 PressGang CCMS. If not, see <http://www.gnu.org/licenses/>.
*/
package org.jboss.pressgang.ccms.server.rest.v1.interceptor;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.ext.Provider;
import java.lang.reflect.Method;
import java.util.List;
import org.jboss.pressgang.ccms.rest.v1.constants.RESTv1Constants;
import org.jboss.pressgang.ccms.rest.v1.jaxrsinterfaces.RESTInterfaceV1;
import org.jboss.pressgang.ccms.server.rest.v1.RESTv1;
import org.jboss.pressgang.ccms.utils.common.VersionUtilities;
import org.jboss.resteasy.annotations.interception.ServerInterceptor;
import org.jboss.resteasy.core.Headers;
import org.jboss.resteasy.core.ResourceMethod;
import org.jboss.resteasy.core.ServerResponse;
import org.jboss.resteasy.spi.Failure;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.interception.AcceptedByMethod;
import org.jboss.resteasy.spi.interception.PreProcessInterceptor;
@Provider
@ServerInterceptor
public class RESTv1VersionInterceptor implements PreProcessInterceptor, AcceptedByMethod {
private static final int UPGRADE_STATUS_CODE = 426;
private static final String RESTv1_VERSION = VersionUtilities.getAPIVersion(RESTInterfaceV1.class);
private static final boolean IS_RESTv1_SNAPSHOT = isSnapshotVersion(RESTv1_VERSION);
private static final int MIN_CSP_MAJOR_VERSION = 1;
private static final int MIN_CSP_MINOR_VERSION = 9;
private static final int MIN_CSP_Z_VERSION = 2;
private static final String REST_VERSION_ERROR_MSG = "The REST Client Implementation is out of date, " +
"and no longer supported. Please update the REST Client library.";
private static final String CSP_VERSION_ERROR_MSG = "The csprocessor application is out of date, " +
"and no longer compatible. Please update the csprocessor application.";
@Override
public ServerResponse preProcess(HttpRequest request, ResourceMethod method) throws Failure, WebApplicationException {
if (request != null && request.getHttpHeaders() != null && request.getHttpHeaders().getRequestHeaders() != null) {
final HttpHeaders headers = request.getHttpHeaders();
if (headers.getRequestHeaders() != null) {
// REST API Version Check
final List<String> RESTVersions = headers.getRequestHeader(RESTv1Constants.X_VERSION_HEADER);
if (RESTVersions != null) {
for (final String version : RESTVersions) {
if (!isValidVersion(version)) {
return new ServerResponse(REST_VERSION_ERROR_MSG, UPGRADE_STATUS_CODE, new Headers<Object>());
}
}
}
// CSP Version Check
final List<String> CSPVersions = headers.getRequestHeader(RESTv1Constants.X_CSP_VERSION_HEADER);
if (CSPVersions != null) {
for (final String version : CSPVersions) {
if (!isValidCSPVersion(version)) {
return new ServerResponse(CSP_VERSION_ERROR_MSG, UPGRADE_STATUS_CODE, new Headers<Object>());
}
}
}
}
return null;
}
return null;
}
/**
* Checks to see if the REST Version is valid and compatible with the server.
*
* @param version The REST Version from the "X-Version" HTTP Header.
* @return True if the REST Version is compatible, otherwise false.
*/
protected boolean isValidVersion(final String version) {
if (isUnknownVersion(version)) return true;
final Integer majorVersion = getMajorVersion(version);
final Integer minorVersion = getMinorVersion(version);
final boolean isSnapshot = isSnapshotVersion(version);
final Integer MIN_MINOR_RESTv1_VERSION = 0;
switch (majorVersion) {
case 1:
if (minorVersion == null) {
return true;
} else {
// Check that the minor version is 0 or higher
if (minorVersion < MIN_MINOR_RESTv1_VERSION) {
return false;
} else if (minorVersion.equals(MIN_MINOR_RESTv1_VERSION)) {
if (!IS_RESTv1_SNAPSHOT && isSnapshot) {
return false;
} else {
return true;
}
} else {
return minorVersion > MIN_MINOR_RESTv1_VERSION;
}
}
default:
return false;
}
}
protected boolean isValidCSPVersion(final String version) {
if (isUnknownVersion(version)) return false;
final Integer majorVersion = getMajorVersion(version);
final Integer minorVersion = getMinorVersion(version);
final Integer zVersion = getZStreamVersion(version);
final boolean isSnapshotVersion = isSnapshotVersion(version);
// Check that the version is 1.9.2 or higher (allowing for snapshots).
return (majorVersion != null && majorVersion >= MIN_CSP_MAJOR_VERSION)
&& (minorVersion == null || (minorVersion >= MIN_CSP_MINOR_VERSION && !isSnapshotVersion) || minorVersion > MIN_CSP_MINOR_VERSION)
&& (zVersion == null || (zVersion >= MIN_CSP_Z_VERSION && !isSnapshotVersion) || zVersion > MIN_CSP_Z_VERSION);
}
protected static Integer getMajorVersion(final String version) {
// Remove any extra information, ie -SNAPSHOT
final String cleanedVersion = version.replaceAll("-.*", "");
String[] tmp = cleanedVersion.split("\\.");
return Integer.parseInt(tmp[0]);
}
protected static Integer getMinorVersion(final String version) {
// Remove any extra information, ie -SNAPSHOT
final String cleanedVersion = version.replaceAll("-.*", "");
String[] tmp = cleanedVersion.split("\\.");
return tmp.length >= 2 ? Integer.parseInt(tmp[1]) : null;
}
protected static Integer getZStreamVersion(final String version) {
// Remove any extra information, ie -SNAPSHOT
final String cleanedVersion = version.replaceAll("-.*", "");
String[] tmp = cleanedVersion.split("\\.");
return tmp.length >= 3 ? Integer.parseInt(tmp[2]) : null;
}
protected static boolean isSnapshotVersion(final String version) {
return version.contains("-SNAPSHOT");
}
protected static boolean isUnknownVersion(final String version) {
return version.toLowerCase().equals("unknown");
}
@Override
public boolean accept(Class declaring, Method method) {
// Only use this interceptor for v1 endpoints.
return RESTv1.class.equals(declaring);
}
}