package gw.internal.xml.ws.server;

import com.guidewire.commons.metadata.MetadataDependencies;
import com.guidewire.commons.metadata.i18n.config.GWLanguage;
import com.guidewire.commons.metadata.i18n.config.GWLocale;
import com.guidewire.pl.logging.PLLoggingMarker;
import com.guidewire.pl.logging.PLTraceabilityUtils;
import com.guidewire.pl.system.dependency.CommonDependencies;
import com.guidewire.pl.system.dependency.PLDependencies;
import com.guidewire.pl.system.exception.ConvertToSOAPException;
import com.guidewire.pl.system.integration.plugins.PluginConfig;
import com.guidewire.pl.system.integration.plugins.PluginException;
import com.guidewire.pl.system.internal.InternalMethods;
import com.guidewire.pl.system.server.InitTab;
import com.guidewire.pl.system.server.Version;
import com.guidewire.pl.system.service.context.ServiceToken;
import com.guidewire.pl.system.service.context.ServiceTokenManager;
import com.guidewire.pl.system.servlet.ServletUtilities;
import com.guidewire.pl.system.transaction.CommitOptions;
import com.guidewire.pl.system.transaction.ConnectionHandlerFactory;
import com.guidewire.pl.web.controller.WebServlet;
import entity.User;
import gw.api.exception.ErrorInfo;
import gw.api.exception.HasErrorInfo;
import gw.api.intentionallogging.IntentionalLogger;
import gw.api.profiler.WebServiceProfiler;
import gw.api.server.AvailabilityLevel;
import gw.api.system.PLConfigParameters;
import gw.api.system.ParameterKey;
import gw.api.system.server.Runlevel;
import gw.api.util.ConfigAccess;
import gw.api.webservice.exception.SOAPException;
import gw.api.xsd.XSDPublishHelper;
import gw.fs.IFile;
import gw.internal.xml.XmlElementTypeDataImpl;
import gw.internal.xml.module.XmlModule;
import gw.internal.xml.ws.server.PLWebservicesServlet.1;
import gw.internal.xml.ws.server.PLWebservicesServlet.2;
import gw.internal.xml.ws.server.PLWebservicesServlet.3;
import gw.internal.xml.ws.server.PLWebservicesServlet.4;
import gw.internal.xml.ws.server.PLWebservicesServlet.5;
import gw.internal.xml.ws.server.PLWebservicesServlet.6;
import gw.lang.reflect.IAnnotationInfo;
import gw.lang.reflect.IMethodInfo;
import gw.lang.reflect.IType;
import gw.lang.reflect.ReflectUtil;
import gw.lang.reflect.TypeSystem;
import gw.pl.logging.ElapsedTime;
import gw.pl.logging.LogMessageParams;
import gw.plugin.security.WebservicesAuthenticationContext;
import gw.plugin.security.WebservicesAuthenticationPlugin;
import gw.util.concurrent.LockingLazyVar;
import gw.xml.XmlElement;
import gw.xml.ws.WebServiceException;
import gw.xml.ws.WsdlFault;
import gw.xml.ws.WsiAuthenticationException;
import gw.xml.ws.WsiRequestLocal;
import gw.xml.ws.WsdlFault.FaultCode;
import gw.xml.ws.annotation.ExternalTransactionIdRequiredException;
import gw.xml.ws.annotation.IWsiCheckDuplicateExternalTransaction;
import gw.xsd.guidewire.soapheaders.Authentication;
import gw.xsd.guidewire.soapheaders.GwLanguage;
import gw.xsd.guidewire.soapheaders.GwLocale;
import gw.xsd.guidewire.soapheaders.Locale;
import gw.xsd.guidewire.soapheaders.TraceabilityId;
import gw.xsd.guidewire.soapheaders.TransactionId;
import gw.xsd.gw.gw_pl_wsdl_additions.Address;
import gw.xsd.w3c.soap12.Binding;
import gw.xsd.w3c.soap12.Header;
import gw.xsd.w3c.soap12.enums.UseChoice;
import gw.xsd.w3c.wsdl.anonymous.elements.TBindingOperation_Input;
import gw.xsd.w3c.wsdl.anonymous.elements.TBinding_Operation;
import gw.xsd.w3c.wsdl.anonymous.elements.TDefinitions_Binding;
import gw.xsd.w3c.wsdl.anonymous.elements.TDefinitions_Message;
import gw.xsd.w3c.wsdl.anonymous.elements.TDefinitions_Service;
import gw.xsd.w3c.wsdl.anonymous.elements.TMessage_Part;
import gw.xsd.w3c.wsdl.anonymous.elements.TService_Port;
import gw.xsd.w3c.xmlschema.Import;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.Callable;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.xml.namespace.QName;
import typekey.SystemPermissionType;

