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

com.marklogic.xcc.impl.ContentSourceImpl Maven / Gradle / Ivy

/*
 * Copyright (c) 2024 MarkLogic Corporation
 *
 * Licensed 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
 *
 *    http://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 com.marklogic.xcc.impl;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;

import com.marklogic.http.HttpChannel;
import com.marklogic.xcc.ContentSource;
import com.marklogic.xcc.Session;
import com.marklogic.xcc.UserCredentials;
import com.marklogic.xcc.spi.ConnectionProvider;

@SuppressWarnings("deprecation")
public class ContentSourceImpl implements ContentSource {
	
    public static enum AuthType {
        NONE, BASIC, DIGEST, NEGOTIATE, MLCLOUD, OAUTH
    };
    
    private static final String DEFAULT_LOGGER_NAME = "com.marklogic.xcc";
    private static final String XCC_LOGGING_CONFIG_FILE = "xcc.logging.properties";
    private static final String XCC_CONFIG_FILE = "xcc.properties";
    private static final String SYSTEM_LOGGING_CONFIG_CLASS = "java.util.logging.config.class";
    private static final String SYSTEM_LOGGING_CONFIG_FILE = "java.util.logging.config.file";

    private final ConnectionProvider connectionProvider;
    private final String contentBase;
    private final String basePath;
    private boolean authenticationPreemptive = false; 
    private boolean challengeIgnored = false; // for regression testing only
    /**
     * logger is initiated before initializeConfig()
     */
    private Logger logger = newDefaultLogger();

    private AuthType authType = AuthType.NONE;
    private String challenge;

    private Credentials credentials;
    private static MLCloudAuthManager mlCloudAuthManager;

    private static Logger newDefaultLogger() {
        LogManager logManager = LogManager.getLogManager();
        Logger logger = logManager.getLogger(DEFAULT_LOGGER_NAME);

        if (logger != null) {
            return logger;
        }

        if ((System.getProperty(SYSTEM_LOGGING_CONFIG_CLASS) != null)
                || (System.getProperty(SYSTEM_LOGGING_CONFIG_FILE) != null)) {
            // If custom config file or class, don't override anything
            return Logger.getLogger(DEFAULT_LOGGER_NAME);
        }

        return customizedLogger(logManager);
    }

    private void initializeConfig() {
        URL url = getClass().getClassLoader().getResource(XCC_CONFIG_FILE);
        Properties props = System.getProperties();
        if (url != null) {
            try (FileInputStream is = new FileInputStream(url.getPath())) {
                props.load(is);
            } catch (IOException e) {
                logger.log(Level.WARNING,
                    "property file not found:" + url.getPath());
            }
        }
    }

    public ContentSourceImpl(ConnectionProvider connectionProvider,
                             char[] OAuthToken, String contentBase,
                             String basePath) {
        this(connectionProvider, new Credentials(OAuthToken), contentBase,
            basePath);
    }

    public ContentSourceImpl(ConnectionProvider connectionProvider, String user,
                             char[] password, String contentBase) {
        this(connectionProvider, new Credentials(user, password), contentBase,
            null);
    }

    public ContentSourceImpl(ConnectionProvider connectionProvider, String user,
                             char[] password, String contentBase,
                             String basePath) {
        this(connectionProvider, new Credentials(user, password), contentBase,
            basePath);
    }

    public ContentSourceImpl(ConnectionProvider connectionProvider,
                             char[] apiKey, String contentBase, String basePath,
                             String tokenEndpoint, String grantType,
                             int tokenDuration) {
        this(connectionProvider,
            new Credentials(apiKey, tokenEndpoint, grantType, tokenDuration),
            contentBase, basePath);
    }

    public ContentSourceImpl(ConnectionProvider connectionProvider,
                             Credentials credentials, String contentBase,
                             String basePath) {
        this.connectionProvider = connectionProvider;
        this.credentials = credentials;
        String cbName = contentBase;
        this.basePath = basePath;

        if (cbName != null) {
            cbName = cbName.trim();

            if (cbName.length() == 0) {
                cbName = null;
            }
        }

        this.contentBase = cbName;
        initializeConfig();
        //For MLCloudAuth and OAuth, different from the flow that xcc relies on
        // ML server to set auth challenge by sending the first request to ML
        // server and getting a 401 unauthorized, xcc needs the user to pass the
        // tokens to figure out the auth type.
        initTokenBasedAuth();
    }

    // Constructor for creating dedicated content source object for ML Cloud
    private ContentSourceImpl(ConnectionProvider connectionProvider,
                              Credentials credentials, String basePath) {
        this.connectionProvider = connectionProvider;
        this.credentials = credentials;
        this.basePath = basePath;
        this.contentBase = null;
    }

    private void initTokenBasedAuth() {
        if (credentials.getMLCloudAuthConfig() != null) {
            setMLCloudAuthType();
            HttpChannel.setUseHTTP(true);
            MLCloudAuthManager.createMLCloudAuthContext(new ContentSourceImpl(
                    connectionProvider, credentials, basePath));
        } else if (credentials.getOAuthToken() != null) {
            setOAuthAuthType();
        }
    }

    public ConnectionProvider getConnectionProvider() {
		return connectionProvider;
	}

    public UserCredentials getUserCredentials() { return credentials; }

    public Session newSession() {
        return (new SessionImpl(
            this, connectionProvider, credentials, contentBase, basePath));
    }

    public Session newSession(String userName, char[] password) {
        return (new SessionImpl(this, connectionProvider, 
                new Credentials(userName, password), contentBase, basePath));
    }

    public Session newSession(String user, char[] password, String contentBaseArg) {
        String contentBase = (contentBaseArg == null) ? this.contentBase : contentBaseArg;
        return (new SessionImpl(this, connectionProvider, 
                new Credentials(user, password), contentBase, basePath));
    }

    public Session newSession(String databaseId) {
        return (new SessionImpl(
            this, connectionProvider, credentials, databaseId, basePath));
    }

    public Logger getDefaultLogger() {
        return logger;
    }

    public void setDefaultLogger(Logger logger) {
        this.logger = logger;
    }

    public boolean isAuthenticationPreemptive() {
    	return this.authenticationPreemptive;
    }
    
    public void setAuthenticationPreemptive(boolean value) {
    	this.authenticationPreemptive = value;
    }

    public void setAuthChallenge(String challenge) {
    	synchronized(this) {
    		this.authType = AuthType.valueOf(challenge.split(" ")[0].toUpperCase());
    		this.challenge = challenge;
    	}
    }

    public synchronized void setMLCloudAuthType() {
        this.authType = AuthType.MLCLOUD;
    }

    public synchronized void setOAuthAuthType() {
        this.authType = AuthType.OAUTH;
    }

    /**
     * For regression testing only; returns whether session to ignore authentication challenges and fail immediately.
     */
    public boolean isChallengeIgnored() {
        return challengeIgnored;
    }

    /**
     * For regression testing only; tells session to ignore authentication challenges and fail immediately.
     */
    public void setChallengeIgnored(boolean challengeIgnored) {
        this.challengeIgnored = challengeIgnored;
    }

    public String getAuthString(String method, String uri, UserCredentials credentials) {
        AuthType authType;
        String challenge;
        synchronized(this) {
            authType = this.authType;
            challenge = this.challenge;
        }
        switch (authType) {
        case BASIC:
            return credentials.toHttpBasicAuth();
        case DIGEST:
            return credentials.toHttpDigestAuth(method, uri, challenge);
        case NEGOTIATE:
            return credentials.toHttpNegotiateAuth(connectionProvider.getHostName(), challenge);
        case MLCLOUD:
            return credentials.toMLCloudAuth();
        case OAUTH:
            return credentials.toOAuth();
        default:
            return isAuthenticationPreemptive() ? credentials.toHttpBasicAuth() : null;
        }
    }

    @Override
    public String toString() {
        return "user=" + ((credentials.getUserName() == null) ?
            "{none}" : credentials.getUserName()) +
            ", cb=" + ((contentBase == null) ? "{none}" : contentBase) +
            " [provider: " + connectionProvider.toString() + "]";
    }

    // -------------------------------------------------------------

    private static Logger customizedLogger(LogManager logManager) {
        Properties props = loadLoggingPropertiesFromResource();
        Logger logger = Logger.getLogger(DEFAULT_LOGGER_NAME);
        List handlers = getLoggerHandlers(logger, logManager, props);

        for (Iterator it = handlers.iterator(); it.hasNext();) {
            logger.addHandler(it.next());
        }

        boolean useParentHandlers = getUseParentHandlersFlag(logger, logManager, props);

        logger.setUseParentHandlers(useParentHandlers);

        logManager.addLogger(logger);

        return logger;
    }

    private static Properties loadLoggingPropertiesFromResource() {
        Properties props = new Properties();
        URL url = ClassLoader.getSystemResource(XCC_LOGGING_CONFIG_FILE);
        if (url != null) {
            try (FileInputStream is = new FileInputStream(url.getPath())) {
                props.load(is);
                return props;
            } catch (IOException e) {
                //property file not found
                Logger logger = Logger.getLogger(DEFAULT_LOGGER_NAME);
                if(logger!=null) {
                    logger.warning("property file not found: " + url);
                }
            }
        }
        // Load properties internally from com.marklogic.xcc package in
        // xcc.jar
        try (InputStream is = 
             ContentSource.class.getResourceAsStream(XCC_LOGGING_CONFIG_FILE)) {
            if (is != null) {
                props.load(is);
            }
        } catch (IOException e) {
            // property file not found
            Logger logger = Logger.getLogger(DEFAULT_LOGGER_NAME);
            if (logger!=null) {
                logger.warning("Error loading default logging file: " + 
                    e.getMessage());
            }
        }
        return props;
    }

    private static List getLoggerHandlers(Logger logger, LogManager logManager, Properties props) {
        String propName = logger.getName() + ".handlers";
        String handlerPropVal = getPropertyValue(propName, logManager, props);

        if (handlerPropVal == null) {
            return new ArrayList(0);
        }

        String[] handlerClassNames = handlerPropVal.split("\\\\s*,?\\\\s*");
        List handlers = new ArrayList(handlerClassNames.length);
        Level level = getLoggerLevel(logger, logManager, props);

        if (level != null)
            logger.setLevel(level);

        for (int i = 0; i < handlerClassNames.length; i++) {
            try {
                Class handlerClass = Class.forName(handlerClassNames[i]).asSubclass(Handler.class);
                Handler handler = handlerClass.newInstance();
                Formatter formatter = getFormatter(handler, logManager, props);

                handlers.add(handler);
                if (formatter != null)
                    handler.setFormatter(formatter);
                if (level != null)
                    handler.setLevel(level);
            } catch (Exception e) {
                // Do nothing, can't instantiate the handler class
            }
        }

        return handlers;
    }

    private static Formatter getFormatter(Handler handler, LogManager logManager, Properties props) {
        String propName = handler.getClass().getName() + ".formatter";
        String formatterClassName = getPropertyValue(propName, logManager, props);

        try {
            Class clazz = Class.forName(formatterClassName).asSubclass(Formatter.class);
            Constructor cons = null;

            try {
                cons = clazz.getConstructor(new Class[] { Properties.class, LogManager.class });
            } catch (Exception e) {
                // do nothing, may not be our LogFormatter class
            }

            if (cons != null) {
                return cons.newInstance(new Object[] { props, logManager });
            }

            return (Formatter)Class.forName(formatterClassName).newInstance();
        } catch (Exception e) {
            return null;
        }
    }

    private static Level getLoggerLevel(Logger logger, LogManager logManager, Properties props) {
        String propName = logger.getName() + ".level";
        String levelName = getPropertyValue(propName, logManager, props);

        try {
            return Level.parse(levelName);
        } catch (Exception e) {
            return null;
        }
    }

    private static boolean getUseParentHandlersFlag(Logger logger, LogManager logManager, Properties props) {
        String propName = logger.getName() + ".useParentHandlers";
        String propValue = getPropertyValue(propName, logManager, props);

        if (propValue == null) {
            return false;
        }

        try {
            return Boolean.valueOf(propValue).booleanValue();
        } catch (Exception e) {
            return false;
        }
    }

    private static String getPropertyValue(String propName, LogManager logManager, Properties props) {
        String propVal = props.getProperty(propName);

        if (propVal != null) {
            return propVal.trim();
        }

        propVal = logManager.getProperty(propName);

        if (propVal != null) {
            return propVal.trim();
        }

        return null;
    }

    // -------------------------------------------------------------

    @Override
    public Session newSession(String userName, String password) {
        return newSession(userName, 
                password == null ? null : password.toCharArray());
    }

    @Override
    public Session newSession(String userName, String password,
            String contentbaseId) {
        return newSession(userName, 
                password == null ? null : password.toCharArray(), 
                        contentbaseId);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy