All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.genexus.db.odata.ODataConnection Maven / Gradle / Ivy

Go to download

Core classes for the runtime used by Java and Android apps generated with GeneXus

The newest version!
package com.genexus.db.odata;

import com.genexus.ModelContext;
import com.genexus.db.driver.GXDBMSservice;
import com.genexus.db.service.ServiceConnection;
import com.genexus.db.service.ServiceError;
import com.genexus.diagnostics.core.LogManager;
import org.apache.http.*;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.HttpEntityWrapper;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.apache.olingo.client.api.ODataClient;
import org.apache.olingo.client.api.communication.ODataClientErrorException;
import org.apache.olingo.client.api.domain.ClientEnumValue;
import org.apache.olingo.client.api.http.HttpClientFactory;
import org.apache.olingo.client.core.ODataClientFactory;
import org.apache.olingo.client.core.http.BasicAuthHttpClientFactory;
import org.apache.olingo.client.core.http.DefaultHttpClientFactory;
import org.apache.olingo.client.core.http.ProxyWrappingHttpClientFactory;
import org.apache.olingo.commons.api.edm.*;
import org.apache.olingo.commons.api.format.ContentType;
import org.apache.olingo.commons.api.http.HttpMethod;

import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.Executor;
import java.util.function.Function;

public class ODataConnection extends ServiceConnection
{
	private static final String GXODATA_VERSION = "1.1";

    ODataClient client;
    private ModelInfo modelInfo;

	public ODataConnection(String connUrl, Properties initialConnProps) throws SQLException
	{
        super(connUrl, initialConnProps); /// Luego de la inicialización usar props de la clase base para obtener las propiedades
        modelInfo = ModelInfo.getModel(connUrl);
		if(modelInfo == null)
        {
        	try
			{
				initializeModel(connUrl);
			}catch(ODataClientErrorException e)
			{
				throw new SQLException(e);
			}
        }else
		{
			Edm model  = modelInfo.model;
			client = model == null ? ODataClientFactory.getClient() : ODataClientFactory.getEdmEnabledClient(url, model, null, modelInfo.defaultContentType);
			if(modelInfo.handlerFactory != null)
				client.getConfiguration().setHttpClientFactory(modelInfo.handlerFactory);
		}
        client.getConfiguration().setUseChuncked(modelInfo.useChunked);
    }

