content: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You 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.apache.camel.component.jira;
  
  import java.net.URI;
  import java.util.Map;
  
  import com.atlassian.jira.rest.client.api.JiraRestClient;
  import com.atlassian.jira.rest.client.api.JiraRestClientFactory;
  import org.apache.camel.Category;
  import org.apache.camel.Consumer;
  import org.apache.camel.Processor;
  import org.apache.camel.Producer;
  import org.apache.camel.component.jira.consumer.AbstractJiraConsumer;
  import org.apache.camel.component.jira.consumer.NewCommentsConsumer;
  import org.apache.camel.component.jira.consumer.NewIssuesConsumer;
  import org.apache.camel.component.jira.consumer.WatchUpdatesConsumer;
  import org.apache.camel.component.jira.oauth.JiraOAuthAuthenticationHandler;
  import org.apache.camel.component.jira.oauth.OAuthAsynchronousJiraRestClientFactory;
  import org.apache.camel.component.jira.producer.AddCommentProducer;
  import org.apache.camel.component.jira.producer.AddIssueLinkProducer;
  import org.apache.camel.component.jira.producer.AddIssueProducer;
  import org.apache.camel.component.jira.producer.AddWorkLogProducer;
  import org.apache.camel.component.jira.producer.AttachFileProducer;
  import org.apache.camel.component.jira.producer.DeleteIssueProducer;
  import org.apache.camel.component.jira.producer.FetchCommentsProducer;
  import org.apache.camel.component.jira.producer.FetchIssueProducer;
  import org.apache.camel.component.jira.producer.TransitionIssueProducer;
  import org.apache.camel.component.jira.producer.UpdateIssueProducer;
  import org.apache.camel.component.jira.producer.WatcherProducer;
  import org.apache.camel.spi.EndpointServiceLocation;
  import org.apache.camel.spi.Metadata;
  import org.apache.camel.spi.Registry;
  import org.apache.camel.spi.UriEndpoint;
  import org.apache.camel.spi.UriParam;
  import org.apache.camel.spi.UriPath;
  import org.apache.camel.support.ScheduledPollEndpoint;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import static org.apache.camel.component.jira.JiraConstants.JIRA_REST_CLIENT_FACTORY;
  
  /**
   * Interact with JIRA issue tracker.
   * <p>
   * The endpoint encapsulates portions of the JIRA API, relying on the jira-rest-java-client SDK. Available endpoint URIs
   * include:
   * <p>
   * CONSUMERS jira://newIssues (retrieve only new issues after the route is started) jira://newComments (retrieve only
   * new comments after the route is started) jira://watchChanges (retrieve only defined changes in issues picked base on
   * provided jql)
   * <p>
   * PRODUCERS jira://addIssue (add an issue) jira://addComment (add a comment on a given issue) jira://attach (add an
   * attachment on a given issue) jira://deleteIssue (delete a given issue) jira://updateIssue (update fields of a given
   * issue) jira://transitionIssue (transition a status of a given issue) jira://watchers (add/remove watchers of a given
   * issue)
   * <p>
   * The endpoints will respond with jira-rest-java-client POJOs (Issue, Comment, etc.)
   * <p>
   * Note: Rather than webhooks, this endpoint relies on simple polling. Reasons include: - concerned about
   * reliability/stability if this somehow relied on an exposed, embedded server (Jetty?) - the types of payloads we're
   * polling aren't typically large (plus, paging is available in the API) - need to support apps running somewhere not
   * publicly accessible where a webhook would fail
   */
  @UriEndpoint(firstVersion = "3.0", scheme = "jira", title = "Jira", syntax = "jira:type",
               category = { Category.DOCUMENT }, headersClass = JiraConstants.class)
  public class JiraEndpoint extends ScheduledPollEndpoint implements EndpointServiceLocation {
  
      private static final Logger LOG = LoggerFactory.getLogger(JiraEndpoint.class);
  
      @UriPath
      @Metadata(required = true)
      private JiraType type;
      @UriParam(label = "consumer")
      private String jql;
      @UriParam(label = "consumer", defaultValue = "Status,Priority")
      private String watchedFields = "Status,Priority";
      @UriParam(label = "consumer", defaultValue = "true")
      private boolean sendOnlyUpdatedField = true;
      @UriParam(label = "consumer", defaultValue = "50")
      private Integer maxResults = 50;
      @UriParam
      private JiraConfiguration configuration;
  
      private transient JiraRestClient client;
  
      public JiraEndpoint(String uri, JiraComponent component, JiraConfiguration configuration) {
          super(uri, component);
          this.configuration = configuration;
      }
  
      @Override
      public String getServiceUrl() {
          return configuration.getJiraUrl();
      }
  
      @Override
      public String getServiceProtocol() {
          return "rest";
      }
  
      @Override
      public Map<String, String> getServiceMetadata() {
          if (configuration.getUsername() != null) {
              return Map.of("username", configuration.getUsername());
          }
          return null;
      }
  
      public JiraConfiguration getConfiguration() {
          return configuration;
      }
  
      @Override
      public void doStart() throws Exception {
          super.doStart();
          connect();
      }
  
      @Override
      protected void doStop() throws Exception {
          super.doStop();
          disconnect();
      }
  
      public void connect() {
          lock.lock();
          try {
              if (client == null) {
                  Registry registry = getCamelContext().getRegistry();
                  JiraRestClientFactory factory
                          = registry.lookupByNameAndType(JIRA_REST_CLIENT_FACTORY, JiraRestClientFactory.class);
                  if (factory == null) {
                      factory = new OAuthAsynchronousJiraRestClientFactory();
                  }
                  final URI jiraServerUri = URI.create(configuration.getJiraUrl());
                  if (configuration.getUsername() != null) {
                      LOG.debug("Connecting to JIRA with Basic authentication with username/password");
                      client = factory.createWithBasicHttpAuthentication(jiraServerUri, configuration.getUsername(),
                              configuration.getPassword());
                  } else if (configuration.getAccessToken() != null
                          && configuration.getVerificationCode() == null
                          && configuration.getPrivateKey() == null
                          && configuration.getConsumerKey() == null) {
                      client = factory.create(jiraServerUri, builder -> {
                          builder.setHeader("Authorization", "Bearer " + configuration.getAccessToken());
                      });
                  } else {
                      LOG.debug("Connecting to JIRA with OAuth authentication");
                      JiraOAuthAuthenticationHandler oAuthHandler = new JiraOAuthAuthenticationHandler(
                              configuration.getConsumerKey(),
                              configuration.getVerificationCode(), configuration.getPrivateKey(),
                              configuration.getAccessToken(),
                              configuration.getJiraUrl());
                      client = factory.create(jiraServerUri, oAuthHandler);
                  }
              }
          } finally {
              lock.unlock();
          }
      }
  
      public void disconnect() throws Exception {
          lock.lock();
          try {
              if (client != null) {
                  LOG.debug("Disconnecting from JIRA");
                  client.close();
                  client = null;
              }
          } finally {
              lock.unlock();
          }
      }
  
      @Override
      public Producer createProducer() {
          return switch (type) {
              case ADDISSUE -> new AddIssueProducer(this);
              case ATTACH -> new AttachFileProducer(this);
              case ADDCOMMENT -> new AddCommentProducer(this);
              case WATCHERS -> new WatcherProducer(this);
              case DELETEISSUE -> new DeleteIssueProducer(this);
              case UPDATEISSUE -> new UpdateIssueProducer(this);
              case TRANSITIONISSUE -> new TransitionIssueProducer(this);
              case ADDISSUELINK -> new AddIssueLinkProducer(this);
              case ADDWORKLOG -> new AddWorkLogProducer(this);
              case FETCHISSUE -> new FetchIssueProducer(this);
              case FETCHCOMMENTS -> new FetchCommentsProducer(this);
              default -> throw new IllegalArgumentException("Producer does not support type: " + type);
          };
      }
  
      @Override
      public Consumer createConsumer(Processor processor) throws Exception {
          AbstractJiraConsumer consumer;
          if (type == JiraType.NEWCOMMENTS) {
              consumer = new NewCommentsConsumer(this, processor);
          } else if (type == JiraType.NEWISSUES) {
              consumer = new NewIssuesConsumer(this, processor);
          } else if (type == JiraType.WATCHUPDATES) {
              consumer = new WatchUpdatesConsumer(this, processor);
          } else {
              throw new IllegalArgumentException("Consumer does not support type: " + type);
          }
          consumer.setMaxMessagesPerPoll(getMaxResults());
          configureConsumer(consumer);
          return consumer;
      }
  
      public JiraType getType() {
          return type;
      }
  
      /**
       * Operation to perform. Consumers: NewIssues, NewComments. Producers: AddIssue, AttachFile, DeleteIssue,
       * TransitionIssue, UpdateIssue, Watchers. See this class javadoc description for more information.
       */
      public void setType(JiraType type) {
          this.type = type;
      }
  
      /**
       * JQL is the query language from JIRA which allows you to retrieve the data you want. For example
       * <tt>jql=project=MyProject</tt> Where MyProject is the product key in Jira. It is important to use the RAW() and
       * set the JQL inside it to prevent camel parsing it, example: RAW(project in (MYP, COM) AND resolution =
       * Unresolved)
       */
      public String getJql() {
          return jql;
      }
  
      public void setJql(String jql) {
          this.jql = jql;
      }
  
      public long getDelay() {
          return configuration.getDelay();
      }
  
      public JiraRestClient getClient() {
          // ensure connected
          connect();
          return client;
      }
  
      public void setClient(JiraRestClient client) {
          this.client = client;
      }
  
      public Integer getMaxResults() {
          return maxResults;
      }
  
      /**
       * Max number of issues to search for
       */
      public void setMaxResults(Integer maxResults) {
          this.maxResults = maxResults;
      }
  
      /**
       * Comma separated list of fields to watch for changes. "Status,Priority" are the defaults.
       */
      public String getWatchedFields() {
          return watchedFields;
      }
  
      public void setWatchedFields(String watchChange) {
          this.watchedFields = watchChange;
      }
  
      /**
       * Indicator for sending only changed fields in exchange body or issue object. By default consumer sends only
       * changed fields.
       */
      public boolean isSendOnlyUpdatedField() {
          return sendOnlyUpdatedField;
      }
  
      public void setSendOnlyUpdatedField(boolean sendOnlyUpdatedField) {
          this.sendOnlyUpdatedField = sendOnlyUpdatedField;
      }
  
  }
  