###
# Licensed Materials - Property of IBM* and/or HCL**
# @product.name.full@
# (c) Copyright IBM Corporation 2003, 2014, 2017. All Rights Reserved.
# (c) Copyright HCL Technologies Ltd. 2018. All Rights Reserved.
#
# U.S. Government Users Restricted Rights - Use, duplication or disclosure restricted by
# GSA ADP Schedule Contract with IBM Corp.
#
# * Trademark of International Business Machines
# ** Trademark of HCL Technologies Limited
#
# Filename: confdiscover.py
#
#
###

from org.codehaus.jettison.json import JSONObject, JSONArray;
import sys;
import os;
import codecs;
from java.io import File;  # @UnusedImport

from java.lang import Throwable
import traceback

# Necessary to make "os" functions work on Windows systems that did not exist when Jython 2.1 was created.
# This workaround overcomes the Jython Windows limitation described on the following webpage:
# http://www.ibm.com/support/knowledgecenter/SS7JFU_6.1.0/com.ibm.websphere.express.doc/info/exp/ae/cxml_jython.html
# WebSphere 9 has moved to Jython 2.7 so this workaround will fail in that environment - that is why
# I surrounded it with a try/except structure.
try:
    import javaos
    if javaos._osType == 'posix' and java.lang.System.getProperty('os.name').startswith('Windows'):  # @UndefinedVariable
      sys.registry.setProperty('python.os', 'nt');
      reload(javaos);
except:
    pass

sys.modules['AdminConfig'] = AdminConfig  # @UndefinedVariable
sys.modules['AdminControl'] = AdminControl  # @UndefinedVariable
sys.modules['AdminApp'] = AdminApp  # @UndefinedVariable
sys.modules['AdminTask'] = AdminTask  # @UndefinedVariable


configurationTypes = {};
stack = [];

def jsonObjectToArray(jsonObj):
  array = [];
  newObj = JSONObject(jsonObj, ['name', 'path','roleName','roleProperties','inheritTeam','description','teamMappings']);
  array.append(newObj);
  if jsonObj.has("children"):
    children = jsonObj.getJSONArray("children");
    for x in range(children.length()):
      array.extend(jsonObjectToArray(children.getJSONObject(x)));
  return array;

def jsonObjectToJSONArray(jsonObj):
  array = JSONArray();
  objs = jsonObjectToArray(jsonObj);
  for x in objs:
    array.put(x);
  return array;

def jsonObjectToArrayString(jsonObj):
  array = JSONArray();
  objs = jsonObjectToArray(jsonObj);
  for x in objs:
    array.put(x);
  return array.toString(4);

def getAllServerTemplatesAndParents(fullResourceJSONObject, curpartype = None, curpar = None, curclusname = None):
  '''
  :type fullResourceJSONObject: JSONObject'''
  returnval = {};
  roleName = None;
  if fullResourceJSONObject.has("roleName"):
    roleName = Util.roleNameToType(fullResourceJSONObject.getString("roleName"));
    if roleName == 'Server' and curpartype == 'ServerCluster':
      Log.debug("Adding server template to original file");
      returnval[fullResourceJSONObject] = [curpar, curclusname];
    elif roleName == 'ServerCluster':
      curclusname = fullResourceJSONObject.getJSONObject('roleProperties').getString("websphere.cluster");
  else:
    roleName = curpartype;
  if fullResourceJSONObject.has('children'):
    children = fullResourceJSONObject.getJSONArray('children'); #: :type children: JSONArray
    for i in range(0, children.length()):
      child = children.getJSONObject(i); #: :type child: JSONObject
      returnval.update(getAllServerTemplatesAndParents(child, roleName, fullResourceJSONObject, curclusname));
  return returnval;

#this method is in charge of ghosting JSON created from cluster multiplicity properties when we compare
#so that the user can see what the configuration will look like against the live instance before they apply
def fanOutServersForCluster(serverTemplate, parentObject, clusterName):
  #clear current children
  children = JSONArray()
  parentObject.put('children', children) #: :type children: JSONArray
  #create servers for each expected server to be imported
  roleProperties = serverTemplate.getJSONObject('roleProperties') #: :type roleProperties: JSONObject
  serverTemplateString = serverTemplate.toString()
  #hardcoded until we find another solution
  overlayEndpointNames = ["CSIV2_SSL_MUTUALAUTH_LISTENER_ADDRESS", "CSIV2_SSL_SERVERAUTH_LISTENER_ADDRESS", "ORB_LISTENER_ADDRESS", "SAS_SSL_SERVERAUTH_LISTENER_ADDRESS", "SOAP_CONNECTOR_ADDRESS"]
  origServerName = roleProperties.getString('websphere.server')
  nodes = roleProperties.optString("websphere.server.nodelist", None)
  serverCount = roleProperties.optString("websphere.server.servercount", "1")
  serverNameFormats = roleProperties.optString('websphere.server.servernameformats', None)
  endPointPortMappings = roleProperties.optString('websphere.server.endpointportmappings', None)
  starttokendelimiter = roleProperties.optString("websphere.server.starttokendelimiter", "@");
  endtokendelimiter = roleProperties.optString("websphere.server.endtokendelimiter", "@");
  serverType = roleProperties.optString('websphere.server.servertype', None)
  nodenamelist = Util.getNodeNameList(nodes)
  nameformats = Util.getServerNameFormats(serverNameFormats)
  cell = Util.getCell()
  clustersGroupObj = createRoledResourceJSONObject("Cluster Members", "/%(cluster)s/Cluster Members" % {'cluster':clusterName}, "Discovered Cluster Members")
  #hardcoded because we don't have a way for the user to alter this yet
  clustermemberweight = 2

  #we need these variables in order to continue. stop the process if we are missing something and alert the user
  if not nodes or not serverCount or not serverNameFormats or not endPointPortMappings:
      raise Exception("Some properties are missing values. When you use the multiplicity feature, you must set values for all of the following properties, either in the Cluster Config component or in a custom component that references the properties. The following properties were found.\nwebsphere.server.nodelist=%(nodes)s\nwebsphere.server.servercount=%(serverCount)s\nwebsphere.server.serverNameFormats=%(serverNameFormats)s\nwebsphere.server.endpointportmappings=%(endPointPortMappings)s" % {'nodes':nodes, 'serverCount':serverCount, 'serverNameFormats':serverNameFormats, 'endPointPortMappings':endPointPortMappings})

  #loop through nodes in nodename list
  for nodename in nodenamelist:
    nodeconpath = "/Cell:" + Util.getCell() + "/Node:" + nodename + "/"
    nodeid = Util.getid(nodeconpath)
    hostname = Util.getRequiredAttribute(nodeid, "hostName", "Node")
    nodename = nodename.strip()
    #default value
    nameformat = "%c-%n-%i"
    if nameformats.has_key('*'):
      nameformat = nameformats['*']
    if nameformats.has_key(nodename):
      nameformat = nameformats[nodename]

    #loop through server count and create a json to ghost each server
    for i in range(1, int(serverCount) + 1):
      servername = nameformat.replace("%c", clusterName)
      servername = servername.replace("%n", nodename)
      servername = servername.replace("%i", i.toString())
      Log.debug("Adding %(new)s server from %(orig)s template for %(clus)s" % {'orig':origServerName, 'new':servername, 'clus':clusterName})
      newjsonstring = serverTemplateString.replace(origServerName, servername)
      newjsonstring = newjsonstring.replace(starttokendelimiter + "websphere.node" + endtokendelimiter, nodename)
      newjsonstring = newjsonstring.replace(starttokendelimiter + "websphere.node.hostname" + endtokendelimiter, hostname)
      newjsonobject = JSONObject(newjsonstring)
      serverRoleProperties = newjsonobject.getJSONObject("roleProperties")

      #remove properties to hide the left side of the comparison
      serverRoleProperties.remove("websphere.server.nodelist")
      serverRoleProperties.remove("websphere.server.servercount")
      serverRoleProperties.remove("websphere.server.servernameformats")
      serverRoleProperties.remove("websphere.server.endpointportmappings")

      #move ssl config groups to correct locations
      sslConfigGroupResult = findJSONObjectFromChildren(newjsonobject, "SSL Config Groups")
      if sslConfigGroupResult:
        sslConfigGroupsJSON = JSONObject(sslConfigGroupResult['json'])
        #remove incorrect ssl config group from master json
        newjsonobject.put("children", removeAtIndexFromJSONArray(newjsonobject.optJSONArray("children"), sslConfigGroupResult['index']))
        #correct ssl config groups paths then put them back in newjsonobject
        prefixPath = "/%(clusterName)s/Servers/%(servername)s/Server Entries/ServerEntry/Named End Points" % {'clusterName':clusterName, 'servername':servername}
        adminHostSecureJSON = alterPathOfJSONChildren(sslConfigGroupsJSON, prefixPath, "WC_adminhost_secure", "/SSL Config Groups")
        defaultHostSecureJSON = alterPathOfJSONChildren(sslConfigGroupsJSON, prefixPath, "WC_defaulthost_secure", "/SSL Config Groups")
        newjsonobject.optJSONArray("children").put(adminHostSecureJSON)
        newjsonobject.optJSONArray("children").put(defaultHostSecureJSON)
      children.put(newjsonobject)

      #setup cluster members json
      clustermembername = "%(nodename)s-%(servername)s" % {'nodename':nodename, 'servername':servername}
      clustermemberpath = "/%(cluster)s/Cluster Members/%(clustermembername)s" % {'cluster':clusterName, 'clustermembername':clustermembername}
      clustermemberrolename = "WebSphereClusterMember"
      clustermemberroleproperties = {
        "websphere.clustermember.membername":servername,
        "websphere.clustermember.weight":clustermemberweight,
        "websphere.clustermember.nodename":nodename
      }
      clusterMemberJSON = createRoledResourceJSONObject(clustermembername, clustermemberpath, clustermemberrolename, clustermemberroleproperties)
      children.put(clusterMemberJSON)

      #server entry json
      serverEntryName = "ServerEntry"
      serverEntryPath = "/%(cluster)s/Servers/%(servername)s/Server Entries/ServerEntry" % {'cluster':clusterName, 'servername':servername}
      serverEntryRoleName = "WebSphereServerEntry"
      serverEntryRoleProperties = {
          "websphere.serverentry.servername": servername,
          "websphere.serverentry.deployedapplications": "commsvc.ear/deployments/commsvc\nibmasyncrsp.ear/deployments/ibmasyncrsp",
          "websphere.serverentry.servertype": serverType
      }
      serverEntryJSON = createRoledResourceJSONObject(serverEntryName, serverEntryPath, serverEntryRoleName, serverEntryRoleProperties)
      children.put(serverEntryJSON)

      #endpoint port mappings json
      endpointList = Util.getEndPointMappings(endPointPortMappings)
      for endPointPortMap in endpointList:
        endpointName = endPointPortMap[0]
        endpointHost = endPointPortMap[1]
        #because unique ports need to be assigned to each server
        endpointPort = str(int(endPointPortMap[2]) + (i - 1))
        if endpointHost == ".":
          if cell == None or "":
            endpointHost = "No Cell Detected!"
          else:
            endpointHost = hostname

        #endpoint parent json
        endPointPath = serverEntryPath + "/Named End Points/%s" % endpointName
        endPointRoleName = "WebSphereNamedEndPoint"
        endPointRoleProperties = {
          "websphere.namedendpoint.endpointname": endpointName
        }
        endPointParentJSON = createRoledResourceJSONObject(endpointName, endPointPath, endPointRoleName, endPointRoleProperties)
        children.put(endPointParentJSON)

        #data bit at the end to hold hostnames and ports json
        if endpointName in overlayEndpointNames:
          endPointDataPath = endPointPath + "/Overlay Endpoints/OverlayEndpoint"
          endPointDataName = "OverlayEndpoint"
          endPointDataRoleName = "WebSphereOverlayEndpoint"
          endPointDataHostKey = "websphere.overlayendpoint.host"
          endPointDataPortKey = "websphere.overlayendpoint.port"
        else:
          endPointDataPath = endPointPath + "/End Points/EndPoint"
          endPointDataName = "EndPoint"
          endPointDataRoleName = "WebSphereEndPoint"
          endPointDataHostKey = "websphere.endpoint.host"
          endPointDataPortKey = "websphere.endpoint.port"

        endPointDataRoleProperties = {
          endPointDataPortKey: endpointPort,
          endPointDataHostKey: endpointHost
        }
        endPointDataJSON = createRoledResourceJSONObject(endPointDataName, endPointDataPath, endPointDataRoleName, endPointDataRoleProperties)
        children.put(endPointDataJSON)

        #name server endpoint json
        if endpointName == "BOOTSTRAP_ADDRESS":
          genericEndPointDataRoleProperties = {
            "websphere.endpoint.host":endpointHost,
            "websphere.endpoint.port":endpointPort
          }
          nameServerEndpointPath = "/%(cluster)s/Servers/%(servername)s/Name Servers/NameServer0/End Points/BOOTSTRAP_ADDRESS"  % {'cluster':clusterName, 'servername':servername}
          nameServerEndpointJSON = createRoledResourceJSONObject(endpointName, nameServerEndpointPath, "WebSphereEndPoint", genericEndPointDataRoleProperties)
          children.put(nameServerEndpointJSON)

        #object request endpoint json
        if endpointName in overlayEndpointNames and endpointName != "SOAP_CONNECTOR_ADDRESS":
          genericEndPointDataRoleProperties = {
            "websphere.endpoint.host":endpointHost,
            "websphere.endpoint.port":endpointPort
          }
          objectRequestEndPointPath = "/%(cluster)s/Servers/%(servername)s/Services/ObjectRequestBroker0/End Points/%(endpointname)s"  % {'cluster':clusterName, 'servername':servername, 'endpointname':endpointName}
          objectRequestEndPointJSON = createRoledResourceJSONObject(endpointName, objectRequestEndPointPath, "WebSphereEndPoint", genericEndPointDataRoleProperties)
          children.put(objectRequestEndPointJSON)

def exportOriginalFullRepresentation(fullResourceJSONObject, logDir):
  '''
  :type fullResourceJSONObject: JSONObject
  :type logDir: File'''
  #we need to find all servertemplates and create the expected number of servers in the server folder of clusters
  serverTemplateDict = getAllServerTemplatesAndParents(fullResourceJSONObject);
  for serverTemplate in serverTemplateDict.keys():#: :type serverTemplate: JSONObject
    parentObject = serverTemplateDict[serverTemplate][0]; #: :type clusterObject: JSONObject
    clusterName = serverTemplateDict[serverTemplate][1];
    fanOutServersForCluster(serverTemplate, parentObject, clusterName)

  fullResource = jsonObjectToArrayString(fullResourceJSONObject)

  # remove attributes that are never updated so live compare does not display them as differences
  fullResourceJSONArray = jsonObjectToJSONArray(fullResourceJSONObject);
  fullResource = Util.removeAttributesToNotUpdate(fullResourceJSONArray);
  # remove any object types we wish to ignore during compare
  fullResource = Util.removeObjectTypeFromJson(fullResource);

  startJSONFile = codecs.open("%s/ibm-ucd-resources.json" % logDir.getAbsolutePath(), 'w', "utf-8");
  startJSONFile.write(fullResource.toString(4));
  startJSONFile.close();

def doExport(applicableTypes, resourceJSONObject):
  roleName = None
  containmentpath = "/Cell:" + Util.getCell()
  if resourceJSONObject is not None:
    roleName = resourceJSONObject.getString("roleName")
    roleName = Util.roleNameToType(roleName)
    containmentpath = Util.findContainmentPath(resourceJSONObject)
  else:
    roleName = "Cell"
  topObjectDir = Util.findObjectDir(roleName, objectsDir)
  finalResources = JSONArray()
  respath = "/"
  index = containmentpath.rindex('/', 0, len(containmentpath) - 2) + 1
  parentcontainmentpath = containmentpath[0:index]
  if parentcontainmentpath == "/":
    parentcontainmentpath = ""
  Log.debug("Initial Containment path %s" % containmentpath)
  if containmentpath != "":
    objid = Util.getid(containmentpath)
  else:
    objid = Util.getid("/Cell:/")
  Log.debug("Fin Containment path %s" % containmentpath)
  Log.debug("Parent Containment path %s" % parentcontainmentpath)
  Log.debug(roleName)
  Log.debug(respath)
  Log.debug(objid)
  typePaths = getAllTypePaths(resourceJSONObject, applicableTypes)
  Log.debug("TypePaths: %s" % typePaths);
  curresources = None
  if objid:
    curresources = Util.exportObjectToResources(stack, objectsDir, topObjectDir, objid, respath, parentcontainmentpath, roleName, "None", typePaths)
  if curresources is not None:
    for y in range(curresources.length()):
      finalResources.put(curresources.get(y))

  finalResources = Util.filterExportForTypes(finalResources, applicableTypes, configurationTypes)
  return finalResources

def createRoledResourceJSONObject(name, path, roleName, propsMap = {}):
  obj = JSONObject();
  obj.put("name", name);
  obj.put("path", path);
  obj.put("roleName", roleName);
  roleProperties = JSONObject();
  obj.put("roleProperties", roleProperties);
  for key in propsMap.keys():
    roleProperties.put(key, propsMap[key]);
  return obj;

#recursively loop through children of parent json and change paths to new path
#this is done by using targetPathPhrase to mark a middle point
#then attaching prefixPath + hostType + (everything after targetPathPhrase)
#and setting this as the new path
def alterPathOfJSONChildren(parentJSON, prefixPath, hostType, targetPathPhrase):
  #need to make true copies to avoid pointers to original JSON
  copyParentJSON = JSONObject(parentJSON.toString())
  oldPath = copyParentJSON.optString("path")
  #change parent's path according to host type
  formatHostType = "/%s" % hostType
  newPath = replacePath(oldPath, prefixPath, formatHostType, targetPathPhrase)
  copyParentJSON.put("path", newPath)

  #recurse through the children now that the parent's path is fixed
  childrenOfJSON = copyParentJSON.optJSONArray("children")
  if childrenOfJSON.length() <= 0:
    #recursion terminator, straddle that stack and ride it back down
    return copyParentJSON
  else:
    #loop through all of its children and change their paths recursively
    newChildren = JSONArray()
    for i in range(0, childrenOfJSON.length()):
      child = childrenOfJSON.get(i)
      if child.optString("path").find(hostType) >= 0:
        alteredChild = alterPathOfJSONChildren(child, prefixPath, hostType, targetPathPhrase)
        newChildren.put(alteredChild)
    copyParentJSON.put("children", newChildren)
    return copyParentJSON

def findJSONObjectFromChildren(rootJSON, nameToSearchFor):
    rootChildren = rootJSON.optJSONArray("children")
    for i in range(0, int(rootChildren.length())):
      rootChild = rootChildren.get(i)
      if rootChild.optString("name") == nameToSearchFor:
        return {'json':rootChild.toString(), 'index':i}
    #did not find it
    return None

def replacePath(oldPath, prefixPath, hostType, targetPathPhrase):
  pathCapIndex = oldPath.find(targetPathPhrase)
  if pathCapIndex >= 0:
    newPath = "%(prefixPath)s%(hostType)s%(cap)s" % {'prefixPath':prefixPath, 'hostType':hostType, 'cap':oldPath[pathCapIndex:]}
    return newPath
  else:
    return oldPath

def removeAtIndexFromJSONArray(jsonArrayInput, removeAtIndex):
  resultJSONArray = JSONArray()
  for i in range(0, int(jsonArrayInput.length())):
    if i != removeAtIndex:
      copyJSONObject = JSONObject(jsonArrayInput.get(i).toString())
      resultJSONArray.put(copyJSONObject)
  return resultJSONArray

def mainmethod():
  pihelper = PluginInputHelper.PluginInputHelper(sys.argv[0]);
  inputProps = pihelper.getInputProperties();

  logDir = pihelper.getLogDirectory();
  logFile = open("%s/debugLog.txt" % logDir.getAbsolutePath(),'w');
  redoFile = open("%s/redoLog.txt" % logDir.getAbsolutePath(),'w');
  Log.setLogLevel(inputProps.getProperty("logLevel"));
  Log.setLogFile(logFile);
  Log.setRedoFile(redoFile);
  adminConfigTypes = Util.getAdminConfigTypes();

  configurationTypesInput = Util.formatConfigurationTypes(inputProps.getProperty("configurationTypes"));
  if configurationTypesInput is not None and len(configurationTypesInput) > 0:
    for line in configurationTypesInput.splitlines():
      line = line.strip();
      if len(line) > 0:
        if line in adminConfigTypes:
          configurationTypes[line] = line;

  Util.setConfigurationTypes(configurationTypes);
  applicableTypes = Util.getApplicableTypes("%s/plugin.xml" % PLUGIN_HOME);

  resourceJSONObject = pihelper.getFullResourceConfiguration();
  serverTemplateToParentMap = getAllServerTemplatesAndParents(resourceJSONObject);

  finalResources = doExport(applicableTypes, resourceJSONObject)
  exportOriginalFullRepresentation(resourceJSONObject, logDir);

  if len(serverTemplateToParentMap) > 0:
    #we need to clone this so we can do stuff...
    for x in serverTemplateToParentMap.keys():
      serverTemplate = x;#: :type serverTemplate: JSONObject
      parentpath = serverTemplateToParentMap[serverTemplate][0].getString("path");
      clustername = serverTemplateToParentMap[serverTemplate][1];
      clustermembers = Util.getid("/ServerCluster:%s/ClusterMember:/" % clustername);
      for clustermember in clustermembers.splitlines():
        nodename = Util.getRequiredAttribute(clustermember, 'nodeName', "ClusterMember");
        servername = Util.getRequiredAttribute(clustermember, 'memberName', "ClusterMember");
        #we need to generate a parent element on this server so the containment path for this server can be correctly computed
        nodepropsmap = {'websphere.node': nodename};
        parentJSONObject = createRoledResourceJSONObject(nodename, "/foo", "WebSphereNode", nodepropsmap);
        serverTemplate.put("name", servername);
        serverTemplate.getJSONObject("roleProperties").put("websphere.server", servername);
        serverTemplate.put("parent", parentJSONObject);
        grandparentJSONObject = createRoledResourceJSONObject(Util.getCell(), "/", "WebSphereCell", {'websphere.cell': Util.getCell()});
        parentJSONObject.put("parent", grandparentJSONObject);
        serverExportedResources = doExport(applicableTypes, serverTemplate); #: :type serverExportedResources: JSONArray
        #update the paths
        for ind in range(serverExportedResources.length()):
          curobj = serverExportedResources.getJSONObject(ind); #: :type curobj: JSONObject
          curpath = curobj.getString("path");
          curobj.put("path", "%(par)s%(cur)s" % {'par':parentpath, 'cur':curpath});
          finalResources.put(curobj);

  logFile.close();
  redoFile.close();

  # remove attributes that are never updated so live compare does not display them as differences
  finalResources = Util.removeAttributesToNotUpdate(finalResources)
  # remove any object types we wish to ignore during compare
  finalResources = Util.removeObjectTypeFromJson(finalResources);

  compareJSONFile = codecs.open("%s/ibm-ucd-resources.json.compare" % logDir.getAbsolutePath(), 'w', "utf-8");
  compareJSONFile.write(finalResources.toString(4));
  compareJSONFile.close();

  print "\nConfiguration Discovery Complete.";

def getAllTypePaths(resourceJSONObject, applicableTypes):
  return Util.getHandleTypePathsFromJSON(resourceJSONObject, applicableTypes);

PLUGIN_HOME = os.environ['PLUGIN_HOME'];
objectsDir = "%(plugHome)s%(fileSep)sobjects" % { 'plugHome': PLUGIN_HOME, 'fileSep': os.sep };
sys.path.insert(0,objectsDir);
Util = __import__("utilities.Util").Util;
Log = __import__("WASConfLog.Log").Log;
PluginInputHelper = __import__("PluginInputHelper.PluginInputHelper").PluginInputHelper;

#we put everything possible in a method due to python global versus function scoping.
#repeatedly had issues with name collisions causing refactoring to pick up  a global variable with the same name as a local variable that was removed
try:
  mainmethod();
except: #except on anything, then check if its a java or python exception
  #why in the did jython not handle this gracefully....
  #print the stack here because websphere may decide to just swallow it
  actualExcept = sys.exc_info()[1];
  if isinstance(actualExcept, Throwable):
    actualExcept.printStackTrace();
  traceback.print_exc();
  raise actualExcept;
