<commit_before>package com.cedarsoftware.controller

import com.cedarsoftware.ncube.ApplicationID
import com.cedarsoftware.ncube.Axis
import com.cedarsoftware.ncube.AxisType
import com.cedarsoftware.ncube.AxisValueType
import com.cedarsoftware.ncube.CellInfo
import com.cedarsoftware.ncube.Column
import com.cedarsoftware.ncube.CommandCell
import com.cedarsoftware.ncube.Delta
import com.cedarsoftware.ncube.DeltaProcessor
import com.cedarsoftware.ncube.GroovyExpression
import com.cedarsoftware.ncube.NCube
import com.cedarsoftware.ncube.NCubeInfoDto
import com.cedarsoftware.ncube.NCubeManager
import com.cedarsoftware.ncube.NCubeTest
import com.cedarsoftware.ncube.RuleInfo
import com.cedarsoftware.ncube.StringValuePair
import com.cedarsoftware.ncube.exception.AxisOverlapException
import com.cedarsoftware.ncube.exception.BranchMergeException
import com.cedarsoftware.ncube.exception.CommandCellException
import com.cedarsoftware.ncube.exception.CoordinateNotFoundException
import com.cedarsoftware.ncube.exception.RuleJump
import com.cedarsoftware.ncube.exception.RuleStop
import com.cedarsoftware.ncube.formatters.NCubeTestReader
import com.cedarsoftware.ncube.formatters.NCubeTestWriter
import com.cedarsoftware.ncube.formatters.TestResultsFormatter
import com.cedarsoftware.service.ncube.NCubeService
import com.cedarsoftware.servlet.JsonCommandServlet
import com.cedarsoftware.util.ArrayUtilities
import com.cedarsoftware.util.CaseInsensitiveSet
import com.cedarsoftware.util.Converter
import com.cedarsoftware.util.Visualizer
import com.cedarsoftware.util.InetAddressUtilities
import com.cedarsoftware.util.StringUtilities
import com.cedarsoftware.util.ThreadAwarePrintStream
import com.cedarsoftware.util.ThreadAwarePrintStreamErr
import com.cedarsoftware.util.io.JsonReader
import com.cedarsoftware.util.io.JsonWriter
import com.google.common.util.concurrent.AtomicDouble
import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap
import groovy.transform.CompileStatic
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger

import javax.management.MBeanServer
import javax.management.ObjectName
import javax.servlet.http.HttpServletRequest
import java.lang.management.ManagementFactory
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentMap
import java.util.concurrent.ConcurrentSkipListSet
import java.util.regex.Pattern

/**
 * NCubeController API.
 *
 * @author <NAME> (<EMAIL>)
 *         <br/>
 *         Copyright (c) Cedar Software LLC
 *         <br/><br/>
 *         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
 *         <br/><br/>
 *         http://www.apache.org/licenses/LICENSE-2.0
 *         <br/><br/>
 *         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.
 */
@CompileStatic
class NCubeController extends BaseController
{
    private static final Logger LOG = LogManager.getLogger(NCubeController.class)
    private static final Pattern VERSION_REGEX = ~/[.]/
    private static final Pattern IS_NUMBER_REGEX = ~/^[\d,.e+-]+$/
    private static final Pattern NO_QUOTES_REGEX = ~/"/
    private NCubeService nCubeService;
    private static String servletHostname = null
    private static String inetHostname = null
    private static AtomicDouble processLoadPeak = new AtomicDouble(0.0d)
    private static AtomicDouble systemLoadPeak = new AtomicDouble(0.0d)

    // TODO: Temporary until we have n_cube_app table
    // TODO: Verify all places that can create a cube are clearing the appCache
    private static final Set<String> appCache = new ConcurrentSkipListSet<>()

    // TODO: Temporary until we have n_cube_app_versions table
    // TODO: Verify all places that can create a cube are clearing the appVersionsCache
    private static final Map<String, List<String>> appVersions = new ConcurrentHashMap<>()
    private static final Object versionsLock = new Object()