    private void initializeModel(String connUrl)
	{
		Edm model = null;
		String metadataLocation = String.format("Metadata%sServices%s", File.separatorChar, File.separatorChar);
		String metadataDocName = null;
		String proxyURI = null;
		String checkOptimisticConcurrency = null;
		String user = null, password = null;
		HashSet recordNotFoundServiceCodes = null;
		HashSet recordAlreadyExistsServiceCodes = null;
		boolean force_auth = false;
		boolean use_chunked = false;
		String sapLoginBO = null, initialB1SessionId = null;
		ContentType defaultContentType = ContentType.JSON_FULL_METADATA;
		for(Enumeration keys = props.keys(); keys.hasMoreElements(); )
		{
			String key = ((String)keys.nextElement());
			String value = props.getProperty(key, key);
			switch(key.toLowerCase())
			{
				case "user": user = value; break;
				case "password": password = value; break;
				case "metadatalocation": metadataLocation = String.format("%s%s", value, File.separatorChar); break;
				case GXDBMSservice.DATASOURCE_NAME: metadataDocName = value; break;
				case "proxy": proxyURI = value; break;
				case "checkoptimisticconcurrency": checkOptimisticConcurrency = value; break;
				case "force_auth": force_auth = getBoolean(value); break;
				case "use_chunked": use_chunked = getBoolean(value); break;
				case "saplogin": sapLoginBO = value; break;
				case "b1session": initialB1SessionId = value; break;
				case "recordnotfoundservicecodes":
				{
					if(recordNotFoundServiceCodes == null)
						recordNotFoundServiceCodes = new HashSet<>();
					recordNotFoundServiceCodes.addAll(Arrays.asList(value.split(",")));
					break;
				}
				case "recordalreadyexistsservicecodes":
				{
					if(recordAlreadyExistsServiceCodes == null)
						recordAlreadyExistsServiceCodes = new HashSet<>();
					recordAlreadyExistsServiceCodes.addAll(Arrays.asList(value.split(",")));
					break;
				}
				default: break;
			}
		}
		try
		{
			if(metadataDocName == null)
				metadataDocName = new java.net.URI(url).getHost();
			String metadataDocLoc = String.format("%s%s.xml", metadataLocation, metadataDocName);
			File metadataDocFile = new File(metadataDocLoc);
			if(!metadataDocFile.canRead())
			{
				URL alternativeLocationURL = ModelContext.getModelContext().packageClass.getResource("./../../");
				if(alternativeLocationURL != null)
				{
					metadataDocFile = new File(new URI(alternativeLocationURL.toURI() + metadataDocLoc.replace('\\', '/')));
					if (metadataDocFile.canRead())
					{
						try (FileInputStream fis = new FileInputStream(metadataDocFile))
						{
							model = ODataClientFactory.getClient().getReader().readMetadata(fis);
						}
					}
				}
			}
		}catch(URISyntaxException | IOException ex)
		{
			LogManager.getLogger(ODataConnection.class).warn(String.format("Could not load metadata file: %s%s", metadataLocation, metadataDocName), ex);
		}

		String loginBase = null;
		if(sapLoginBO != null)
		{ // SAP BusinessOne requiere autenticarse previamente para obtener la sesion
			// Ademas no esta aceptando ContentType.JSON_FULL_METADATA
			defaultContentType = ContentType.JSON;
			loginBase = url.trim();
			while(loginBase.endsWith("/"))
				loginBase = loginBase.substring(0, loginBase.length() - 1);
			if(initialB1SessionId != null)
				B1_sessionIds.put(loginBase, String.format("B1SESSION=%s", initialB1SessionId));
			if(recordNotFoundServiceCodes == null)
				recordNotFoundServiceCodes = new HashSet<>(Arrays.asList("-2028"));
			if(recordAlreadyExistsServiceCodes == null)
				recordAlreadyExistsServiceCodes = new HashSet<>(Arrays.asList("-2035"));
		}

		HttpClientFactory handlerFactory = null;
		if(user != null && password != null &&
			!(user.equals("") && password.equals("")))
		{
			if(!force_auth && sapLoginBO == null)
				handlerFactory = new BasicAuthHttpClientFactory(user, password);
			else
			{
				final String authHeaderValue = force_auth ? "Basic " + Base64.getEncoder().encodeToString(String.format("%s:%s", user, password).getBytes(StandardCharsets.UTF_8)) : null;
				final boolean fixResponse = sapLoginBO != null;
				final String m_user = user, m_password = password, m_sapLoginBO = sapLoginBO, m_loginBase = loginBase;
				handlerFactory = new BasicAuthHttpClientFactory(user, password)
				{
					@Override
					public DefaultHttpClient create(HttpMethod method, URI uri)
					{
						final DefaultHttpClient httpClient = super.create(method, uri);
						final SapB1CredentialsProvider sapB1CredentialsProvider = m_sapLoginBO != null ? new SapB1CredentialsProvider(httpClient.getCredentialsProvider(), m_user, m_password, m_sapLoginBO, m_loginBase) : null;
						if(sapB1CredentialsProvider != null)
							httpClient.setCredentialsProvider(sapB1CredentialsProvider);
						httpClient.addRequestInterceptor((request, context) ->
						{ // Caso en que el servicio va con autenticación Basic pero no esta enviando el Challenge al dar el response 401.Unauthorized
							// Si se pone la property force_auth=y se manda el header de autorización de antemano
							if(authHeaderValue != null)
								request.addHeader("Authorization", authHeaderValue);
							// Caso SAP BusinessOne se manda la B1SESSION
							if(sapB1CredentialsProvider != null)
								sapB1CredentialsProvider.addRequestHeaders(request);
						});
						if(fixResponse)
						{
							// Preprocesa el response para soportar que los datos del JSon vengan como '"value" : ' en vez de '"value":'
							httpClient.addResponseInterceptor(new HttpResponseInterceptor()
							{
								@Override
								public void process(HttpResponse response, HttpContext context) throws IOException
								{
									HttpEntity entity = response.getEntity();
									if(entity != null)
									{ // Solo interceptamos cuando efectivamente hay content
										StatusLine statusLine = response.getStatusLine();
										boolean inBadRequest = false;
										if(statusLine != null && statusLine.getStatusCode() == 400)
										{ // Sap B1 manda codigos de error numericos en vez de texto como dice el estándar
											response.setEntity(fixEntity(entity, (String content)->
											{
												int index = content.indexOf("\"code\"");
												if(index >= 0)
												{
													String head = content.substring(0, index+6);
													String tail = content.substring(index+7).trim();
													if(tail.startsWith(":"))
													{
														tail = tail.substring(1).trim();
														if(!tail.startsWith("'\""))
														{
															int index2 = tail.indexOf(',');
															int index3 = tail.indexOf('}');
															if(index2 == -1)
																index2 = index3;
															else if(index3 != -1 && index3 < index2)
																index2 = index3;
															if(index2 != -1)
															{
																return String.format("%s : \"%s\"%s", head, tail.substring(0, index2), tail.substring(index2));
															}
														}
													}
												}
												return content;
											}));
										}
										else
										{
											if (entity.isStreaming())
											{
												response.setEntity(new HttpEntityWrapper(entity)
												{
													@Override
													public InputStream getContent() throws IOException
													{
														return new FilterInputStream(super.getContent())
														{
															final int[] PREFIX_LOWER = new int[]{'\"', 'v', 'a', 'l', 'u', 'e', '\"'};
															final int[] PREFIX_UPPER = new int[]{'\"', 'V', 'A', 'L', 'U', 'E', '\"'};
															private int index = 0;

															public int read() throws IOException
															{
																int b = super.read();
																if (b < 0)
																	return b;
																if (index < PREFIX_LOWER.length)
																{
																	if (b == PREFIX_LOWER[index] || b == PREFIX_UPPER[index])
																		index++;
																	else index = 0;
																} else
																{
																	while (b == ' ')
																		b = super.read();
																	index = 0;
																}
																return b;
															}
														};
													}
												});
											} else
											{
												response.setEntity(fixEntity(entity, (String content)->content.replace("\"value\" : ", "\"value\":")));
											}
										}
									}
								}
							});
						}
						return httpClient;
					}
				};
			}
		}
		if(proxyURI != null)
		{
			handlerFactory = (handlerFactory != null ? new ProxyWrappingHttpClientFactory(URI.create(proxyURI), (DefaultHttpClientFactory) handlerFactory) : new ProxyWrappingHttpClientFactory(URI.create(proxyURI)));
		}

		client = ODataClientFactory.getClient();
		client.getConfiguration().setDefaultPubFormat(defaultContentType);
		if(handlerFactory != null)
			client.getConfiguration().setHttpClientFactory(handlerFactory);
		if(model == null)
			model = client.getRetrieveRequestFactory().getMetadataRequest(url).execute().getBody();
		modelInfo = new ModelInfo(url, model, checkOptimisticConcurrency);
		modelInfo.handlerFactory = handlerFactory;
		modelInfo.useChunked = use_chunked;
		modelInfo.defaultContentType = defaultContentType;
		modelInfo.recordNotFoundServiceCodes = recordNotFoundServiceCodes;
		modelInfo.recordAlreadyExistsServiceCodes = recordAlreadyExistsServiceCodes;
		ModelInfo.addModel(connUrl, modelInfo);
	}

