blob: f7c21fa73749378b6a33ad97ffbbe2c126fcff73 [file] [log] [blame]
/*
* 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
*
* https://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.directory.fortress.core.rest;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.Response;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Marshaller;
import jakarta.xml.bind.Unmarshaller;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.directory.fortress.core.GlobalErrIds;
import org.apache.directory.fortress.core.RestException;
import org.apache.directory.fortress.core.model.FortRequest;
import org.apache.directory.fortress.core.model.FortResponse;
import org.apache.directory.fortress.core.model.ObjectFactory;
import org.apache.directory.fortress.core.model.Props;
import org.apache.directory.fortress.core.util.Config;
import org.apache.directory.fortress.core.util.EncryptUtil;
import org.apache.http.HttpEntity;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.HttpClientBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.directory.fortress.core.GlobalIds.*;
/**
* This utility class provides methods that wrap Apache's HTTP Client APIs. This class is thread safe.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
public final class RestUtils
{
private static final String CLS_NM = RestUtils.class.getName();
private static final Logger LOG = LoggerFactory.getLogger( CLS_NM );
private static final int HTTP_OK = 200;
private static final int HTTP_400_VALIDATION_EXCEPTION = 400;
private static final int HTTP_401_UNAUTHORIZED = 401;
private static final int HTTP_403_FORBIDDEN = 403;
private static final int HTTP_404_NOT_FOUND = 404;
private static final int HTTP_500_INTERNAL_SERVER_ERROR = 500;
private static final String VALID_RESPONSE = "FortResponse";
private static CachedJaxbContext cachedJaxbContext = new CachedJaxbContext();
// static member contains this
private static volatile RestUtils sINSTANCE = null;
/**
* Used to manage trust store properties. If enabled, create SSL connection.
*
*/
private String trustStore;
private String trustStorePw;
// These members contain the http coordinates to a running fortress-rest instance:
private String httpUid, httpPw, httpHost, httpPort, httpProtocol, fortressRestVersion, serviceName, uri;
/**
* create a new request and set its tenant id.
* @param szContextId contains the tenant id
* @return a brand new FortRequest
*/
static FortRequest getRequest( String szContextId )
{
FortRequest request = new FortRequest();
request.setContextId(szContextId);
return request;
}
public static RestUtils getInstance()
{
if(sINSTANCE == null)
{
synchronized (RestUtils.class)
{
if(sINSTANCE == null)
{
sINSTANCE = new RestUtils();
}
}
}
return sINSTANCE;
}
private void init()
{
httpUid = Config.getInstance().getProperty( HTTP_UID_PROP );
httpPw = ( ( EncryptUtil.isEnabled() ) ? EncryptUtil.getInstance().decrypt( Config
.getInstance().getProperty( HTTP_PW_PROP ) ) : Config.getInstance().getProperty( HTTP_PW_PROP ) );
httpHost = Config.getInstance().getProperty( "http.host" );
httpPort = Config.getInstance().getProperty( "http.port" );
httpProtocol = Config.getInstance().getProperty( "http.protocol", "http" );
trustStore = Config.getInstance().getProperty( "trust.store" );
trustStorePw = Config.getInstance().getProperty( "trust.store.password" );
fortressRestVersion = System.getProperty( "version" );
serviceName = "fortress-rest-" + fortressRestVersion;
uri = httpProtocol + "://" + httpHost + ":" + httpPort + "/" + serviceName + "/";
LOG.info("HTTP Connect Properties: host:{}, port:{}, protocol:{}, version:{}, service:{}, uri:{}", httpHost, httpPort, httpProtocol, fortressRestVersion, serviceName, uri);
if( StringUtils.isNotEmpty(trustStore ) && StringUtils.isNotEmpty(trustStorePw ) )
{
LOG.info( "javax.net.ssl.trustStore: {}", trustStore );
System.setProperty( "javax.net.ssl.trustStore", trustStore );
System.setProperty( "javax.net.ssl.trustStorePassword", trustStorePw );
}
//System.setProperty( "http.maxConnections", "50" );
}
private RestUtils(){
init();
}
/**
* Marshall the request into an XML String.
*
* @param request
* @return String containing xml request
* @throws RestException
*/
public static String marshal( FortRequest request ) throws RestException
{
String szRetValue;
try
{
// Create a JAXB context passing in the class of the object we want to marshal/unmarshal
final JAXBContext context = cachedJaxbContext.getJaxbContext( FortRequest.class );
// =============================================================================================================
// Marshalling OBJECT to XML
// =============================================================================================================
// Create the marshaller, that will transform the object into XML
final Marshaller marshaller = context.createMarshaller();
// Create a stringWriter to hold the XML
final StringWriter stringWriter = new StringWriter();
// Marshal the javaObject and write the XML to the stringWriter
marshaller.marshal( request, stringWriter );
szRetValue = stringWriter.toString();
}
catch ( JAXBException je )
{
String error = "marshal caught JAXBException=" + je;
throw new RestException( GlobalErrIds.REST_MARSHALL_ERR, error, je );
}
return szRetValue;
}
/**
* Unmarshall the XML response into its associated Java objects.
*
* @param szResponse
* @return FortResponse
* @throws RestException
*/
public static FortResponse unmarshall( String szResponse ) throws RestException
{
FortResponse response;
try
{
// Create a JAXB context passing in the class of the object we want to marshal/unmarshal
final JAXBContext context = cachedJaxbContext.getJaxbContext( FortResponse.class );
// Create the unmarshaller, that will transform the XML back into an object
final Unmarshaller unmarshaller = context.createUnmarshaller();
response = ( FortResponse ) unmarshaller.unmarshal( new StringReader( szResponse ) );
}
catch ( JAXBException je )
{
String error = "unmarshall caught JAXBException=" + je;
throw new RestException( GlobalErrIds.REST_UNMARSHALL_ERR, error, je );
}
return response;
}
/**
* Perform HTTP Get REST request.
*
* @param userId
* @param password
* @param id
* @param id2
* @param id3
* @param function
* @return String containing response
* @throws RestException
*/
public String get( String userId, String password, String id, String id2, String id3, String function )
throws RestException
{
String url = uri + function + "/" + id;
if ( id2 != null )
{
url += "/" + id2;
}
if ( id3 != null )
{
url += "/" + id3;
}
LOG.debug( "get function1:{}, id1:{}, id2:{}, id3:{}, url:{}", function, id, id2, id3, url );
String szResponse;
HttpGet get = null;
try
{
get = new HttpGet(url);
setMethodHeaders( get );
szResponse = handleHttpMethod( get ,HttpClientBuilder.create().useSystemProperties()
.setDefaultCredentialsProvider(getCredentialProvider(userId, password)).build() );
}
catch ( WebApplicationException we )
{
String error = generateErrorMessage( uri,function, "caught WebApplicationException=" + we.getMessage() );
LOG.error( error, we);
throw new RestException( GlobalErrIds.REST_WEB_ERR, error, we );
}
catch ( java.lang.NoSuchMethodError e )
{
String error = generateErrorMessage( uri, function, "caught NoSuchMethodError = "+ e.getMessage() );
LOG.error( error, e );
throw new RestException( GlobalErrIds.REST_UNKNOWN_ERR, error);
}
finally
{
// Release current connection to the connection pool.
if ( get != null )
get.releaseConnection();
}
return szResponse;
}
/**
* Perform HTTP Get REST request.
*
* @param id
* @param id2
* @param id3
* @param function
* @return String containing response
* @throws RestException
*/
public String get( String id, String id2, String id3, String function ) throws RestException
{
return get( null, null, id, id2, id3, function );
}
/**
* Perform an HTTP Post REST operation.
*
* @param userId
* @param password
* @param szInput
* @param function
* @return String containing response
* @throws RestException
*/
public String post( String userId, String password, String szInput, String function ) throws RestException
{
LOG.debug( "post uri=[{}], function=[{}], request=[{}]", uri, function, szInput );
String szResponse = null;
HttpPost post = new HttpPost( uri + function);
post.addHeader( "Accept", "text/xml" );
setMethodHeaders( post );
try
{
HttpEntity entity = new StringEntity( szInput, ContentType.TEXT_XML );
post.setEntity( entity );
org.apache.http.client.HttpClient httpclient = HttpClientBuilder.create().useSystemProperties()
.setDefaultCredentialsProvider(getCredentialProvider(userId, password)).build();
HttpResponse response = httpclient.execute( post );
String error;
switch ( response.getStatusLine().getStatusCode() )
{
case HTTP_OK :
szResponse = IOUtils.toString( response.getEntity().getContent(), "UTF-8" );
if( StringUtils.isNotEmpty( szResponse ) && szResponse.contains(VALID_RESPONSE) )
{
LOG.debug( "post uri=[{}], function=[{}], response=[{}]", uri, function, szResponse );
}
else
{
error = generateErrorMessage( uri, function, "invalid response" );
LOG.error( error );
throw new RestException( GlobalErrIds.REST_NOT_FOUND_ERR, error );
}
break;
case HTTP_401_UNAUTHORIZED :
error = generateErrorMessage( uri, function, "401 function unauthorized on host" );
LOG.error( error );
throw new RestException( GlobalErrIds.REST_UNAUTHORIZED_ERR, error );
case HTTP_403_FORBIDDEN :
error = generateErrorMessage( uri, function, "403 function forbidden on host" );
LOG.error( error );
throw new RestException( GlobalErrIds.REST_FORBIDDEN_ERR, error );
case HTTP_404_NOT_FOUND:
szResponse = IOUtils.toString( response.getEntity().getContent(), "UTF-8" );
// Crack the response and see if it can be parsed as a valid Fortress Response object or generic HTTP:
if( StringUtils.isNotEmpty( szResponse ) && szResponse.contains(VALID_RESPONSE) )
{
LOG.debug( "HTTP: 404: post uri=[{}], function=[{}], response=[{}]", uri, function, szResponse );
}
else
{
error = generateErrorMessage( uri, function, "HTTP Error:" + response.getStatusLine().getStatusCode());
LOG.error( error );
throw new RestException( GlobalErrIds.REST_NOT_FOUND_ERR, error );
}
break;
case HTTP_500_INTERNAL_SERVER_ERROR:
szResponse = IOUtils.toString( response.getEntity().getContent(), "UTF-8" );
// Crack the response and see if it can be parsed as a valid Fortress Response object or generic HTTP:
if( StringUtils.isNotEmpty( szResponse ) && szResponse.contains(VALID_RESPONSE) )
{
LOG.debug( "HTTP 500: post uri=[{}], function=[{}], response=[{}]", uri, function, szResponse );
}
else
{
error = generateErrorMessage( uri, function, "HTTP 500 Internal Error:" + response.getStatusLine().getStatusCode());
LOG.error( error );
throw new RestException( GlobalErrIds.REST_INTERNAL_ERR, error );
}
break;
case HTTP_400_VALIDATION_EXCEPTION:
szResponse = IOUtils.toString( response.getEntity().getContent(), "UTF-8" );
// Crack the response and see if it can be parsed as a valid Fortress Response object or generic HTTP:
if( StringUtils.isNotEmpty( szResponse ) && szResponse.contains(VALID_RESPONSE) )
{
LOG.debug( "HTTP 400: post uri=[{}], function=[{}], response=[{}]", uri, function, szResponse );
}
else
{
error = generateErrorMessage( uri, function, "HTTP 400 Validation Error:" + response.getStatusLine().getStatusCode());
LOG.error( error );
throw new RestException( GlobalErrIds.REST_VALIDATION_ERR, error );
}
break;
default :
error = generateErrorMessage( uri, function, "error received from host: " + response.getStatusLine().getStatusCode() );
LOG.error( error );
throw new RestException( GlobalErrIds.REST_UNKNOWN_ERR, error );
}
}
catch ( IOException ioe )
{
String error = generateErrorMessage( uri, function, "caught IOException=" + ioe.getMessage() );
LOG.error( error, ioe );
throw new RestException( GlobalErrIds.REST_IO_ERR, error, ioe );
}
catch ( WebApplicationException we )
{
String error = generateErrorMessage( uri,function, "caught WebApplicationException=" + we.getMessage() );
LOG.error( error, we);
throw new RestException( GlobalErrIds.REST_WEB_ERR, error, we );
}
catch ( java.lang.NoSuchMethodError e )
{
String error = generateErrorMessage( uri, function, "caught Exception = "+ e.getMessage() );
LOG.error( error, e );
throw new RestException( GlobalErrIds.REST_UNKNOWN_ERR, error);
}
finally
{
// Release current connection to the connection pool.
post.releaseConnection();
}
return szResponse;
}
private String generateErrorMessage( String uri, String function, String messageToShow ) {
return new StringBuilder().append( "post uri=[" ).append( uri) .append( "], function=[" )
.append( function ).append( "], " ).append( messageToShow ).toString();
}
/**
* Perform an HTTP Post REST operation.
*
* @param szInput
* @param function
* @return String containing response
* @throws RestException
*/
public String post( String szInput, String function ) throws RestException
{
return post(null,null,szInput, function);
}
private CredentialsProvider getCredentialProvider(String uid, String password) {
BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials( new AuthScope( httpHost,Integer.valueOf( httpPort )),
new UsernamePasswordCredentials(uid==null? httpUid :uid,password==null? httpPw :password) );
return credentialsProvider;
}
/**
* Set these params into their associated HTTP header vars.
*
* @param httpRequest
*/
private static void setMethodHeaders( HttpRequest httpRequest )
{
if ( httpRequest instanceof HttpPost || httpRequest instanceof HttpPut)
{
httpRequest.addHeader( "Content-Type", "application/xml" );
httpRequest.addHeader( "Accept", "application/xml" );
}
}
/**
* Convert from non-Base64 to Base64 encoded.
*
* @param value
* @return String contains encoded data
*/
private static String base64Encode( String value )
{
return new String ( Base64.encodeBase64( value.getBytes() ) );
}
/* private static String base64Encode( String value )
{
return Base64Utility.encode( value.getBytes() );
}*/
/**
* Process the HTTP method request.
*
* @param httpGetRequest
* @return String containing response
* @throws Exception
*/
private static String handleHttpMethod( HttpRequestBase httpGetRequest ,org.apache.http.client.HttpClient client ) throws RestException
{
String szResponse = null;
try
{
HttpResponse response = client.execute( httpGetRequest );
LOG.debug( "handleHttpMethod Response status : {}", response.getStatusLine().getStatusCode() );
Response.Status status = Response.Status.fromStatusCode( response.getStatusLine().getStatusCode() );
if ( status == Response.Status.OK )
{
szResponse = IOUtils.toString( response.getEntity().getContent() );
LOG.debug( szResponse );
}
else if ( status == Response.Status.FORBIDDEN )
{
LOG.debug( "handleHttpMethod Authorization failure" );
}
else if ( status == Response.Status.UNAUTHORIZED )
{
LOG.debug( "handleHttpMethod Authentication failure" );
}
else
{
LOG.debug( "handleHttpMethod Unknown error" );
}
}
catch ( IOException ioe )
{
String error = "handleHttpMethod caught IOException=" + ioe;
LOG.error( error );
throw new RestException( GlobalErrIds.REST_IO_ERR, error, ioe );
}
finally
{
// Release current connection to the connection pool.
httpGetRequest.releaseConnection();
}
return szResponse;
}
/**
* @param inProps
* @return Properties
*/
public static Properties getProperties( Props inProps )
{
Properties properties = null;
List<Props.Entry> props = inProps.getEntry();
if ( props.size() > 0 )
{
properties = new Properties();
//int size = props.size();
for ( Props.Entry entry : props )
{
String key = entry.getKey();
String val = entry.getValue();
properties.setProperty( key, val );
}
}
return properties;
}
/**
*
* @param properties
* @return Prop contains name value pairs.
*/
public static Props getProps( Properties properties )
{
Props props = null;
if ( properties != null )
{
props = new ObjectFactory().createProps();
for ( Enumeration<?> e = properties.propertyNames(); e.hasMoreElements(); )
{
String key = ( String ) e.nextElement();
String val = properties.getProperty( key );
Props.Entry entry = new Props.Entry();
entry.setKey( key );
entry.setValue( val );
props.getEntry().add( entry );
}
}
return props;
}
}