public class PLWebservicesServlet extends WebservicesServletBase {
   private static final WsiRequestLocal<WebServiceProfiler> WSI_REQUEST_PROFILER = new WsiRequestLocal();
   public static final WsiRequestLocal<HttpServletRequest> WSI_HTTP_REQUEST = new WsiRequestLocal();
   public static final int TIMEOUT_60_SECS = 60000;
   public static final String WSDL_MSG_AUTH_HDR = "gw-authentication";
   public static final String WSDL_MSG_LOCALE_HDR = "gw-locale-only";
   public static final String WSDL_MSG_LANG_HDR = "gw-language-only";
   public static final String WSDL_MSG_TRACE_HDR = "gw-traceability-id";
   public static final IntentionalLogger IL = IntentionalLogger.from(getILogger());
   private boolean _generatingWsiLocal;
   final LockingLazyVar<IType> PermAnnoType = new 1(this);
   final LockingLazyVar<IType> TransAnnoType = new 2(this);
   final LockingLazyVar<IType> PermissionAnnoType = new 3(this);
   final LockingLazyVar<IType> AvailAnnoType = new 4(this);
   final LockingLazyVar<IType> ReduceDBAnnoType = new 5(this);
   final LockingLazyVar<XmlModule> GlobalXmlModule = new 6(this);
   public static final String PERF_TIMING_REF = "com.guidewire.commons.performance.gmeter.PerfTiming";
   private Boolean _perfTimingExistsBool;
   ThreadLocal<Long> _timeBefore = new ThreadLocal<>();
   ThreadLocal<String> _serviceName = new ThreadLocal<>();
   ThreadLocal<String> _opName = new ThreadLocal<>();

   public PLWebservicesServlet() {
   }

   public PLWebservicesServlet(boolean wsiLocal) {
      super(wsiLocal);
   }

   public void init(ServletConfig config) throws ServletException {
      ServletUtilities.initServletEnvironment(config.getServletContext());
      super.init(config);
   }

   protected String getStringValue(ServletConfig config, final String name, final String defaultValue) {
      ParameterKey<String> parameterKey = (ParameterKey<String>)PLConfigParameters.getKeyMap().get("Webservices" + name);
      if (parameterKey != null) {
         String value = (String)parameterKey.getValue();
         if (value != null && value.length() > 0) {
            return value;
         }
      }

      return super.getStringValue(config, name, defaultValue);
   }

   protected boolean getBooleanValue(ServletConfig config, final String name, final boolean defaultValue) {
      ParameterKey<Boolean> parameterKey = (ParameterKey<Boolean>)PLConfigParameters.getKeyMap().get("Webservices" + name);
      if (parameterKey != null) {
         Boolean value = (Boolean)parameterKey.getValue();
         if (value != null) {
            return value;
         }
      }

      return super.getBooleanValue(config, name, defaultValue);
   }

   protected void initAvailable() {
      this.onRefreshTypeSystem();
   }

   protected void printIndexBody(DftreeNode node, String reqPath) throws IOException {
      this.printIndexWebServices(node, reqPath);
      Set<String> xsds = new TreeSet<>();
      Set<String> wsdls = new TreeSet<>();
      this.extractXmlResourcesForIndex(xsds, wsdls);
      this.printIndexSchemas(node, reqPath, xsds);
      this.printIndexGeneratedSchemas(node, reqPath);
      this.printIndexWSDLs(node, reqPath, wsdls);
   }