	ServiceError getServiceError(String errorCode)
	{
		if(errorCode != null)
		{
			if (modelInfo.recordNotFoundServiceCodes != null && modelInfo.recordNotFoundServiceCodes.contains(errorCode))
				return ServiceError.OBJECT_NOT_FOUND;
			if (modelInfo.recordAlreadyExistsServiceCodes != null && modelInfo.recordAlreadyExistsServiceCodes.contains(errorCode))
				return ServiceError.DUPLICATE_KEY;
		}
		return ServiceError.INVALID_QUERY;
	}

	private static HttpEntity fixEntity(HttpEntity entity, Function fixer) throws IOException
	{
		Header contentTypeHeader = entity.getContentType();
		org.apache.http.entity.ContentType contentType = contentTypeHeader != null ? org.apache.http.entity.ContentType.parse(contentTypeHeader.getValue()) : org.apache.http.entity.ContentType.DEFAULT_TEXT;
		String content = EntityUtils.toString(entity, contentType.getCharset());
		return new StringEntity(fixer.apply(content), contentType);
	}
        
    private boolean getBoolean(String value)
    {
        return value.equalsIgnoreCase("y") || value.equalsIgnoreCase("true") || value.equalsIgnoreCase("yes");
    }

    boolean needsCheckOptimisticConcurrency(URI updURI)
    {
        return modelInfo.needsCheckOptimisticConcurrency(updURI);
    }    
    
    public int getEnumValue(ClientEnumValue enumValue)
    {
        String typeName = enumValue.getTypeName();
        return Integer.parseInt(modelInfo.getModel().getEnumType(new FullQualifiedName(typeName)).getMember(enumValue.getValue()).getValue());
    }
    
    public String toEnumValue(EdmEnumType type, int value)
    {
        String sValue = Integer.toString(value);
        for(String memberName:type.getMemberNames())
        {
            EdmMember member = type.getMember(memberName);
            if(member.getValue().equals(sValue))
                return member.getName();
        }
        throw new RuntimeException(String.format("Cannot parse enum value %s - %d", type.toString(), value));
    }

    private String entity(String name)
    {
        return modelInfo.entity(name);
    }