    // Bind to ConcurrentLinkedHashMap because some plugins will need it.
    private ConcurrentMap<String, Object> futureCache = new ConcurrentLinkedHashMap.Builder<String, Object>()
            .maximumWeightedCapacity(100)
            .build()

    NCubeController(NCubeService service)
    {
        nCubeService = service;
        System.err = new ThreadAwarePrintStreamErr()
        System.out = new ThreadAwarePrintStream()
    }

    private String getUserForDatabase()
    {
        String user = getUser()
        return StringUtilities.length(user) > 10 ? user.substring(0, 10) : user;
    }

    private static String getUser()
    {
        String user = null
        HttpServletRequest request = JsonCommandServlet.servletRequest.get()
        Enumeration e = request.headerNames
        while (e.hasMoreElements())
        {
            String headerName = (String) e.nextElement()
            if ("smuser".equalsIgnoreCase(headerName))
            {
                user = request.getHeader(headerName)
                break;
            }
        }

        if (StringUtilities.isEmpty(user))
        {
            user = System.getProperty("user.name")
        }

        return user
    }

    // TODO: This API will look into sys.permissions cube for determination of access
    private static boolean isAllowed(ApplicationID appId, String cubeName, Delta.Type operation)
    {
        String action = operation != null ? operation.toString() : 'read'
        if (false)
        {   // permissions checks
            String msg = 'You do not have ' + action + ' access'
            if (StringUtilities.hasContent(cubeName) && appId != null)
            {
                msg += ' to ' + appId.cacheKey(cubeName)
            }
            else
            {
                if (appId)
                {
                    msg += ' to ' + appId
                }
                else if (cubeName)
                {
                    msg += ' to ' + cubeName
                }
                else
                {
                    msg += '.'
                }
            }
            markRequestFailed(msg)
            throw new SecurityException(msg)
        }

        return true
    }

    // ============================================= Begin API =========================================================

    Object[] search(ApplicationID appId, String cubeNamePattern, String content, boolean active)
    {
        try
        {
            appId = addTenant(appId)
            isAllowed(appId, cubeNamePattern, null)
            Map options = [:]
            if (active)
            {
                options[(NCubeManager.SEARCH_ACTIVE_RECORDS_ONLY)] = true
            }
            else
            {
                options[(NCubeManager.SEARCH_DELETED_RECORDS_ONLY)] = true
            }

            List<NCubeInfoDto> cubeInfos = nCubeService.search(appId, cubeNamePattern, content, options)

            Collections.sort(cubeInfos, new Comparator<NCubeInfoDto>() {
                public int compare(NCubeInfoDto info1, NCubeInfoDto info2)
                {
                    return info1.name.compareToIgnoreCase(info2.name)
                }
            })

            return cubeInfos.toArray()
        }
        catch (Exception e)
        {
            fail(e)
            return null
        }
    }

    void restoreCubes(ApplicationID appId, Object[] cubeNames)
    {
        try
        {
            appId = addTenant(appId)
            isAllowed(appId, null, Delta.Type.ADD)
            nCubeService.restoreCubes(appId, cubeNames, getUserForDatabase())
        }
        catch (Exception e)
        {
            fail(e)
        }
    }

    Object[] getRevisionHistory(ApplicationID appId, String cubeName)
    {
        try
        {
            appId = addTenant(appId)
            isAllowed(appId, cubeName, null)
            List<NCubeInfoDto> cubeInfos = nCubeService.getRevisionHistory(appId, cubeName)
            return cubeInfos.toArray()
        }
        catch (Exception e)
        {
            fail(e)
            return null
        }
    }

    String getHtml(ApplicationID appId, String cubeName)
    {
        try
        {
            appId = addTenant(appId)
            isAllowed(appId, cubeName, null)
            NCube ncube = nCubeService.loadCube(appId, cubeName)
            // The Strings below are hints to n-cube to tell it which axis to place on top
            String html = toHtmlWithColumnHints(ncube)
            return html
        }
        catch (Exception e)
        {
            fail(e)
            return null
        }
    }

    private String toHtmlWithColumnHints(NCube ncube)
    {
        ncube.toHtml('trait', 'traits', 'businessDivisionCode', 'bu', 'month', 'months', 'col', 'column', 'cols', 'columns')
    }

    String getJson(ApplicationID appId, String cubeName)
    {
        return getJson(appId, cubeName, [mode:"json-index"])
    }

    String getJson(ApplicationID appId, String cubeName, Map options)
    {
        try
        {
            appId = addTenant(appId)
            isAllowed(appId, cubeName, null)
            NCube ncube = nCubeService.loadCube(appId, cubeName)
            String mode = options.mode
            switch(mode)
            {
                case "json":
                    return ncube.toFormattedJson()
                case "json-pretty":
                    return JsonWriter.formatJson(ncube.toFormattedJson())
                case "json-index":
                    return ncube.toFormattedJson([indexFormat:true] as Map)
                case "json-index-pretty":
                    return JsonWriter.formatJson(ncube.toFormattedJson([indexFormat:true] as Map))
                case "html":
                    return ncube.toHtml()
                default:
                    throw new IllegalArgumentException("getJson() - unknown mode: " + mode)
            }
        }
        catch (Exception e)
        {
            fail(e)
            return null
        }
    }

    Map getVisualizerJson(ApplicationID appId, Map options)
    {
        try
        {
            String cubeName = options.startCubeName
            if (!cubeName.startsWith(Visualizer.RPM_CLASS))
            {
                throw new IllegalArgumentException('n-cube name must begin with rpm.class. Name = ' + cubeName)
            }
            appId = addTenant(appId)
            isAllowed(appId, cubeName, null)

            Visualizer vis = new Visualizer()
            vis.input = [options:options]
            vis.ncube = nCubeService.getCube(appId, cubeName)
            return vis.buildGraph()
        }<commit_msg>[RA-26932] Refactor items in previous code review PR #170<commit_after>package com.cedarsoftware.controller

import com.cedarsoftware.ncube.ApplicationID
import com.cedarsoftware.ncube.Axis
import com.cedarsoftware.ncube.AxisType
import com.cedarsoftware.ncube.AxisValueType
import com.cedarsoftware.ncube.CellInfo
import com.cedarsoftware.ncube.Column
import com.cedarsoftware.ncube.CommandCell
import com.cedarsoftware.ncube.Delta
import com.cedarsoftware.ncube.DeltaProcessor
import com.cedarsoftware.ncube.GroovyExpression
import com.cedarsoftware.ncube.NCube
import com.cedarsoftware.ncube.NCubeInfoDto
import com.cedarsoftware.ncube.NCubeManager
import com.cedarsoftware.ncube.NCubeTest
import com.cedarsoftware.ncube.RuleInfo
import com.cedarsoftware.ncube.StringValuePair
import com.cedarsoftware.ncube.exception.AxisOverlapException
import com.cedarsoftware.ncube.exception.BranchMergeException
import com.cedarsoftware.ncube.exception.CommandCellException
import com.cedarsoftware.ncube.exception.CoordinateNotFoundException
import com.cedarsoftware.ncube.exception.RuleJump
import com.cedarsoftware.ncube.exception.RuleStop
import com.cedarsoftware.ncube.formatters.NCubeTestReader
import com.cedarsoftware.ncube.formatters.NCubeTestWriter
import com.cedarsoftware.ncube.formatters.TestResultsFormatter
import com.cedarsoftware.service.ncube.NCubeService
import com.cedarsoftware.servlet.JsonCommandServlet
import com.cedarsoftware.util.ArrayUtilities
import com.cedarsoftware.util.CaseInsensitiveSet
import com.cedarsoftware.util.Converter
import com.cedarsoftware.util.Visualizer
import com.cedarsoftware.util.InetAddressUtilities
import com.cedarsoftware.util.StringUtilities
import com.cedarsoftware.util.ThreadAwarePrintStream
import com.cedarsoftware.util.ThreadAwarePrintStreamErr
import com.cedarsoftware.util.io.JsonReader
import com.cedarsoftware.util.io.JsonWriter
import com.google.common.util.concurrent.AtomicDouble
import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap
import groovy.transform.CompileStatic
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger

import javax.management.MBeanServer
import javax.management.ObjectName
import javax.servlet.http.HttpServletRequest
import java.lang.management.ManagementFactory
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentMap
import java.util.concurrent.ConcurrentSkipListSet
import java.util.regex.Pattern

/**
 * NCubeController API.
 *
 * @author <NAME> (<EMAIL>)
 *         <br/>
 *         Copyright (c) Cedar Software LLC
 *         <br/><br/>
 *         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
 *         <br/><br/>
 *         http://www.apache.org/licenses/LICENSE-2.0
 *         <br/><br/>
 *         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.
 */
@CompileStatic
class NCubeController extends BaseController
{
    private static final Logger LOG = LogManager.getLogger(NCubeController.class)
    private static final Pattern VERSION_REGEX = ~/[.]/
    private static final Pattern IS_NUMBER_REGEX = ~/^[\d,.e+-]+$/
    private static final Pattern NO_QUOTES_REGEX = ~/"/
    private NCubeService nCubeService;
    private static String servletHostname = null
    private static String inetHostname = null
    private static AtomicDouble processLoadPeak = new AtomicDouble(0.0d)
    private static AtomicDouble systemLoadPeak = new AtomicDouble(0.0d)

    // TODO: Temporary until we have n_cube_app table
    // TODO: Verify all places that can create a cube are clearing the appCache
    private static final Set<String> appCache = new ConcurrentSkipListSet<>()

    // TODO: Temporary until we have n_cube_app_versions table
    // TODO: Verify all places that can create a cube are clearing the appVersionsCache
    private static final Map<String, List<String>> appVersions = new ConcurrentHashMap<>()
    private static final Object versionsLock = new Object()

    // Bind to ConcurrentLinkedHashMap because some plugins will need it.
    private ConcurrentMap<String, Object> futureCache = new ConcurrentLinkedHashMap.Builder<String, Object>()
            .maximumWeightedCapacity(100)
            .build()

    NCubeController(NCubeService service)
    {
        nCubeService = service;
        System.err = new ThreadAwarePrintStreamErr()
        System.out = new ThreadAwarePrintStream()
    }

    private String getUserForDatabase()
    {
        String user = getUser()
        return StringUtilities.length(user) > 10 ? user.substring(0, 10) : user;
    }

    private static String getUser()
    {
        String user = null
        HttpServletRequest request = JsonCommandServlet.servletRequest.get()
        Enumeration e = request.headerNames
        while (e.hasMoreElements())
        {
            String headerName = (String) e.nextElement()
            if ("smuser".equalsIgnoreCase(headerName))
            {
                user = request.getHeader(headerName)
                break;
            }
        }

        if (StringUtilities.isEmpty(user))
        {
            user = System.getProperty("user.name")
        }

        return user
    }

    // TODO: This API will look into sys.permissions cube for determination of access
    private static boolean isAllowed(ApplicationID appId, String cubeName, Delta.Type operation)
    {
        String action = operation != null ? operation.toString() : 'read'
        if (false)
        {   // permissions checks
            String msg = 'You do not have ' + action + ' access'
            if (StringUtilities.hasContent(cubeName) && appId != null)
            {
                msg += ' to ' + appId.cacheKey(cubeName)
            }
            else
            {
                if (appId)
                {
                    msg += ' to ' + appId
                }
                else if (cubeName)
                {
                    msg += ' to ' + cubeName
                }
                else
                {
                    msg += '.'
                }
            }
            markRequestFailed(msg)
            throw new SecurityException(msg)
        }

        return true
    }

    // ============================================= Begin API =========================================================

    Object[] search(ApplicationID appId, String cubeNamePattern, String content, boolean active)
    {
        try
        {
            appId = addTenant(appId)
            isAllowed(appId, cubeNamePattern, null)
            Map options = [:]
            if (active)
            {
                options[(NCubeManager.SEARCH_ACTIVE_RECORDS_ONLY)] = true
            }
            else
            {
                options[(NCubeManager.SEARCH_DELETED_RECORDS_ONLY)] = true
            }

            List<NCubeInfoDto> cubeInfos = nCubeService.search(appId, cubeNamePattern, content, options)

            Collections.sort(cubeInfos, new Comparator<NCubeInfoDto>() {
                public int compare(NCubeInfoDto info1, NCubeInfoDto info2)
                {
                    return info1.name.compareToIgnoreCase(info2.name)
                }
            })

            return cubeInfos.toArray()
        }
        catch (Exception e)
        {
            fail(e)
            return null
        }
    }

    void restoreCubes(ApplicationID appId, Object[] cubeNames)
    {
        try
        {
            appId = addTenant(appId)
            isAllowed(appId, null, Delta.Type.ADD)
            nCubeService.restoreCubes(appId, cubeNames, getUserForDatabase())
        }
        catch (Exception e)
        {
            fail(e)
        }
    }

    Object[] getRevisionHistory(ApplicationID appId, String cubeName)
    {
        try
        {
            appId = addTenant(appId)
            isAllowed(appId, cubeName, null)
            List<NCubeInfoDto> cubeInfos = nCubeService.getRevisionHistory(appId, cubeName)
            return cubeInfos.toArray()
        }
        catch (Exception e)
        {
            fail(e)
            return null
        }
    }

    String getHtml(ApplicationID appId, String cubeName)
    {
        try
        {
            appId = addTenant(appId)
            isAllowed(appId, cubeName, null)
            NCube ncube = nCubeService.loadCube(appId, cubeName)
            // The Strings below are hints to n-cube to tell it which axis to place on top
            String html = toHtmlWithColumnHints(ncube)
            return html
        }
        catch (Exception e)
        {
            fail(e)
            return null
        }
    }

    private String toHtmlWithColumnHints(NCube ncube)
    {
        ncube.toHtml('trait', 'traits', 'businessDivisionCode', 'bu', 'month', 'months', 'col', 'column', 'cols', 'columns')
    }

    String getJson(ApplicationID appId, String cubeName)
    {
        return getJson(appId, cubeName, [mode:"json-index"])
    }

    String getJson(ApplicationID appId, String cubeName, Map options)
    {
        try
        {
            appId = addTenant(appId)
            isAllowed(appId, cubeName, null)
            NCube ncube = nCubeService.loadCube(appId, cubeName)
            String mode = options.mode
            switch(mode)
            {
                case "json":
                    return ncube.toFormattedJson()
                case "json-pretty":
                    return JsonWriter.formatJson(ncube.toFormattedJson())
                case "json-index":
                    return ncube.toFormattedJson([indexFormat:true] as Map)
                case "json-index-pretty":
                    return JsonWriter.formatJson(ncube.toFormattedJson([indexFormat:true] as Map))
                case "html":
                    return ncube.toHtml()
                default:
                    throw new IllegalArgumentException("getJson() - unknown mode: " + mode)
            }
        }
        catch (Exception e)
        {
            fail(e)
            return null
        }
    }

    Map getVisualizerJson(ApplicationID appId, Map options)
    {
        try
        {
            String cubeName = options.startCubeName
            if (!cubeName.startsWith(Visualizer.RPM_CLASS))
            {
                throw new IllegalArgumentException('n-cube name must begin with rpm.class, n-cube: ' + cubeName + ', app: ' + appId)
            }
            appId = addTenant(appId)
            isAllowed(appId, cubeName, null)

            Visualizer vis = new Visualizer()
            vis.input = [options:options]
            vis.ncube = nCubeService.getCube(appId, cubeName)
            return vis.buildGraph()
        }
{'fraction_non_alphanumeric': 0.08877296439101837, 'fraction_numerical': 0.0023588115218870494, 'mean_word_length': 2.253061826767006, 'pattern_counts': {'":': 10, '<': 41, '<?xml version=': 0, '>': 43, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, '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': '5712252', 'n_tokens_mistral': 6154, 'n_tokens_neox': 5898, 'n_words': 1402}