   protected void doGetXSD(WebservicesResponseAdapter responseAdapter, String path) throws ServletException {
      try {
         if (XSDPublishHelper.getAdditionalPaths().contains(path)) {
            responseAdapter.setContentType("text/xml");
            responseAdapter.setStatus(200);
            XSDPublishHelper.outputSchema(path, responseAdapter.getOutputStream());
         } else {
            super.doGetXSD(responseAdapter, path);
         }
      } catch (IOException ex) {
         throw new ServletException(ex);
      }
   }

   public String postConfigureWebservicePath(IType type, String xsdRootPath) {
      return this._generatingWsiLocal ? xsdRootPath + type.getNamespace().replace('.', '/') + "/" : "";
   }

   public void postConfigureWebservice(IType type, WsiServiceInfo serviceInfo) {
      XmlElementTypeDataImpl typeData = (XmlElementTypeDataImpl)Authentication.asTypeData();
      serviceInfo.getSchemas().add(typeData.getSchemaIndex());
      StringBuilder pathToSoapHeadersSchema = new StringBuilder();
      if (this._generatingWsiLocal) {
         pathToSoapHeadersSchema.append("../../");
      }

      for (int i = 0; i < this.countDots(type); i++) {
         pathToSoapHeadersSchema.append("../");
      }

      pathToSoapHeadersSchema.append("gw/xsd/guidewire/soapheaders.xsd");
      Import imprt = new Import();
      imprt.setSchemaLocation(pathToSoapHeadersSchema.toString());
      imprt.setNamespace(Authentication.$QNAME.getNamespaceURI());
      serviceInfo.getSchema().get$Children().add(0, imprt);
      String wsdlTns = serviceInfo.getWsdl().getTargetNamespace();
      if (!this._generatingWsiLocal) {
         String gwAddress = "${" + Version.getAppCode() + "}/ws/" + type.getName().replace('.', '/');

         for (TDefinitions_Service service : serviceInfo.getWsdl().getService()) {
            for (TService_Port port : service.getPort()) {
               Address gwAddressElement = new Address();
               if (port.getName().endsWith("Soap12Port")) {
                  gwAddressElement.setLocation(gwAddress);
               } else {
                  if (!port.getName().endsWith("Soap11Port")) {
                     throw new IllegalStateException("Unrecognized port name: " + port.getName());
                  }

                  gwAddressElement.setLocation(gwAddress + "/soap11");
               }

               port.addChild(gwAddressElement);
            }
         }
      }

      serviceInfo.getWsdl().declareNamespace(Authentication.$QNAME);
      this.addGwSoapHeader(serviceInfo, "gw-authentication", Authentication.$QNAME);
      this.addGwSoapHeader(serviceInfo, "gw-locale-only", GwLocale.$QNAME);
      this.addGwSoapHeader(serviceInfo, "gw-language-only", GwLanguage.$QNAME);
      this.addGwSoapHeader(serviceInfo, "gw-transaction-id", TransactionId.$QNAME);
      this.addGwSoapHeader(serviceInfo, "gw-traceability-id", TraceabilityId.$QNAME);

      for (TDefinitions_Binding binding : serviceInfo.getWsdl().getBinding()) {
         if (binding.getChild(Binding.$QNAME) != null) {
            for (TBinding_Operation operation : binding.getOperation()) {
               TBindingOperation_Input input = operation.getInput();
               input.addChild(this.create12Header(wsdlTns, "gw-authentication"));
               input.addChild(this.create12Header(wsdlTns, "gw-language-only"));
               input.addChild(this.create12Header(wsdlTns, "gw-locale-only"));
               input.addChild(this.create12Header(wsdlTns, "gw-traceability-id"));
            }
         }

         if (binding.getChild(gw.xsd.w3c.soap11.Binding.$QNAME) != null) {
            for (TBinding_Operation operation : binding.getOperation()) {
               TBindingOperation_Input input = operation.getInput();
               input.addChild(this.create11Header(wsdlTns, "gw-authentication"));
               input.addChild(this.create11Header(wsdlTns, "gw-language-only"));
               input.addChild(this.create11Header(wsdlTns, "gw-locale-only"));
               input.addChild(this.create11Header(wsdlTns, "gw-traceability-id"));
            }
         }
      }
   }