    public String entity(EdmEntityType [] fromEntity, String name)
    {
        if(fromEntity == null || fromEntity[0] == null)
            return entity((EdmEntityType)null, name);
        String entityName = entity(fromEntity[0], name);
        if(entityName == null)
            return entity(name);
        EdmNavigationProperty navProp = fromEntity[0].getNavigationProperty(entityName);
        if(navProp != null)
            fromEntity[0] = navProp.getType();
        return entityName;        
    }
    
    public String entity(EdmEntityType fromEntity, String name)
    {
        return modelInfo.entity(fromEntity, name);
    }
    
    Edm getModel()
    {
        return modelInfo.getModel();
    }
    
//----------------------------------------------------------------------------------------------------

    @Override
    public void close()
	{
        client = null;
    }

    @Override
    public boolean isClosed()
	{
        return client != null;
    }

//----------------------------------------------------------------------------------------------------
    @Override
    public String getDatabaseProductName()
	{
        return "OData";
    }

    @Override
    public String getDatabaseProductVersion()
	{
        return client.getServiceVersion().toString();
    }

    @Override
    public String getDriverName()
	{
        return client.getClass().getName();
    }

    @Override
    public String getDriverVersion()
	{
        return String.format("%s/%s", client.getServiceVersion().toString(), GXODATA_VERSION);
    }

// JDK8:
    @Override
    public void setSchema(String schema)
	{
        throw new UnsupportedOperationException("Not supported yet."); 
    }

    @Override
    public String getSchema()
	{
        throw new UnsupportedOperationException("Not supported yet."); 
    }

    @Override
    public void abort(Executor executor)
	{
        throw new UnsupportedOperationException("Not supported yet."); 
    }

    @Override
    public void setNetworkTimeout(Executor executor, int milliseconds)
	{
        throw new UnsupportedOperationException("Not supported yet."); 
    }

    @Override
    public int getNetworkTimeout()
	{
        throw new UnsupportedOperationException("Not supported yet."); 
    }

    @Override
    public ResultSet getPseudoColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern)
	{
        throw new UnsupportedOperationException("Not supported yet."); 
    }

    @Override
    public boolean generatedKeyAlwaysReturned()
	{
        throw new UnsupportedOperationException("Not supported yet."); 
    }

	private static final HashMap B1_sessionIds = new HashMap<>();
	private class SapB1CredentialsProvider implements CredentialsProvider
	{
		private final CredentialsProvider parent;
		private final String user;
		private final String password;
		private final String sapLoginBO;
		private final String loginBase;
		private boolean doLogin = true;
		SapB1CredentialsProvider(CredentialsProvider parent, String user, String password, String sapLoginBO, String loginBase)
		{
			this.parent = parent;
			this.user = user;
			this.password = password;
			this.sapLoginBO = sapLoginBO;
			this.loginBase = loginBase;
			if(B1_sessionIds.get(loginBase) == null)
				loginBO();
		}

		@Override
		public void setCredentials(AuthScope authScope, Credentials credentials)
		{
			parent.setCredentials(authScope, credentials);
		}

		@Override
		public Credentials getCredentials(AuthScope authScope)
		{
			if(doLogin)
				loginBO();
			return parent.getCredentials(authScope);
		}

		@Override
		public void clear()
		{
			doLogin = true;
			B1_sessionIds.remove(loginBase);
			parent.clear();
		}

		private void loginBO()
		{
			try (DefaultHttpClient webClient = new DefaultHttpClient();)
			{
				doLogin = false;
				B1_sessionIds.remove(loginBase);
				URI loginURI = new URI(String.format("%s/Login", loginBase));
				HttpPost login = new HttpPost(loginURI);
				StringEntity sloginInfo = new StringEntity(String.format("{\"UserName\":\"%s\", \"CompanyDB\":\"%s\"}", user, sapLoginBO));
				login.setEntity(sloginInfo);
				HttpResponse loginResponse = webClient.execute(login);
				Header cookieHdr = loginResponse.getFirstHeader("Set-Cookie");
				if(cookieHdr != null)
				{
					String cookie = cookieHdr.getValue();
					int cookieStart = cookie.indexOf("B1SESSION=");
					if (cookieStart >= 0)
					{
						int cookieEnd = cookie.indexOf(';', cookieStart);
						String b1SessionCookie = cookie.substring(cookieStart, cookieEnd - cookieStart);
						B1_sessionIds.put(loginBase, b1SessionCookie);
						doLogin = true;
					}
				}
			}catch (URISyntaxException | IOException ex)
			{
				LogManager.getLogger(ODataConnection.class).warn(String.format("Could not login to %s", loginBase), ex);
			}
		}

		void addRequestHeaders(HttpRequest request)
		{
			String b1SessionCookie = B1_sessionIds.get(loginBase);
			if(b1SessionCookie != null)
				request.addHeader("Cookie", b1SessionCookie);
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy