<commit_before>/*
 * Copyright (c) 2008, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * 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 org.wso2.carbon.registry.core.caching;

import javax.cache.Cache;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.context.CarbonContext;
import org.wso2.carbon.registry.api.GhostResource;
import org.wso2.carbon.registry.core.*;
import org.wso2.carbon.registry.core.config.DataBaseConfiguration;
import org.wso2.carbon.registry.core.config.Mount;
import org.wso2.carbon.registry.core.config.RegistryContext;
import org.wso2.carbon.registry.core.config.RemoteConfiguration;
import org.wso2.carbon.registry.core.exceptions.RegistryException;
import org.wso2.carbon.registry.core.jdbc.EmbeddedRegistry;
import org.wso2.carbon.registry.core.secure.AuthorizationFailedException;
import org.wso2.carbon.registry.core.session.CurrentSession;
import org.wso2.carbon.registry.core.utils.AuthorizationUtils;
import org.wso2.carbon.registry.core.utils.RegistryUtils;
import org.wso2.carbon.utils.multitenancy.MultitenantConstants;

import java.io.Reader;
import java.io.Writer;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * CacheBackedRegistry has wrapped from original Registry interface to support caching
 */
public class CacheBackedRegistry implements Registry {

    /**
     * wrapped this original registry object from CachedBackedRegistry
     */
    private Registry registry;

    private int tenantId = MultitenantConstants.INVALID_TENANT_ID;

    private Map<String, String> cacheIds =
            new HashMap<String, String>();
    private Map<String, DataBaseConfiguration> dbConfigs =
            new HashMap<String, DataBaseConfiguration>();
    private Map<String, String> pathMap =
            new HashMap<String, String>();

    private static Cache<RegistryCacheKey, GhostResource> getCache() {
        return RegistryUtils.getResourceCache(RegistryConstants.REGISTRY_CACHE_BACKED_ID);
    }

    private static final Log log = LogFactory.getLog(CacheBackedRegistry.class);


    public CacheBackedRegistry(Registry registry) {
        this.registry = registry;
        RegistryContext registryContext = RegistryContext.getBaseInstance();
        for (Mount mount : registryContext.getMounts()) {
            for(RemoteConfiguration configuration : registryContext.getRemoteInstances()) {
                if (configuration.getDbConfig() != null &&
                        mount.getInstanceId().equals(configuration.getId())) {
                    dbConfigs.put(mount.getPath(),
                            registryContext.getDBConfig(configuration.getDbConfig()));
                    pathMap.put(mount.getPath(), mount.getTargetPath());
                } else if (configuration.getCacheId() != null &&
                        mount.getInstanceId().equals(configuration.getId())) {
                    cacheIds.put(mount.getPath(), configuration.getCacheId());
                    pathMap.put(mount.getPath(), mount.getTargetPath());
                }
            }
        }
    }

    public CacheBackedRegistry(Registry registry, int tenantId) {
        this(registry);
        this.tenantId = tenantId;
    }

    /**
     * This method used to calculate the cache key
     *
     * @param registry Registry
     * @param path     Resource path
     *
     * @return RegistryCacheKey
     */
    private RegistryCacheKey getRegistryCacheKey(Registry registry, String path) {
        String connectionId = "";

        int tenantId;
        if (this.tenantId == MultitenantConstants.INVALID_TENANT_ID) {
            tenantId = CurrentSession.getTenantId();
            if (tenantId == MultitenantConstants.INVALID_TENANT_ID) {
                tenantId = CarbonContext.getThreadLocalCarbonContext().getTenantId();
            }
        } else {
            tenantId = this.tenantId;
        }
        String resourceCachePath;
        RegistryContext registryContext = registry.getRegistryContext();
        if (registryContext == null) {
            registryContext = RegistryContext.getBaseInstance();
        }
        if (registry instanceof EmbeddedRegistry) {
            resourceCachePath = path;
        } else {
            resourceCachePath = RegistryUtils.getAbsolutePath(registryContext, path);
        }
        DataBaseConfiguration dataBaseConfiguration = null;
        if (dbConfigs.size() > 0) {
            for (String sourcePath : dbConfigs.keySet()) {
                if (resourceCachePath.startsWith(sourcePath)) {
                    resourceCachePath = pathMap.get(sourcePath) + resourceCachePath.substring(sourcePath.length());
                    dataBaseConfiguration = dbConfigs.get(sourcePath);
                    break;
                }
            }
        } else if (cacheIds.size() > 0) {
            for (String sourcePath : cacheIds.keySet()) {
                if (resourceCachePath.startsWith(sourcePath)) {
                    resourceCachePath = pathMap.get(sourcePath) + resourceCachePath.substring(sourcePath.length());
                    connectionId = cacheIds.get(sourcePath);
                    break;
                }
            }
        }
        if (connectionId.length() == 0) {
            if (dataBaseConfiguration == null) {
                dataBaseConfiguration = registryContext.getDefaultDataBaseConfiguration();
            }
            if (dataBaseConfiguration != null) {
                connectionId = (dataBaseConfiguration.getUserName() != null
                        ? dataBaseConfiguration.getUserName().split("@")[0]:dataBaseConfiguration.getUserName()) + "@" + dataBaseConfiguration.getDbUrl();
            }
        }

        return RegistryUtils.buildRegistryCacheKey(connectionId, tenantId, resourceCachePath);
    }

    @SuppressWarnings("unchecked")
    public Resource get(String path) throws RegistryException {
        if (registry.getRegistryContext().isNoCachePath(path) || isCommunityFeatureRequest(path)) {
            return registry.get(path);
        }
        
        Resource resource;        
        if (!AuthorizationUtils.authorize(path, ActionConstants.GET)) {
            String msg = "User " + CurrentSession.getUser() + " is not authorized to " +
                    "read the resource " + path + ".";
            log.warn(msg);
            throw new AuthorizationFailedException(msg);
        }

        GhostResource<Resource> ghostResource = getGhostResourceFromCache(path);

        resource = ghostResource.getResource();
        if (resource == null) {
            resource = registry.get(path);
            if (resource.getProperty(RegistryConstants.REGISTRY_LINK) == null ||
                    resource.getProperty(RegistryConstants.REGISTRY_MOUNT) != null) {
                ghostResource.setResource(resource);
            }
        }

        return resource;
    }
    
    private GhostResource<Resource> getGhostResourceFromCache(String path) throws RegistryException {
        Resource resource;
        RegistryCacheKey registryCacheKey = getRegistryCacheKey(registry, path);

        GhostResource<Resource> ghostResource;
        Object ghostResourceObject;
        Cache<RegistryCacheKey, GhostResource> cache = getCache();
        if ((ghostResourceObject = cache.get(registryCacheKey)) == null) {
            synchronized (path.intern()){
                //Checking again as the some other previous thread might have updated the cache
                if ((ghostResourceObject = cache.get(registryCacheKey)) == null) {
                    resource = registry.get(path);
                    ghostResource = new GhostResource<Resource>(resource);
                    if (resource.getProperty(RegistryConstants.REGISTRY_LINK) == null ||
                            resource.getProperty(RegistryConstants.REGISTRY_MOUNT) != null) {
                        cache.put(registryCacheKey, ghostResource);
                    }
                }else {
                    ghostResource = (GhostResource<Resource>) ghostResourceObject;
                }
            }
        } else {
            ghostResource = (GhostResource<Resource>) ghostResourceObject;
        }
       return ghostResource;
    }