   private Header create12Header(String wsdlTns, String localPart) {
      Header header = new Header();
      header.setMessage(new QName(wsdlTns, localPart));
      header.setPart(localPart);
      header.setUse(UseChoice.Literal);
      return header;
   }

   private gw.xsd.w3c.soap11.Header create11Header(String wsdlTns, String localPart) {
      gw.xsd.w3c.soap11.Header header = new gw.xsd.w3c.soap11.Header();
      header.setMessage(new QName(wsdlTns, localPart));
      header.setPart(localPart);
      header.setUse(gw.xsd.w3c.soap11.enums.UseChoice.Literal);
      return header;
   }

   private void addGwSoapHeader(WsiServiceInfo serviceInfo, String messageName, QName elementQName) {
      TDefinitions_Message message = new TDefinitions_Message();
      message.setName(messageName);
      TMessage_Part part = new TMessage_Part();
      part.setName(messageName);
      part.setElement(elementQName);
      message.getPart().add(part);
      serviceInfo.getWsdl().getMessage().add(message);
   }

   private int countDots(IType type) {
      int count = 0;
      String remaining = type.getName();

      while (true) {
         int idx = remaining.indexOf(46);
         if (idx < 0) {
            return count;
         }

         count++;
         remaining = remaining.substring(idx + 1);
      }
   }

   protected void printIndexGeneratedSchemas(DftreeNode node, String reqPath) throws IOException {
      DftreeNode category = new DftreeNode("Generated Schemas");
      node.addChild(category);

      for (String path : XSDPublishHelper.getAdditionalPaths()) {
         String url = reqPath + path;
         this.addDftreeChild(category, path, url);
      }
   }

   protected boolean initAvailable(String path) {
      File confFile = ConfigAccess.getConfigFile(path);
      if (confFile != null) {
         try {
            this.readAvailable(new FileInputStream(confFile));
            return true;
         } catch (IOException e) {
            throw new RuntimeException("initAvailable from path " + confFile, e);
         }
      } else {
         IFile resFile = TypeSystem.getGlobalModule().getFileRepository().findFirstFile(path);
         if (resFile != null) {
            try {
               this.readAvailable(resFile.openInputStream());
               return true;
            } catch (IOException e) {
               throw new RuntimeException("initAvailable from module " + resFile, e);
            }
         } else {
            return super.initAvailable(path);
         }
      }
   }

   public boolean perfTimingExists() {
      if (this._perfTimingExistsBool == null) {
         this._perfTimingExistsBool = TypeSystem.getByFullNameIfValid("com.guidewire.commons.performance.gmeter.PerfTiming") != null;
      }

      return this._perfTimingExistsBool;
   }

   protected final void beforeInvoke(WebservicesRequest request, WebservicesResponseAdapter responseAdapter) {
      super.beforeInvoke(request, responseAdapter);
      WebServlet.getIdleLock().lockRead();
      Runlevel target = InitTab.getStartingRunlevel();
      if (Runlevel.SHUTDOWN.isEarlier(target)) {
         target = Runlevel.SHUTDOWN;
      }

      if (!this.isWsiLocal() && InitTab.getInstance().getRunLevel().isEarlier(target)) {
         InitTab.getInstance().waitForRunLevel(target, 0L);
      }

      if (this.perfTimingExists()) {
         this._timeBefore.set(System.currentTimeMillis());
      }
   }

   protected final void afterInvoke(WebservicesRequest request, WebservicesResponseAdapter responseAdapter) {
      WebServlet.getIdleLock().unlockRead();
      WebServiceProfiler profiler = (WebServiceProfiler)WSI_REQUEST_PROFILER.get();
      super.afterInvoke(request, responseAdapter);
      if (profiler != null) {
         profiler.maybeExitProfiling();
      }

      if (this.perfTimingExists()) {
         long timeAfter = System.currentTimeMillis();
         ReflectUtil.invokeStaticMethod(
            "com.guidewire.commons.performance.gmeter.PerfTiming",
            "roundTrip",
            new Object[]{-1, (int)(timeAfter - this._timeBefore.get()), -1, this._serviceName.get(), this._opName.get(), Collections.emptyList()}
         );
      }

      PLTraceabilityUtils.removeTraceabilityID();
   }

   protected WsdlFault getWsdlFault(Throwable throwable, WsiServiceInfo serviceInfo) {
      WsdlFault wsdlFault = null;
      if (throwable instanceof WsdlFault) {
         wsdlFault = (WsdlFault)throwable;
      } else {
         FaultCode faultCode = null;
         String faultSubCode = null;
         String message = null;
         IType declaredFaultType = this.getExceptionTypeExpected(serviceInfo.getThrownExceptions(), TypeSystem.getTypeFromObject(throwable));
         if (declaredFaultType != null) {
            message = throwable.getMessage();
         }

         Throwable work = (Throwable)(throwable instanceof ConvertToSOAPException ? ((ConvertToSOAPException)throwable).convertToSOAPException() : throwable);
         if (work instanceof SOAPException) {
            if (declaredFaultType == null) {
               declaredFaultType = this.getExceptionTypeExpected(serviceInfo.getThrownExceptions(), TypeSystem.getTypeFromObject(work));
            }

            faultCode = ((SOAPException)work).getFaultCode();
            faultSubCode = ((SOAPException)work).getErrorCode();
         } else if (work instanceof HasErrorInfo) {
            ErrorInfo ei = ((HasErrorInfo)work).getErrorInfo();
            faultCode = ei.getFaultCode();
            faultSubCode = ei.getErrorCode();
            message = ei.getMessage();
         }

         if (faultCode == null) {
            if (declaredFaultType == null && (!(work instanceof WebServiceException) || !((WebServiceException)work).isSenderFault())) {
               faultCode = FaultCode.Receiver;
            } else {
               faultCode = FaultCode.Sender;
            }
         }

         if (declaredFaultType == null) {
            getILogger().warn("Unexpected exception: ", throwable);
         }

         if (message == null) {
            if (declaredFaultType == null && !(work instanceof WebServiceException)) {
               message = work.toString();
            } else {
               message = work.getMessage() == null ? "" : work.getMessage();
            }
         }

         XmlElement detailElement = null;
         if (declaredFaultType != null) {
            detailElement = new XmlElement(new QName(serviceInfo.getSchema().getTargetNamespace(), declaredFaultType.getRelativeName()));
         }

         wsdlFault = new WsdlFault(message, work);
         wsdlFault.setCode(faultCode);
         wsdlFault.setDetail(detailElement);
      }

      return wsdlFault;
   }

   public void preUnmarshal(
      IType type, XmlElement headersFromEnvelope, XmlElement body, IMethodInfo method, List<Callable> finallyList, WebservicesRequest request, QName opName
   ) throws IOException {
      if (this.perfTimingExists()) {
         this._opName.set(opName.getLocalPart());
         this._serviceName.set(type.getName());
      }

      getRequestLocalMapForCurrentThread().put(WSI_HTTP_REQUEST, null);
      HttpServletRequest httpServletRequest = request.getHttpServletRequest();
      if (httpServletRequest == null && (Boolean)PLConfigParameters.ScopedVariableSupportedForAllCapturedRequests.getValue()) {
         URI uri = null;

         try {
            uri = new URI(request.getRequestURL());
         } catch (Throwable t) {
            getILogger().debug("Local WS has invalid url: " + request.getRequestURL());
         }

         httpServletRequest = new LocalHttpServletRequest(uri);
      }

      WSI_HTTP_REQUEST.set(httpServletRequest);
      if (!InitTab.getInstance().getRunLevel().isEarlier(Runlevel.NODAEMONS)) {
         getRequestLocalMapForCurrentThread().put(WSI_REQUEST_PROFILER, null);
         WebServiceProfiler profiler = new WebServiceProfiler(
            type.getName(), method == null ? "document" : method.getDisplayName(), PLTraceabilityUtils.getTraceabilityID()
         );
         WSI_REQUEST_PROFILER.set(profiler);
         profiler.maybeEnterProfiling();
      }

      this.checkAvailability(type, method, opName);
      this.checkPermissions(type, method, finallyList, request, headersFromEnvelope);
      this.checkLocale(headersFromEnvelope, finallyList);
      this.handleReduceDBConnections(type, method, finallyList);
      this.handleExternalTransaction(type, method, headersFromEnvelope, finallyList);
   }

   private void checkLocale(XmlElement headersFromEnvelope, List<Callable> finallyList) {
      if (headersFromEnvelope != null) {
         GWLanguage gwlanguage = null;
         GWLocale gwlocale = null;
         XmlElement gw_language = headersFromEnvelope.getChild(GwLanguage.$QNAME);
         XmlElement gw_locale = headersFromEnvelope.getChild(GwLocale.$QNAME);
         XmlElement locale = headersFromEnvelope.getChild(Locale.$QNAME);
         if (gw_language == null && gw_locale == null) {
            if (locale != null && !locale.is$Nil()) {
               gwlanguage = GWLanguage.parseLocaleCode(locale.get$Text());
               if (gwlanguage == null) {
                  throw new WebServiceException("Locale " + locale.get$Text() + " not found");
               }

               gwlocale = GWLocale.parseLocaleCode(locale.get$Text());
            }
         } else {
            if (gw_language != null && !gw_language.is$Nil()) {
               gwlanguage = GWLanguage.parseLocaleCode(gw_language.get$Text());
               if (gwlanguage == null) {
                  throw new WebServiceException("Language " + gw_language.get$Text() + " not found");
               }
            }

            if (gw_locale != null && !gw_locale.is$Nil()) {
               gwlocale = GWLocale.parseLocaleCode(gw_locale.get$Text());
               if (gwlocale == null) {
                  throw new WebServiceException("Locale " + gw_locale.get$Text() + " not found");
               }
            }
         }

         if (gwlanguage != null) {
            finallyList.add(() -> {
               MetadataDependencies.getCurrentLocaleProvider().setCurrentLanguage(null);
               return null;
            });
            MetadataDependencies.getCurrentLocaleProvider().setCurrentLanguage(gwlanguage);
         }

         if (gwlocale != null) {
            finallyList.add(() -> {
               MetadataDependencies.getCurrentLocaleProvider().setCurrentLocale(null);
               return null;
            });
            MetadataDependencies.getCurrentLocaleProvider().setCurrentLocale(gwlocale);
         }
      }
   }

   private void checkPermissions(IType type, IMethodInfo method, final List<Callable> finallyList, WebservicesRequest request, XmlElement headersFromEnvelope) {
      SystemPermissionType[] requiredPermissions = new SystemPermissionType[]{SystemPermissionType.TC_SOAPADMIN};
      IAnnotationInfo permissionsAnnotation = method == null ? null : method.getAnnotation((IType)this.PermAnnoType.get());
      if (permissionsAnnotation == null) {
         permissionsAnnotation = type.getTypeInfo().getAnnotation((IType)this.PermissionAnnoType.get());
      }

      if (permissionsAnnotation != null) {
         Object instance = permissionsAnnotation.getInstance();
         requiredPermissions = (SystemPermissionType[])((IType)this.PermissionAnnoType.get())
            .getTypeInfo()
            .getProperty("RequiredPermissions")
            .getAccessor()
            .getValue(instance);
      }

      User user = null;
      boolean authenticationPluginEnabled = false;
      PluginConfig pluginConfig = PLDependencies.getPluginConfig();
      if (pluginConfig != null && pluginConfig.isPluginEnabled(WebservicesAuthenticationPlugin.class)) {
         try {
            WebservicesAuthenticationPlugin plugin = (WebservicesAuthenticationPlugin)pluginConfig.getPlugin(WebservicesAuthenticationPlugin.class);
            authenticationPluginEnabled = true;
            user = plugin.authenticate(new WebservicesAuthenticationContext(headersFromEnvelope, request.getHttpServletRequest(), request.getHttpHeaders()));
            if (user != null) {
               ServiceTokenManager stm = PLDependencies.getServiceTokenManager();
               CommonDependencies commonDependencies = PLDependencies.getCommonDependencies();
               ServiceToken token = stm.createAuthenticatedToken(user);
               finallyList.add(() -> {
                  commonDependencies.setServiceToken(null);
                  stm.removeToken(token);
                  return null;
               });
               commonDependencies.setServiceToken(token);
               stm.suspendTokenTimeout(token);
            }
         } catch (PluginException var15) {
         }
      }

      if (requiredPermissions.length > 0) {
         if (user == null) {
            if (!authenticationPluginEnabled) {
               throw new WsiAuthenticationException("This webservice call requires authentication, but no authentication service is available");
            }

            throw new WsiAuthenticationException("This webservice call requires authentication");
         }

         for (SystemPermissionType systemPermission : requiredPermissions) {
            if (!InternalMethods.asUserInternal(user).hasCustomPermission(null, systemPermission)) {
               throw new WsiAuthenticationException(
                  "User is missing \"" + systemPermission.getCode() + "\" permission, which is required for this webservice call"
               );
            }
         }
      }
   }

   private void checkAvailability(IType type, IMethodInfo method, QName opName) {
      AvailabilityLevel requiredAvailability = AvailabilityLevel.MAINTENANCE;
      IAnnotationInfo availabilityAnnotation = method == null ? null : method.getAnnotation((IType)this.AvailAnnoType.get());
      if (availabilityAnnotation == null) {
         availabilityAnnotation = type.getTypeInfo().getAnnotation((IType)this.AvailAnnoType.get());
      }

      if (availabilityAnnotation != null) {
         Object instance = availabilityAnnotation.getInstance();
         requiredAvailability = (AvailabilityLevel)((IType)this.AvailAnnoType.get()).getTypeInfo().getProperty("Availability").getAccessor().getValue(instance);
      }

      Runlevel requiredRunlevel = requiredAvailability.getRunlevel();
      Runlevel actualRunlevel = InitTab.getInstance().getRunLevel();
      if (actualRunlevel.isEarlier(requiredRunlevel)) {
         if (InitTab.getStartingRunlevel().isEarlier(requiredRunlevel)) {
            AvailabilityLevel availabilityLevel = AvailabilityLevel.getAvailabilityLevel(actualRunlevel);
            String availabilityLevelDescription = availabilityLevel == null ? "Unknown( " + actualRunlevel + " )" : availabilityLevel.toString();
            throw new WebServiceException(
               "Insufficient runlevel for operation " + opName + ". Required: " + requiredAvailability + ", Actual: " + availabilityLevelDescription
            );
         }

         try {
            InitTab.getInstance().waitForRunLevel(requiredRunlevel, 60000L);
         } catch (Throwable e) {
            throw new WebServiceException("On waiting for runlevel " + requiredAvailability + " on operation " + opName + ": " + e.getMessage());
         }
      }
   }