    private GhostResource<Resource> getGhostCollectionFromCache(String path, int start, int pageSize)
            throws RegistryException {
        Collection collection;

        GhostResource<Resource> ghostResource;

        RegistryCacheKey registryCacheKey = getRegistryCacheKey(registry, path +
                ";start=" + start + ";pageSize=" + pageSize);
        
        Cache<RegistryCacheKey, GhostResource> cache = getCache();
        if (!cache.containsKey(registryCacheKey)) {
            synchronized (path.intern()) {
                //check again to cache to validate whether any other thread have updated with that time.
                if (!cache.containsKey(registryCacheKey)) {
                    collection = registry.get(path, start, pageSize);
                    ghostResource = new GhostResource<Resource>(collection);
                    if (collection.getProperty(RegistryConstants.REGISTRY_LINK) == null) {
                        cache.put(registryCacheKey, ghostResource);
                    }
                } else {
                    ghostResource =
                            (GhostResource<Resource>) cache.get(registryCacheKey);
                }
            }
        } else {
            ghostResource =
                    (GhostResource<Resource>) cache.get(registryCacheKey);
        }

        return ghostResource;
    }

    @SuppressWarnings("unchecked")
    public Collection get(String path, int start, int pageSize) throws RegistryException {
        if (registry.getRegistryContext().isNoCachePath(path) || isCommunityFeatureRequest(path)) {
            return registry.get(path, start, pageSize);
        }
        if (!AuthorizationUtils.authorize(path, ActionConstants.GET)) {
            String msg = "User " + CurrentSession.getUser() + " is not authorized to " +
                    "read the resource " + path + ".";
            log.warn(msg);
            throw new AuthorizationFailedException(msg);
        }

        GhostResource<Resource> ghostResource = getGhostCollectionFromCache(path, start, pageSize);
        Collection collection = (Collection) ghostResource.getResource();
        if (collection == null) {
            collection = registry.get(path, start, pageSize);
            if (collection.getProperty(RegistryConstants.REGISTRY_LINK) == null) {
                ghostResource.setResource(collection);
            }
        }
        return collection;
    }

    // test whether this request was made specifically for a tag, comment or a rating.
    private boolean isCommunityFeatureRequest(String path) {
        if (path == null) {
            return false;
        }
        String resourcePath = new ResourcePath(path).getPath();<commit_msg>Fix for REGISTRY-2147<commit_after>/*
 * Copyright (c) 2008, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * 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 org.wso2.carbon.registry.core.caching;

import javax.cache.Cache;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.context.CarbonContext;
import org.wso2.carbon.registry.api.GhostResource;
import org.wso2.carbon.registry.core.*;
import org.wso2.carbon.registry.core.config.DataBaseConfiguration;
import org.wso2.carbon.registry.core.config.Mount;
import org.wso2.carbon.registry.core.config.RegistryContext;
import org.wso2.carbon.registry.core.config.RemoteConfiguration;
import org.wso2.carbon.registry.core.exceptions.RegistryException;
import org.wso2.carbon.registry.core.jdbc.EmbeddedRegistry;
import org.wso2.carbon.registry.core.secure.AuthorizationFailedException;
import org.wso2.carbon.registry.core.session.CurrentSession;
import org.wso2.carbon.registry.core.utils.AuthorizationUtils;
import org.wso2.carbon.registry.core.utils.RegistryUtils;
import org.wso2.carbon.utils.multitenancy.MultitenantConstants;

import java.io.Reader;
import java.io.Writer;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * CacheBackedRegistry has wrapped from original Registry interface to support caching
 */
public class CacheBackedRegistry implements Registry {

    /**
     * wrapped this original registry object from CachedBackedRegistry
     */
    private Registry registry;

    private int tenantId = MultitenantConstants.INVALID_TENANT_ID;

    private Map<String, String> cacheIds =
            new HashMap<String, String>();
    private Map<String, DataBaseConfiguration> dbConfigs =
            new HashMap<String, DataBaseConfiguration>();
    private Map<String, String> pathMap =
            new HashMap<String, String>();

    private static Cache<RegistryCacheKey, GhostResource> getCache() {
        return RegistryUtils.getResourceCache(RegistryConstants.REGISTRY_CACHE_BACKED_ID);
    }

    private static final Log log = LogFactory.getLog(CacheBackedRegistry.class);


    public CacheBackedRegistry(Registry registry) {
        this.registry = registry;
        RegistryContext registryContext = RegistryContext.getBaseInstance();
        for (Mount mount : registryContext.getMounts()) {
            for(RemoteConfiguration configuration : registryContext.getRemoteInstances()) {
                if (configuration.getDbConfig() != null &&
                        mount.getInstanceId().equals(configuration.getId())) {
                    dbConfigs.put(mount.getPath(),
                            registryContext.getDBConfig(configuration.getDbConfig()));
                    pathMap.put(mount.getPath(), mount.getTargetPath());
                } else if (configuration.getCacheId() != null &&
                        mount.getInstanceId().equals(configuration.getId())) {
                    cacheIds.put(mount.getPath(), configuration.getCacheId());
                    pathMap.put(mount.getPath(), mount.getTargetPath());
                }
            }
        }
    }

    public CacheBackedRegistry(Registry registry, int tenantId) {
        this(registry);
        this.tenantId = tenantId;
    }

    /**
     * This method used to calculate the cache key
     *
     * @param registry Registry
     * @param path     Resource path
     *
     * @return RegistryCacheKey
     */
    private RegistryCacheKey getRegistryCacheKey(Registry registry, String path) {
        String connectionId = "";

        int tenantId;
        if (this.tenantId == MultitenantConstants.INVALID_TENANT_ID) {
            tenantId = CurrentSession.getTenantId();
            if (tenantId == MultitenantConstants.INVALID_TENANT_ID) {
                tenantId = CarbonContext.getThreadLocalCarbonContext().getTenantId();
            }
        } else {
            tenantId = this.tenantId;
        }
        String resourceCachePath;
        RegistryContext registryContext = registry.getRegistryContext();
        if (registryContext == null) {
            registryContext = RegistryContext.getBaseInstance();
        }
        if (registry instanceof EmbeddedRegistry) {
            resourceCachePath = path;
        } else {
            resourceCachePath = RegistryUtils.getAbsolutePath(registryContext, path);
        }
        DataBaseConfiguration dataBaseConfiguration = null;
        if (dbConfigs.size() > 0) {
            for (String sourcePath : dbConfigs.keySet()) {
                if (resourceCachePath.startsWith(sourcePath)) {
                    resourceCachePath = pathMap.get(sourcePath) + resourceCachePath.substring(sourcePath.length());
                    dataBaseConfiguration = dbConfigs.get(sourcePath);
                    break;
                }
            }
        } else if (cacheIds.size() > 0) {
            for (String sourcePath : cacheIds.keySet()) {
                if (resourceCachePath.startsWith(sourcePath)) {
                    resourceCachePath = pathMap.get(sourcePath) + resourceCachePath.substring(sourcePath.length());
                    connectionId = cacheIds.get(sourcePath);
                    break;
                }
            }
        }
        if (connectionId.length() == 0) {
            if (dataBaseConfiguration == null) {
                dataBaseConfiguration = registryContext.getDefaultDataBaseConfiguration();
            }
            if (dataBaseConfiguration != null) {
                connectionId = (dataBaseConfiguration.getUserName() != null
                        ? dataBaseConfiguration.getUserName().split("@")[0]:dataBaseConfiguration.getUserName()) + "@" + dataBaseConfiguration.getDbUrl();
            }
        }

        return RegistryUtils.buildRegistryCacheKey(connectionId, tenantId, resourceCachePath);
    }

    @SuppressWarnings("unchecked")
    public Resource get(String path) throws RegistryException {
        if (registry.getRegistryContext().isNoCachePath(path) || isCommunityFeatureRequest(path)) {
            return registry.get(path);
        }
        
        Resource resource;        
        if (!AuthorizationUtils.authorize(path, ActionConstants.GET)) {
            String msg = "User " + CurrentSession.getUser() + " is not authorized to " +
                    "read the resource " + path + ".";
            log.warn(msg);
            throw new AuthorizationFailedException(msg);
        }

        GhostResource<Resource> ghostResource = getGhostResourceFromCache(path);

        resource = ghostResource.getResource();
        if (resource == null) {
        	synchronized (path.intern()) {
	            resource = registry.get(path);
	            if (resource.getProperty(RegistryConstants.REGISTRY_LINK) == null ||
	                    resource.getProperty(RegistryConstants.REGISTRY_MOUNT) != null) {
	                ghostResource.setResource(resource);
	            }
        	}
        }

        return resource;
    }
    
    private GhostResource<Resource> getGhostResourceFromCache(String path) throws RegistryException {
        Resource resource;
        RegistryCacheKey registryCacheKey = getRegistryCacheKey(registry, path);

        GhostResource<Resource> ghostResource;
        Object ghostResourceObject;
        Cache<RegistryCacheKey, GhostResource> cache = getCache();
        if ((ghostResourceObject = cache.get(registryCacheKey)) == null) {
            synchronized (path.intern()){
                //Checking again as the some other previous thread might have updated the cache
                if ((ghostResourceObject = cache.get(registryCacheKey)) == null) {
                    resource = registry.get(path);
                    ghostResource = new GhostResource<Resource>(resource);
                    if (resource.getProperty(RegistryConstants.REGISTRY_LINK) == null ||
                            resource.getProperty(RegistryConstants.REGISTRY_MOUNT) != null) {
                        cache.put(registryCacheKey, ghostResource);
                    }
                }else {
                    ghostResource = (GhostResource<Resource>) ghostResourceObject;
                }
            }
        } else {
            ghostResource = (GhostResource<Resource>) ghostResourceObject;
        }
       return ghostResource;
    }


    private GhostResource<Resource> getGhostCollectionFromCache(String path, int start, int pageSize)
            throws RegistryException {
        Collection collection;

        GhostResource<Resource> ghostResource;

        RegistryCacheKey registryCacheKey = getRegistryCacheKey(registry, path +
                ";start=" + start + ";pageSize=" + pageSize);
        
        Cache<RegistryCacheKey, GhostResource> cache = getCache();
        if (!cache.containsKey(registryCacheKey)) {
            synchronized (path.intern()) {
                //check again to cache to validate whether any other thread have updated with that time.
                if (!cache.containsKey(registryCacheKey)) {
                    collection = registry.get(path, start, pageSize);
                    ghostResource = new GhostResource<Resource>(collection);
                    if (collection.getProperty(RegistryConstants.REGISTRY_LINK) == null) {
                        cache.put(registryCacheKey, ghostResource);
                    }
                } else {
                    ghostResource =
                            (GhostResource<Resource>) cache.get(registryCacheKey);
                }
            }
        } else {
            ghostResource =
                    (GhostResource<Resource>) cache.get(registryCacheKey);
        }

        return ghostResource;
    }

    @SuppressWarnings("unchecked")
    public Collection get(String path, int start, int pageSize) throws RegistryException {
        if (registry.getRegistryContext().isNoCachePath(path) || isCommunityFeatureRequest(path)) {
            return registry.get(path, start, pageSize);
        }
        if (!AuthorizationUtils.authorize(path, ActionConstants.GET)) {
            String msg = "User " + CurrentSession.getUser() + " is not authorized to " +
                    "read the resource " + path + ".";
            log.warn(msg);
            throw new AuthorizationFailedException(msg);
        }

        GhostResource<Resource> ghostResource = getGhostCollectionFromCache(path, start, pageSize);
        Collection collection = (Collection) ghostResource.getResource();
        if (collection == null) {
        	synchronized (path.intern()) {
	            collection = registry.get(path, start, pageSize);
	            if (collection.getProperty(RegistryConstants.REGISTRY_LINK) == null) {
	                ghostResource.setResource(collection);
	            }
            }
        }
        return collection;
    }

    // test whether this request was made specifically for a tag, comment or a rating.
    private boolean isCommunityFeatureRequest(String path) {
        if (path == null) {
            return false;
        }
        String resourcePath = new ResourcePath(path).getPath();
{'fraction_non_alphanumeric': 0.09326133909287257, 'fraction_numerical': 0.0026781857451403887, 'mean_word_length': 2.387620719929763, 'pattern_counts': {'":': 0, '<': 45, '<?xml version=': 0, '>': 49, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2283267', 'n_tokens_mistral': 5801, 'n_tokens_neox': 5614, 'n_words': 1249}