   private void handleReduceDBConnections(IType type, IMethodInfo method, List<Callable> finallyList) {
      boolean reduceDBConn = false;
      IAnnotationInfo reduceDBConnAnnotation = method == null ? null : method.getAnnotation((IType)this.ReduceDBAnnoType.get());
      if (reduceDBConnAnnotation == null) {
         reduceDBConnAnnotation = type.getTypeInfo().getAnnotation((IType)this.ReduceDBAnnoType.get());
      }

      if (reduceDBConnAnnotation != null) {
         Object instance = reduceDBConnAnnotation.getInstance();
         Boolean reduceDBConnValue = (Boolean)((IType)this.ReduceDBAnnoType.get())
            .getTypeInfo()
            .getProperty("ReduceDBConnections")
            .getAccessor()
            .getValue(instance);
         if (reduceDBConnValue != null) {
            reduceDBConn = reduceDBConnValue;
         }
      }

      if (reduceDBConn) {
         ConnectionHandlerFactory.reserve();
         finallyList.add(() -> {
            ConnectionHandlerFactory.release();
            return null;
         });
      }
   }

   private void handleExternalTransaction(IType type, IMethodInfo method, XmlElement headersFromEnvelope, List<Callable> finallyList) {
      IAnnotationInfo transAnno = method == null ? null : method.getAnnotation((IType)this.TransAnnoType.get());
      if (transAnno == null) {
         transAnno = type.getTypeInfo().getAnnotation((IType)this.TransAnnoType.get());
      }

      if (transAnno != null) {
         String transId = this.getTransactionId(headersFromEnvelope);
         if (transId != null) {
            CommitOptions commitOptions = new CommitOptions();
            commitOptions.setTransactionId(transId);
            CommitOptions.setThreadCommitOptions(commitOptions);
            finallyList.add(() -> {
               CommitOptions.clearThreadCommitOptions();
               return null;
            });
         } else if (((IWsiCheckDuplicateExternalTransaction)transAnno.getInstance()).getRequired()) {
            throw new ExternalTransactionIdRequiredException();
         }
      }
   }

   private String getTransactionId(XmlElement headersFromEnvelope) {
      if (headersFromEnvelope == null) {
         return null;
      }

      TransactionId transId = (TransactionId)headersFromEnvelope.getChild(TransactionId.$QNAME);
      return transId != null && !transId.is$Nil() ? transId.getValue() : null;
   }

   public void setGeneratingWsiLocal(boolean generatingWsiLocal) {
      this._generatingWsiLocal = generatingWsiLocal;
   }

   protected XmlModule getGlobalXmlModule() {
      return (XmlModule)this.GlobalXmlModule.get();
   }

   protected void logRequestProcessed(WebservicesRequest request, XmlElement requestElement, long timeMs, Throwable error) {
      getILogger()
         .info(
            PLLoggingMarker.WEB_SERVICE_OPERATION,
            "WS operation processed {}",
            this.asLogMessageParams(request, requestElement).put(ElapsedTime.ms(timeMs)).put("error", error)
         );
      if (error == null) {
         IL.logStop(PLLoggingMarker.WEB_SERVICE_OPERATION, "WS operation processed", ElapsedTime.ms(timeMs), this.asLogMessageParams(request, requestElement));
      } else {
         IL.logError(PLLoggingMarker.WEB_SERVICE_OPERATION, error.getMessage(), error, this.asLogMessageParams(request, requestElement));
         IL.logFail(PLLoggingMarker.WEB_SERVICE_OPERATION, error.getMessage(), ElapsedTime.ms(timeMs), this.asLogMessageParams(request, requestElement));
      }
   }

   protected void logIncomingRequestStarted(WebservicesRequest request, XmlElement requestElement) {
      IL.logStart(PLLoggingMarker.WEB_SERVICE_OPERATION, "WS operation processing started", this.asLogMessageParams(request, requestElement));
   }

   private LogMessageParams asLogMessageParams(WebservicesRequest request, XmlElement requestElement) {
      return LogMessageParams.create()
         .put("name", requestElement == null ? null : requestElement.get$QName())
         .put("from", Optional.ofNullable(request.getHttpServletRequest()).<String>map(ServletRequest::getRemoteAddr).orElse("local"))
         .put(
            "user",
            Optional.ofNullable(PLDependencies.getCommonDependencies())
               .<User>map(CommonDependencies::getCurrentUser)
               .map(u -> u.getCredential().getUserName())
               .orElse(null)
         );
   }
}
