text:
// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2009-2011 Google, All Rights reserved
// Copyright 2011-2012 MIT, All rights reserved
// Released under the MIT License https://raw.github.com/mit-cml/app-inventor/master/mitlicense.txt

package com.google.appinventor.client;

import static com.google.appinventor.client.Ode.MESSAGES;

import com.google.appinventor.client.boxes.AssetListBox;
import com.google.appinventor.client.boxes.MessagesOutputBox;
import com.google.appinventor.client.boxes.OdeLogBox;
import com.google.appinventor.client.boxes.PaletteBox;
import com.google.appinventor.client.boxes.ProjectListBox;
import com.google.appinventor.client.boxes.PropertiesBox;
import com.google.appinventor.client.boxes.SourceStructureBox;
import com.google.appinventor.client.boxes.ViewerBox;
import com.google.appinventor.client.editor.EditorManager;
import com.google.appinventor.client.editor.youngandroid.YaFormEditor;
import com.google.appinventor.client.explorer.commands.CommandRegistry;
import com.google.appinventor.client.explorer.project.Project;
import com.google.appinventor.client.explorer.project.ProjectChangeAdapter;
import com.google.appinventor.client.explorer.project.ProjectManager;
import com.google.appinventor.client.explorer.project.ProjectManagerEventAdapter;
import com.google.appinventor.client.explorer.youngandroid.ProjectToolbar;
import com.google.appinventor.client.jsonp.JsonpConnection;
import com.google.appinventor.client.output.OdeLog;
import com.google.appinventor.client.settings.user.UserSettings;
import com.google.appinventor.client.tracking.Tracking;
import com.google.appinventor.client.widgets.boxes.Box;
import com.google.appinventor.client.widgets.boxes.ColumnLayout;
import com.google.appinventor.client.widgets.boxes.ColumnLayout.Column;
import com.google.appinventor.client.widgets.boxes.WorkAreaPanel;
import com.google.appinventor.client.youngandroid.CodeblocksManager;
import com.google.appinventor.common.version.AppInventorFeatures;
import com.google.appinventor.shared.rpc.GetMotdService;
import com.google.appinventor.shared.rpc.GetMotdServiceAsync;
import com.google.appinventor.shared.rpc.ServerLayout;
import com.google.appinventor.shared.rpc.help.HelpService;
import com.google.appinventor.shared.rpc.help.HelpServiceAsync;
import com.google.appinventor.shared.rpc.launch.LaunchService;
import com.google.appinventor.shared.rpc.launch.LaunchServiceAsync;
import com.google.appinventor.shared.rpc.project.ProjectRootNode;
import com.google.appinventor.shared.rpc.project.ProjectService;
import com.google.appinventor.shared.rpc.project.ProjectServiceAsync;
import com.google.appinventor.shared.rpc.user.User;
import com.google.appinventor.shared.rpc.user.UserInfoService;
import com.google.appinventor.shared.rpc.user.UserInfoServiceAsync;
import com.google.appinventor.shared.settings.SettingsConstants;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.http.client.Response;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Cookies;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.StatusCodeException;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DeckPanel;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.DockPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PushButton;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.VerticalPanel;

/**
 * Main entry point for Ode. Defines the startup UI elements in
 * {@link #onModuleLoad()}.
 *
 */
public class Ode implements EntryPoint {
  // I18n messages
  public static final OdeMessages MESSAGES = GWT.create(OdeMessages.class);

  /**
   * The base URL for App Inventor documentation.
   */
  public static final String APP_INVENTOR_DOCS_URL = "";

  // Global instance of the Ode object
  private static Ode instance;

  // Application level image bundle
  private static final Images IMAGES = GWT.create(Images.class);

  // ProjectEditor registry
  private static final ProjectEditorRegistry EDITORS = new ProjectEditorRegistry();

  // Command registry
  private static final CommandRegistry COMMANDS = new CommandRegistry();

  // User settings
  private static UserSettings userSettings;

  private MotdFetcher motdFetcher;

  // User information
  private User user;

  // Collection of projects
  private ProjectManager projectManager;

  // Collection of editors
  private EditorManager editorManager;

  // Currently active form editor
  private YaFormEditor currentYaFormEditor;

  /*
   * The following fields define the general layout of the UI as seen in the following diagram:
   *
   *  +-- mainPanel --------------------------------+
   *  |+-- topPanel -------------------------------+|
   *  ||                                           ||
   *  |+-------------------------------------------+|
   *  |+-- deckPanel -------------------------------+|
   *  ||                                           ||
   *  |+-------------------------------------------+|
   *  |+-- statusPanel ----------------------------+|
   *  ||                                           ||
   *  |+-------------------------------------------+|
   *  +---------------------------------------------+
   */
  private DeckPanel deckPanel;
  private int projectsTabIndex;
  private int designTabIndex;
  private int debuggingTabIndex;
  private TopPanel topPanel;
  private StatusPanel statusPanel;
  private ProjectToolbar projectToolbar;
  private DesignToolbar designToolbar;
  // Popup that indicates that an asynchronous request is pending. It is visible
  // initially, and will be hidden automatically after the first RPC completes.
  private static RpcStatusPopup rpcStatusPopup;

  // Web service for help information
  private final HelpServiceAsync helpService = GWT.create(HelpService.class);

  // Web service for project related information
  private final ProjectServiceAsync projectService = GWT.create(ProjectService.class);

  // Web service for user related information
  private final UserInfoServiceAsync userInfoService = GWT.create(UserInfoService.class);

  // Web service for launch related services
  private final LaunchServiceAsync launchService = GWT.create(LaunchService.class);

  // Web service for get motd information
  private final GetMotdServiceAsync getMotdService = GWT.create(GetMotdService.class);

  private boolean windowClosing;

  /**
   * Returns global instance of Ode.
   *
   * @return  global Ode instance
   */
  public static Ode getInstance() {
    return instance;
  }

  /**
   * Returns instance of the aggregate image bundle for the application.
   *
   * @return  image bundle
   */
  public static Images getImageBundle() {
    return IMAGES;
  }

  /**
   * Returns the editor registry.
   *
   * @return the editor registry
   */
  public static ProjectEditorRegistry getProjectEditorRegistry() {
    return EDITORS;
  }

  /**
   * Returns the command registry.
   *
   * @return the command registry
   */
  public static CommandRegistry getCommandRegistry() {
    return COMMANDS;
  }

  /**
   * Returns the user settings.
   *
   * @return  user settings
   */
  public static UserSettings getUserSettings() {
    return userSettings;
  }

  /**
   * Returns true if we have received the window closing event.
   */
  public static boolean isWindowClosing() {
    return getInstance().windowClosing;
  }

  /**
   * Switch to the Projects tab
   */
  public void switchToProjectsView() {
    deckPanel.showWidget(projectsTabIndex);
  }

  /**
   * Switch to the Designer tab
   */
  public void switchToDesignView() {
    // Only show designer if there is a current editor.
    // ***** THE DESIGNER TAB DOES NOT DISPLAY CORRECTLY IF THERE IS NO CURRENT EDITOR. *****
    if (currentYaFormEditor != null) {
      deckPanel.showWidget(designTabIndex);
    }
  }

  /**
   * Switch to the Debugging tab
   */
  public void switchToDebuggingView() {
    deckPanel.showWidget(debuggingTabIndex);

    // NOTE(lizlooney) - Calling resizeWorkArea for debuggingTab prevents the
    // boxes from overlapping each other.
    resizeWorkArea((WorkAreaPanel) deckPanel.getWidget(debuggingTabIndex));
  }

  public void openPreviousProject() {
    if (userSettings == null) {
      OdeLog.wlog("Ignoring openPreviousProject() since userSettings is null");
      return;
    }
    String value = userSettings.getSettings(SettingsConstants.USER_GENERAL_SETTINGS).
        getPropertyValue(SettingsConstants.GENERAL_SETTINGS_CURRENT_PROJECT_ID);
    openProject(value);
  }

  private void openProject(String projectIdString) {
    if (projectIdString.equals("")) {
      openPreviousProject();
    } else if (!projectIdString.equals("0")) {
      final long projectId = Long.parseLong(projectIdString);
      Project project = projectManager.getProject(projectId);
      if (project != null) {
        openYoungAndroidProjectInDesigner(project);
      } else {
        // The project hasn't been added to the ProjectManager yet.
        // Add a ProjectManagerEventListener so we'll be notified when it has been added.
        // Alternatively, it is an invalid projectId. In which case,
        // nothing happens since if the listener eventually fires
        // it will not match the projectId.
        projectManager.addProjectManagerEventListener(new ProjectManagerEventAdapter() {
          @Override
          public void onProjectAdded(Project project) {
            if (project.getProjectId() == projectId) {
              projectManager.removeProjectManagerEventListener(this);
              openYoungAndroidProjectInDesigner(project);
            }
          }
        });
      }
    }
    // else projectIdString == 0; do nothing
  }

  public void openYoungAndroidProjectInDesigner(final Project project) {
    ProjectRootNode projectRootNode = project.getRootNode();
    if (projectRootNode == null) {
      // The project nodes haven't been loaded yet.
      // Add a ProjectChangeListener so we'll be notified when they have been loaded.
      project.addProjectChangeListener(new ProjectChangeAdapter() {
        @Override
        public void onProjectLoaded(Project projectLoaded) {
          project.removeProjectChangeListener(this);
          openYoungAndroidProjectInDesigner(project);
        }
      });
      project.loadProjectNodes();

    } else {
      // The project nodes have been loaded. We can open the editor.
      ViewerBox.getViewerBox().show(projectRootNode);
      switchToDesignView();
      String projectIdString = Long.toString(project.getProjectId());
      if (!History.getToken().equals(projectIdString)) {
        // insert token into history but do not trigger listener event
        History.newItem(projectIdString, false);
      }
    }
  }

  /**
   * Returns i18n compatible messages
   * @return messages
   */
  public static OdeMessages getMessages() {
    return MESSAGES;
  }

  /**
   * Returns the rpcStatusPopup object.
   * @return RpcStatusPopup
   */
  public static RpcStatusPopup getRpcStatusPopup() {
    return rpcStatusPopup;
  }

  /**
   * Main entry point for Ode. Setting up the UI and the web service
   * connections.
   */
  @Override
  public void onModuleLoad() {
    Tracking.trackPageview();

    // Handler for any otherwise unhandled exceptions
    GWT.setUncaughtExceptionHandler(new GWT.UncaughtExceptionHandler() {
      @Override
      public void onUncaughtException(Throwable e) {
        OdeLog.xlog(e);

        if (AppInventorFeatures.sendBugReports()) {
          if (Window.confirm(MESSAGES.internalErrorReportBug())) {
            Window.open(BugReport.getBugReportLink(e), "_blank", "");
          }
        } else {
          Window.alert(AppInventorFeatures.hasDebuggingView() ?
              MESSAGES.internalErrorSeeDebuggingView() : MESSAGES.internalError());
        }
      }
    });

    // Define bridge methods to Javascript
    JsonpConnection.defineBridgeMethod();

   // Initialize global Ode instance
    instance = this;

    // Get user information.
    OdeAsyncCallback<User> callback = new OdeAsyncCallback<User>(
        // failure message
        MESSAGES.serverUnavailable()) {

      @Override
      public void onSuccess(User result) {
        // If user hasn't accepted terms of service, ask them to.
        if (!result.getUserTosAccepted()) {
          // We expect that the redirect to the TOS page should be handled
          // by the onFailure method below. The server should return a
          // "forbidden" error if the TOS wasn't accepted.
          ErrorReporter.reportError(MESSAGES.serverUnavailable());
          return;
        }
        user = result;
        userSettings = new UserSettings(user);
        // Here we call userSettings.loadSettings, but the settings are actually loaded
        // asynchronously, so this loadSettings call will return before they are loaded.
        // After the user settings have been loaded, openPreviousProject will be called.
        userSettings.loadSettings();

        // Initialize project and editor managers
        projectManager = new ProjectManager();
        editorManager = new EditorManager();

        // Initialize UI
        initializeUi();

        topPanel.showUserEmail(user.getUserEmail());
      }

      @Override
      public void onFailure(Throwable caught) {
        if (caught instanceof StatusCodeException) {
          StatusCodeException e = (StatusCodeException) caught;
          int statusCode = e.getStatusCode();
          switch (statusCode) {
            case Response.SC_UNAUTHORIZED:
              // unauthorized => not on whitelist
              // getEncodedResponse() gives us the message that we wrote in
              // OdeAuthFilter.writeWhitelistErrorMessage().
              Window.alert(e.getEncodedResponse());
              return;
            case Response.SC_FORBIDDEN:
              // forbidden => need tos accept
              Window.open("/" + ServerLayout.YA_TOS_FORM, "_self", null);
              return;
          }
        }
        super.onFailure(caught);
      }
    };

    // TODO(user): ODE makes too many RPC requests at startup time. Currently
    // we do 3 RPCs + 1 per project + 1 per open file. We should bundle some of
    // those with each other or with the initial HTML transfer.
    userInfoService.getUserInformation(callback);

    History.addValueChangeHandler(new ValueChangeHandler<String>() {
      public void onValueChange(ValueChangeEvent<String> event) {
        openProject(event.getValue());
      }
    });

    // load project based on current url
    // TODO(sharon): Seems like a possible race condition here if the onValueChange
    // handler defined above gets called before the getUserInformation call sets
    // userSettings.
    // The following line causes problems with GWT debugging, and commenting
    // it out doesn't seem to break things.
    //History.fireCurrentHistoryState();
  }

  /*
   * Initializes all UI elements.
   */
  private void initializeUi() {
    rpcStatusPopup = new RpcStatusPopup();

    // Register services with RPC status popup
    rpcStatusPopup.register((ExtendedServiceProxy<?>) helpService);
    rpcStatusPopup.register((ExtendedServiceProxy<?>) projectService);
    rpcStatusPopup.register((ExtendedServiceProxy<?>) userInfoService);

    Window.setTitle(MESSAGES.titleYoungAndroid());
    Window.enableScrolling(true);

    topPanel = new TopPanel();
    statusPanel = new StatusPanel();

    DockPanel mainPanel = new DockPanel();
    mainPanel.add(topPanel, DockPanel.NORTH);

    // Create tab panel for subsequent tabs
    deckPanel = new DeckPanel() {
      @Override
      public final void onBrowserEvent(Event event) {
        switch (event.getTypeInt()) {
          case Event.ONCONTEXTMENU:
            event.preventDefault();
            break;
        }
      }
    };
    deckPanel.sinkEvents(Event.ONCONTEXTMENU);
    deckPanel.setWidth("100%");

    // Projects tab
    VerticalPanel pVertPanel = new VerticalPanel();
    pVertPanel.setWidth("100%");
    pVertPanel.setSpacing(0);
    HorizontalPanel projectListPanel = new HorizontalPanel();
    projectListPanel.setWidth("100%");
    projectListPanel.add(ProjectListBox.getProjectListBox());
    projectToolbar = new ProjectToolbar();
    pVertPanel.add(projectToolbar);
    pVertPanel.add(projectListPanel);
    projectsTabIndex = deckPanel.getWidgetCount();
    deckPanel.add(pVertPanel);

    // Design tab
    VerticalPanel vertPanel = new VerticalPanel();
    vertPanel.setWidth("100%");
    designToolbar = new DesignToolbar();
    vertPanel.add(designToolbar);
    HorizontalPanel workColumns = new HorizontalPanel();
    workColumns.setWidth("100%");
    Box box = PaletteBox.getPaletteBox();
    box.setWidth("225px");
    workColumns.add(box);
    workColumns.setCellWidth(box, "1%");
    box = ViewerBox.getViewerBox();
    workColumns.add(box);
    workColumns.setCellWidth(box, "97%");

    VerticalPanel structureAndAssets = new VerticalPanel();
    structureAndAssets.setVerticalAlignment(VerticalPanel.ALIGN_TOP);
    structureAndAssets.add(SourceStructureBox.getSourceStructureBox());
    structureAndAssets.add(AssetListBox.getAssetListBox());
    workColumns.add(structureAndAssets);
    workColumns.setCellWidth(structureAndAssets, "1%");

    box = PropertiesBox.getPropertiesBox();
    box.setWidth("210px");
    workColumns.add(box);
    workColumns.setCellWidth(box, "1%");
    vertPanel.add(workColumns);
    designTabIndex = deckPanel.getWidgetCount();
    deckPanel.add(vertPanel);

    // Debugging tab
    if (AppInventorFeatures.hasDebuggingView()) {
      ColumnLayout defaultLayout = new ColumnLayout("Default");
      Column column = defaultLayout.addColumn(100);
      column.add(MessagesOutputBox.class, 300, false);
      column.add(OdeLogBox.class, 300, false);
      final WorkAreaPanel debuggingTab = new WorkAreaPanel(new OdeBoxRegistry(), defaultLayout);
      debuggingTabIndex = deckPanel.getWidgetCount();
      deckPanel.add(debuggingTab);

      // Hook the window resize event, so that we can adjust the UI.
      Window.addResizeHandler(new ResizeHandler() {
        @Override
        public void onResize(ResizeEvent event) {
          resizeWorkArea(debuggingTab);
        }
      });

      // Call the window resized handler to get the initial sizes setup. Doing this in a deferred
      // command causes it to occur after all widgets' sizes have been computed by the browser.
      DeferredCommand.addCommand(new Command() {
        @Override
        public void execute() {
          resizeWorkArea(debuggingTab);
        }
      });

      resizeWorkArea(debuggingTab);
    }

    // We do not select the designer tab here because at this point there is no current project.
    // Instead, we select the projects tab. If the user has a previously opened project, we will
    // open it and switch to the designer after the user settings are loaded.
    // Remember, the user may not have any projects at all yet.
    // Or, the user may have deleted their previously opened project.
    // ***** THE DESIGNER TAB DOES NOT DISPLAY CORRECTLY IF THERE IS NO CURRENT PROJECT. *****
    deckPanel.showWidget(projectsTabIndex);

    mainPanel.add(deckPanel, DockPanel.CENTER);
    mainPanel.setCellHeight(deckPanel, "100%");
    mainPanel.setCellWidth(deckPanel, "100%");

    mainPanel.add(statusPanel, DockPanel.SOUTH);
    mainPanel.setSize("100%", "98%");
    RootPanel.get().add(mainPanel);

    // There is no sure-fire way of preventing people from accidentally navigating away from ODE
    // (e.g. by hitting the Backspace key). What we do need though is to make sure that people will
    // not lose any work because of this. We hook into the window closing event to detect the
    // situation.
    Window.addWindowClosingHandler(new Window.ClosingHandler() {
      @Override
      public void onWindowClosing(Window.ClosingEvent event) {
        onClosing();
      }
    });

    // Check if the user has any saved projects.
    // If the user has no saved projects, then show the welcome dialog.
    getProjectService().getProjects(new AsyncCallback<long[]>() {
      @Override
      public void onSuccess(long[] projectIds) {
        if (projectIds.length == 0) {
          createWelcomeDialog(true);
        }
      }

      @Override
      public void onFailure(Throwable projectIds) {
        OdeLog.elog("Could not get project list");
      }
    });

    setupMotd();
  }

  private void setupMotd() {
    AsyncCallback<Integer> callback = new AsyncCallback<Integer>() {
      @Override
      public void onFailure(Throwable caught) {
        OdeLog.log(MESSAGES.getMotdFailed());
      }

      @Override
      public void onSuccess(Integer intervalSecs) {
        if (intervalSecs > 0) {
          topPanel.showMotd();
          motdFetcher = new MotdFetcher(intervalSecs);
          motdFetcher.register((ExtendedServiceProxy<?>) projectService);
          motdFetcher.register((ExtendedServiceProxy<?>) userInfoService);
        }
      }
    };

    getGetMotdService().getCheckInterval(callback);
  }

  /**
   * Returns the editor manager.
   *
   * @return  {@link EditorManager}
   */
  public EditorManager getEditorManager() {
    return editorManager;
  }

  /**
   * Returns the project manager.
   *
   * @return  {@link ProjectManager}
   */
  public ProjectManager getProjectManager() {
    return projectManager;
  }

  /**
   * Returns the project tool bar.
   *
   * @return  {@link ProjectToolbar}
   */
  public ProjectToolbar getProjectToolbar() {
    return projectToolbar;
  }

  /**
   * Returns the design tool bar.
   *
   * @return  {@link DesignToolbar}
   */
  public DesignToolbar getDesignToolbar() {
    return designToolbar;
  }

  /**
   * Get an instance of the project information web service.
   *
   * @return project web service instance
   */
  public ProjectServiceAsync getProjectService() {
    return projectService;
  }

  /**
   * Get an instance of the user information web service.
   *
   * @return user information web service instance
   */
  public UserInfoServiceAsync getUserInfoService() {
    return userInfoService;
  }

  /**
   * Get an instance of the motd web service.
   *
   * @return motd web service instance
   */
  public GetMotdServiceAsync getGetMotdService() {
    return getMotdService;
  }

  /**
   * Get an instance of the help web service.
   *
   * @return help service instance
   */
  public HelpServiceAsync getHelpService() {
    return helpService;
  }

  /**
   * Get an instance of the launch RPC service.
   *
   * @return launch service instance
   */
  public LaunchServiceAsync getLaunchService() {
    return launchService;
  }

  /**
   * Set the current young android form editor.
   *
   * @param yaFormEditor  the form editor, can be null.
   */
  public void setCurrentYaFormEditor(YaFormEditor yaFormEditor) {
    currentYaFormEditor = yaFormEditor;

    switchToDesignView();

    ProjectRootNode root = getCurrentYoungAndroidProjectRootNode();
    String name = "";
    if (root != null) {
      name = root.getName();
    }
    designToolbar.updateProjectName(name);
    designToolbar.updateButtons();

    if (!windowClosing) {
      userSettings.getSettings(SettingsConstants.USER_GENERAL_SETTINGS).
          changePropertyValue(SettingsConstants.GENERAL_SETTINGS_CURRENT_PROJECT_ID,
          "" + getCurrentYoungAndroidProjectId());
      userSettings.saveSettings(null);
    }
  }

  /**
   * Returns the project root node for the current project, or null if there is no current project.
   *
   * @return  project root node corresponding to current project
   */
  public ProjectRootNode getCurrentYoungAndroidProjectRootNode() {
    if (currentYaFormEditor != null) {
      return currentYaFormEditor.getFormNode().getProjectRoot();
    }
    return null;
  }

  /**
   * Updates the modification date for the requested projected in the local
   * cached data structure based on the date received from the server.
   * @param date  the date to update it to
   */
  public void updateModificationDate(long projectId, long date) {
    Project project = getProjectManager().getProject(projectId);
    if (project != null) {
      project.setDateModified(date);
    }
  }

  /**
   * Returns the current project id, or 0 if there is no current project.
   *
   * @return  the current project id
   */
  public long getCurrentYoungAndroidProjectId() {
    if (currentYaFormEditor != null) {
      return currentYaFormEditor.getProjectId();
    }
    return 0;
  }

  /**
   * Returns the current form editor, or null if there is no current form editor.
   *
   * @return  the current form editor
   */
  public YaFormEditor getCurrentYoungAndroidFormEditor() {
    return currentYaFormEditor;
  }

  /**
   * Returns user account information.
   *
   * @return user account information
   */
  public User getUser() {
    return user;
  }

  /**
   * Helper method to create push buttons.
   *
   * @param img  image to shown on face of push button
   * @param tip  text to show in tooltip
   * @return  newly created push button
   */
  public static PushButton createPushButton(ImageResource img, String tip,
      ClickHandler handler) {
    PushButton pb = new PushButton(new Image(img));
    pb.addClickHandler(handler);
    pb.setTitle(tip);
    return pb;
  }

  private void resizeWorkArea(WorkAreaPanel workArea) {
    // Subtract 16px from width to account for vertical scrollbar FF3 likes to add
    workArea.onResize(Window.getClientWidth() - 16, Window.getClientHeight());
  }

  private void onClosing() {
    // At this point, we aren't allowed to do any UI.
    windowClosing = true;

    if (motdFetcher != null) {
      motdFetcher.unregister((ExtendedServiceProxy<?>) projectService);
      motdFetcher.unregister((ExtendedServiceProxy<?>) userInfoService);
    }

    // Unregister services with RPC status popup.
    rpcStatusPopup.unregister((ExtendedServiceProxy<?>) helpService);
    rpcStatusPopup.unregister((ExtendedServiceProxy<?>) projectService);
    rpcStatusPopup.unregister((ExtendedServiceProxy<?>) userInfoService);

    // Save the user settings.
    userSettings.saveSettings(null);

    // Save all unsaved editors.
    editorManager.saveDirtyEditors(null);

    CodeblocksManager codeblocksManager = CodeblocksManager.getCodeblocksManager();
    if (codeblocksManager.isCodeblocksOpen()) {
      codeblocksManager.terminateCodeblocks();

      // NOTE(lizlooney) - Terminating codeblocks involves an asynchronous JSONP call. This works
      // fine on most browsers, but is not actually sent in Safari (and in some cases in Chrome).
      // The only way I found to fix this is to put a Window.alert() at the end of this method.
      // This gives the browser a chance to process the asynchronous call.
      // However, the alert is quite annoying, so for now we don't show the alert and we might
      // leave codeblocks running.
      /*
      if (!isBrowserSafari()) {
        Window.alert(MESSAGES.onClosingBrowserWithCodeblocksOpen());
      }
      */
    }
  }

  /**
   * Creates, visually centers, and optionally displays the dialog box
   * that informs the user how to start learning about using App Inventor
   * or create a new project.
   * @param showDialog Convenience variable to show the created DialogBox.
   * @return The created and optionally displayed Dialog box.
   */
  public DialogBox createWelcomeDialog(boolean showDialog) {
    // Create the UI elements of the DialogBox
    final DialogBox dialogBox = new DialogBox(true);
    dialogBox.setStylePrimaryName("ode-DialogBox");
    dialogBox.setText("Welcome to App Inventor!");

    Grid mainGrid = new Grid(2, 2);
    mainGrid.getCellFormatter().setAlignment(0,
        0,
        HasHorizontalAlignment.ALIGN_CENTER,
        HasVerticalAlignment.ALIGN_MIDDLE);
    mainGrid.getCellFormatter().setAlignment(0,
        1,
        HasHorizontalAlignment.ALIGN_CENTER,
        HasVerticalAlignment.ALIGN_MIDDLE);
    mainGrid.getCellFormatter().setAlignment(1,
        1,
        HasHorizontalAlignment.ALIGN_RIGHT,
        HasVerticalAlignment.ALIGN_MIDDLE);

    Image dialogImage = new Image(Ode.getImageBundle().androidGreenSmall());

    Grid messageGrid = new Grid(2, 1);
    messageGrid.getCellFormatter().setAlignment(0,
        0,
        HasHorizontalAlignment.ALIGN_JUSTIFY,
        HasVerticalAlignment.ALIGN_MIDDLE);
    messageGrid.getCellFormatter().setAlignment(1,
        0,
        HasHorizontalAlignment.ALIGN_LEFT,
        HasVerticalAlignment.ALIGN_MIDDLE);

    Label messageChunk1 = new Label("You don't have any projects yet."
        + " To learn how to use App Inventor, click the \"Learn\" item"
        + " at the top of the window; or to start your first project, click "
        + " the \"New\" button at the upper left of the window.");
    messageChunk1.setWidth("23em");
    Label messageChunk2 = new Label("Happy Inventing!");

    // Add the elements to the grids and DialogBox.
    messageGrid.setWidget(0, 0, messageChunk1);
    messageGrid.setWidget(1, 0, messageChunk2);

    mainGrid.setWidget(0, 0, dialogImage);
    mainGrid.setWidget(0, 1, messageGrid);

    dialogBox.setWidget(mainGrid);

    dialogBox.center();
    if (showDialog) {
      dialogBox.show();
    }
    return dialogBox;
  }
}


text:
    Show / Hide Table of Contents

        <form ng-class="{focus:focus}" class="toc-filter" ng-submit="submit()">

          <input type="text" placeholder="Enter here to filter..." ng-focus="focus=true" ng-blur="focus=false" ng-change="filteredItems(f)"
          ng-model="f" autocomplete="off" />
        </form>

            {{ navGroup.uid || navGroup.name}}

                  {{navItem.name}}

    <markdown-content ng-switch-when="markdown" get-map="true" ng-navbar="navbar" ng-model="contentPath" ng-cloak autoscroll></markdown-content>
    <yaml-content ng-switch-when="yaml" ng-language="'csharp'" ng-navbar="navbar" content-type="{{contentType}}" get-map="true" ng-model="contentPath"
    ng-cloak autoscroll></yaml-content>

          {{item.name}}

Oops!

The page you requested does not exist. Perhaps you were looking for something else...

redpajama_set_name: RedPajamaGithub


text:
package ca.uhn.fhir.jpa.search;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IDao;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.dao.IResultIterator;
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
import ca.uhn.fhir.jpa.dao.SearchBuilderFactory;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.entity.SearchInclude;
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
import ca.uhn.fhir.jpa.interceptor.JpaPreResourceAccessDetails;
import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails;
import ca.uhn.fhir.jpa.model.search.SearchStatusEnum;
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
import ca.uhn.fhir.jpa.search.cache.ISearchResultCacheSvc;
import ca.uhn.fhir.jpa.search.cache.SearchCacheStatusEnum;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.util.InterceptorUtil;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.rest.api.CacheControlDirective;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.SearchTotalModeEnum;
import ca.uhn.fhir.rest.api.SummaryEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.IPreResourceAccessDetails;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.server.IPagingProvider;
import ca.uhn.fhir.rest.server.SimpleBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.method.PageMethodBinding;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
import ca.uhn.fhir.rest.server.util.ICachedSearchDetails;
import ca.uhn.fhir.util.AsyncUtil;
import ca.uhn.fhir.util.StopWatch;
import co.elastic.apm.api.ElasticApm;
import co.elastic.apm.api.Span;
import co.elastic.apm.api.Transaction;
import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.AbstractPageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.orm.jpa.JpaDialect;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import java.io.IOException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;

@Component("mySearchCoordinatorSvc")
public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
	public static final int DEFAULT_SYNC_SIZE = 250;
	public static final String UNIT_TEST_CAPTURE_STACK = "unit_test_capture_stack";
	public static final Integer INTEGER_0 = 0;
	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchCoordinatorSvcImpl.class);
	private final ConcurrentHashMap<String, SearchTask> myIdToSearchTask = new ConcurrentHashMap<>();
	@Autowired
	private FhirContext myContext;
	@Autowired
	private DaoConfig myDaoConfig;
	@Autowired
	private EntityManager myEntityManager;
	private Integer myLoadingThrottleForUnitTests = null;
	private long myMaxMillisToWaitForRemoteResults = DateUtils.MILLIS_PER_MINUTE;
	private boolean myNeverUseLocalSearchForUnitTests;
	@Autowired
	private IInterceptorBroadcaster myInterceptorBroadcaster;
	@Autowired
	private PlatformTransactionManager myManagedTxManager;
	@Autowired
	private ISearchCacheSvc mySearchCacheSvc;
	@Autowired
	private ISearchResultCacheSvc mySearchResultCacheSvc;
	@Autowired
	private DaoRegistry myDaoRegistry;
	@Autowired
	private IPagingProvider myPagingProvider;
	@Autowired
	private SearchBuilderFactory mySearchBuilderFactory;

	private int mySyncSize = DEFAULT_SYNC_SIZE;
	/**
	 * Set in {@link #start()}
	 */
	private boolean myCustomIsolationSupported;
	@Autowired
	private PersistedJpaBundleProviderFactory myPersistedJpaBundleProviderFactory;
	@Autowired
	private IRequestPartitionHelperSvc myRequestPartitionHelperService;

	/**
	 * Constructor
	 */
	@Autowired
	public SearchCoordinatorSvcImpl() {
		super();
	}

	@VisibleForTesting
	Set<String> getActiveSearchIds() {
		return myIdToSearchTask.keySet();
	}

	@VisibleForTesting
	public void setSearchCacheServicesForUnitTest(ISearchCacheSvc theSearchCacheSvc, ISearchResultCacheSvc theSearchResultCacheSvc) {
		mySearchCacheSvc = theSearchCacheSvc;
		mySearchResultCacheSvc = theSearchResultCacheSvc;
	}

	@PostConstruct
	public void start() {
		if (myManagedTxManager instanceof JpaTransactionManager) {
			JpaDialect jpaDialect = ((JpaTransactionManager) myManagedTxManager).getJpaDialect();
			if (jpaDialect instanceof HibernateJpaDialect) {
				myCustomIsolationSupported = true;
			}
		}
		if (myCustomIsolationSupported == false) {
			ourLog.warn("JPA dialect does not support transaction isolation! This can have an impact on search performance.");
		}
	}

	@Override
	public void cancelAllActiveSearches() {
		for (SearchTask next : myIdToSearchTask.values()) {
			ourLog.info("Requesting immediate abort of search: {}", next.getSearch().getUuid());
			next.requestImmediateAbort();
			AsyncUtil.awaitLatchAndIgnoreInterrupt(next.getCompletionLatch(), 30, TimeUnit.SECONDS);
		}
	}

	@SuppressWarnings("SameParameterValue")
	@VisibleForTesting
	void setMaxMillisToWaitForRemoteResultsForUnitTest(long theMaxMillisToWaitForRemoteResults) {
		myMaxMillisToWaitForRemoteResults = theMaxMillisToWaitForRemoteResults;
	}

	/**
	 * This method is called by the HTTP client processing thread in order to
	 * fetch resources.
	 */
	@Override
	@Transactional(propagation = Propagation.NEVER)
	public List<ResourcePersistentId> getResources(final String theUuid, int theFrom, int theTo, @Nullable RequestDetails theRequestDetails) {
		TransactionTemplate txTemplate = new TransactionTemplate(myManagedTxManager);
		txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

		// If we're actively searching right now, don't try to do anything until at least one batch has been
		// persisted in the DB
		SearchTask searchTask = myIdToSearchTask.get(theUuid);
		if (searchTask != null) {
			searchTask.awaitInitialSync();
		}

		ourLog.trace("About to start looking for resources {}-{}", theFrom, theTo);

		Search search;
		StopWatch sw = new StopWatch();
		while (true) {

			if (myNeverUseLocalSearchForUnitTests == false) {
				if (searchTask != null) {
					ourLog.trace("Local search found");
					List<ResourcePersistentId> resourcePids = searchTask.getResourcePids(theFrom, theTo);
					ourLog.trace("Local search returned {} pids, wanted {}-{} - Search: {}", resourcePids.size(), theFrom, theTo, searchTask.getSearch());

					/*
					 * Generally, if a search task is open, the fastest possible thing is to just return its results. This
					 * will work most of the time, but can fail if the task hit a search threshold and the client is requesting
					 * results beyond that threashold. In that case, we'll keep going below, since that will trigger another
					 * task.
					 */
					if ((searchTask.getSearch().getNumFound() - searchTask.getSearch().getNumBlocked()) >= theTo || resourcePids.size() == (theTo - theFrom)) {
						return resourcePids;
					}
				}
			}

			search = mySearchCacheSvc
				.fetchByUuid(theUuid)
				.orElseThrow(() -> newResourceGoneException(theUuid));

			verifySearchHasntFailedOrThrowInternalErrorException(search);
			if (search.getStatus() == SearchStatusEnum.FINISHED) {
				ourLog.trace("Search entity marked as finished with {} results", search.getNumFound());
				break;
			}
			if ((search.getNumFound() - search.getNumBlocked()) >= theTo) {
				ourLog.trace("Search entity has {} results so far", search.getNumFound());
				break;
			}

			if (sw.getMillis() > myMaxMillisToWaitForRemoteResults) {
				ourLog.error("Search {} of type {} for {}{} timed out after {}ms", search.getId(), search.getSearchType(), search.getResourceType(), search.getSearchQueryString(), sw.getMillis());
				throw new InternalErrorException("Request timed out after " + sw.getMillis() + "ms");
			}

			// If the search was saved in "pass complete mode" it's probably time to
			// start a new pass
			if (search.getStatus() == SearchStatusEnum.PASSCMPLET) {
				ourLog.trace("Going to try to start next search");
				Optional<Search> newSearch = mySearchCacheSvc.tryToMarkSearchAsInProgress(search);
				if (newSearch.isPresent()) {
					ourLog.trace("Launching new search");
					search = newSearch.get();
					String resourceType = search.getResourceType();
					SearchParameterMap params = search.getSearchParameterMap().orElseThrow(() -> new IllegalStateException("No map in PASSCOMPLET search"));
					IFhirResourceDao<?> resourceDao = myDaoRegistry.getResourceDao(resourceType);
					RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(theRequestDetails, resourceType, params, null);
					SearchContinuationTask task = new SearchContinuationTask(search, resourceDao, params, resourceType, theRequestDetails, requestPartitionId);
					myIdToSearchTask.put(search.getUuid(), task);
					task.call();
				}
			}

			AsyncUtil.sleep(500);
		}

		ourLog.trace("Finished looping");

		List<ResourcePersistentId> pids = mySearchResultCacheSvc.fetchResultPids(search, theFrom, theTo);
		if (pids == null) {
			throw newResourceGoneException(theUuid);
		}

		ourLog.trace("Fetched {} results", pids.size());

		return pids;
	}

	@Nonnull
	private ResourceGoneException newResourceGoneException(String theUuid) {
		ourLog.trace("Client requested unknown paging ID[{}]", theUuid);
		String msg = myContext.getLocalizer().getMessage(PageMethodBinding.class, "unknownSearchId", theUuid);
		return new ResourceGoneException(msg);
	}

	@Override
	public IBundleProvider registerSearch(final IFhirResourceDao<?> theCallingDao, final SearchParameterMap theParams, String theResourceType, CacheControlDirective theCacheControlDirective, RequestDetails theRequestDetails, RequestPartitionId theRequestPartitionId) {
		final String searchUuid = UUID.randomUUID().toString();

		final String queryString = theParams.toNormalizedQueryString(myContext);
		ourLog.debug("Registering new search {}", searchUuid);

		Search search = new Search();
		populateSearchEntity(theParams, theResourceType, searchUuid, queryString, search, theRequestPartitionId);

		// Interceptor call: STORAGE_PRESEARCH_REGISTERED
		HookParams params = new HookParams()
			.add(ICachedSearchDetails.class, search)
			.add(RequestDetails.class, theRequestDetails)
			.addIfMatchesType(ServletRequestDetails.class, theRequestDetails)
			.add(SearchParameterMap.class, theParams);
		CompositeInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequestDetails, Pointcut.STORAGE_PRESEARCH_REGISTERED, params);

		Class<? extends IBaseResource> resourceTypeClass = myContext.getResourceDefinition(theResourceType).getImplementingClass();
		final ISearchBuilder sb = mySearchBuilderFactory.newSearchBuilder(theCallingDao, theResourceType, resourceTypeClass);
		sb.setFetchSize(mySyncSize);

		final Integer loadSynchronousUpTo = getLoadSynchronousUpToOrNull(theCacheControlDirective);
		boolean isOffsetQuery = theParams.isOffsetQuery();

		if (theParams.isLoadSynchronous() || loadSynchronousUpTo != null || isOffsetQuery) {
			ourLog.debug("Search {} is loading in synchronous mode", searchUuid);
			return executeQuery(theResourceType, theParams, theRequestDetails, searchUuid, sb, loadSynchronousUpTo, theRequestPartitionId);
		}

		/*
		 * See if there are any cached searches whose results we can return
		 * instead
		 */
		SearchCacheStatusEnum cacheStatus = SearchCacheStatusEnum.MISS;
		if (theCacheControlDirective != null && theCacheControlDirective.isNoCache() == true) {
			cacheStatus = SearchCacheStatusEnum.NOT_TRIED;
		}

		if (cacheStatus != SearchCacheStatusEnum.NOT_TRIED) {
			if (theParams.getEverythingMode() == null) {
				if (myDaoConfig.getReuseCachedSearchResultsForMillis() != null) {
					PersistedJpaBundleProvider foundSearchProvider = findCachedQuery(theParams, theResourceType, theRequestDetails, queryString, theRequestPartitionId);
					if (foundSearchProvider != null) {
						foundSearchProvider.setCacheStatus(SearchCacheStatusEnum.HIT);
						return foundSearchProvider;
					}
				}
			}
		}

		PersistedJpaSearchFirstPageBundleProvider retVal = submitSearch(theCallingDao, theParams, theResourceType, theRequestDetails, searchUuid, sb, queryString, theRequestPartitionId, search);
		retVal.setCacheStatus(cacheStatus);
		return retVal;
	}

	@Override
	public Optional<Integer> getSearchTotal(String theUuid) {
		SearchTask task = myIdToSearchTask.get(theUuid);
		if (task != null) {
			return Optional.ofNullable(task.awaitInitialSync());
		}

		/*
		 * In case there is no running search, if the total is listed as accurate we know one is coming
		 * so let's wait a bit for it to show up
		 */
		TransactionTemplate txTemplate = new TransactionTemplate(myManagedTxManager);
		txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
		Optional<Search> search = mySearchCacheSvc.fetchByUuid(theUuid);
		if (search.isPresent()) {
			Optional<SearchParameterMap> searchParameterMap = search.get().getSearchParameterMap();
			if (searchParameterMap.isPresent() && searchParameterMap.get().getSearchTotalMode() == SearchTotalModeEnum.ACCURATE) {
				for (int i = 0; i < 10; i++) {
					if (search.isPresent()) {
						verifySearchHasntFailedOrThrowInternalErrorException(search.get());
						if (search.get().getTotalCount() != null) {
							return Optional.of(search.get().getTotalCount());
						}
					}
					search = mySearchCacheSvc.fetchByUuid(theUuid);
				}
			}
		}

		return Optional.empty();
	}

	@Nonnull
	private PersistedJpaSearchFirstPageBundleProvider submitSearch(IDao theCallingDao, SearchParameterMap theParams, String theResourceType, RequestDetails theRequestDetails, String theSearchUuid, ISearchBuilder theSb, String theQueryString, RequestPartitionId theRequestPartitionId, Search theSearch) {
		StopWatch w = new StopWatch();
		SearchTask task = new SearchTask(theSearch, theCallingDao, theParams, theResourceType, theRequestDetails, theRequestPartitionId);
		myIdToSearchTask.put(theSearch.getUuid(), task);
		task.call();

		PersistedJpaSearchFirstPageBundleProvider retVal = myPersistedJpaBundleProviderFactory.newInstanceFirstPage(theRequestDetails, theSearch, task, theSb);

		ourLog.debug("Search initial phase completed in {}ms", w.getMillis());
		return retVal;
	}

	@Nullable
	private PersistedJpaBundleProvider findCachedQuery(SearchParameterMap theParams, String theResourceType, RequestDetails theRequestDetails, String theQueryString, RequestPartitionId theRequestPartitionId) {
		TransactionTemplate txTemplate = new TransactionTemplate(myManagedTxManager);

		// May be null
		return txTemplate.execute(t -> {

			// Interceptor call: STORAGE_PRECHECK_FOR_CACHED_SEARCH
			HookParams params = new HookParams()
				.add(SearchParameterMap.class, theParams)
				.add(RequestDetails.class, theRequestDetails)
				.addIfMatchesType(ServletRequestDetails.class, theRequestDetails);
			Object outcome = CompositeInterceptorBroadcaster.doCallHooksAndReturnObject(myInterceptorBroadcaster, theRequestDetails, Pointcut.STORAGE_PRECHECK_FOR_CACHED_SEARCH, params);
			if (Boolean.FALSE.equals(outcome)) {
				return null;
			}

			// Check for a search matching the given hash
			Search searchToUse = findSearchToUseOrNull(theQueryString, theResourceType, theRequestPartitionId);
			if (searchToUse == null) {
				return null;
			}

			ourLog.debug("Reusing search {} from cache", searchToUse.getUuid());
			// Interceptor call: JPA_PERFTRACE_SEARCH_REUSING_CACHED
			params = new HookParams()
				.add(SearchParameterMap.class, theParams)
				.add(RequestDetails.class, theRequestDetails)
				.addIfMatchesType(ServletRequestDetails.class, theRequestDetails);
			CompositeInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequestDetails, Pointcut.JPA_PERFTRACE_SEARCH_REUSING_CACHED, params);

			return myPersistedJpaBundleProviderFactory.newInstance(theRequestDetails, searchToUse.getUuid());
		});
	}

	@Nullable
	private Search findSearchToUseOrNull(String theQueryString, String theResourceType, RequestPartitionId theRequestPartitionId) {
		// createdCutoff is in recent past
		final Instant createdCutoff = Instant.now().minus(myDaoConfig.getReuseCachedSearchResultsForMillis(), ChronoUnit.MILLIS);

		Optional<Search> candidate = mySearchCacheSvc.findCandidatesForReuse(theResourceType, theQueryString, createdCutoff, theRequestPartitionId);
		return candidate.orElse(null);
	}

	private IBundleProvider executeQuery(String theResourceType, SearchParameterMap theParams, RequestDetails theRequestDetails, String theSearchUuid, ISearchBuilder theSb, Integer theLoadSynchronousUpTo, RequestPartitionId theRequestPartitionId) {
		SearchRuntimeDetails searchRuntimeDetails = new SearchRuntimeDetails(theRequestDetails, theSearchUuid);
		searchRuntimeDetails.setLoadSynchronous(true);

		boolean wantOnlyCount = isWantOnlyCount(theParams);
		boolean wantCount = isWantCount(theParams, wantOnlyCount);

		// Execute the query and make sure we return distinct results
		TransactionTemplate txTemplate = new TransactionTemplate(myManagedTxManager);
		txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
		txTemplate.setReadOnly(theParams.isLoadSynchronous() || theParams.isOffsetQuery());
		return txTemplate.execute(t -> {

			// Load the results synchronously
			final List<ResourcePersistentId> pids = new ArrayList<>();

			Long count = 0L;
			if (wantCount) {
				ourLog.trace("Performing count");
				// TODO FulltextSearchSvcImpl will remove necessary parameters from the "theParams", this will cause actual query after count to
				//  return wrong response. This is some dirty fix to avoid that issue. Params should not be mutated?
				//  Maybe instead of removing them we could skip them in db query builder if full text search was used?
				List<List<IQueryParameterType>> contentAndTerms = theParams.get(Constants.PARAM_CONTENT);
				List<List<IQueryParameterType>> textAndTerms = theParams.get(Constants.PARAM_TEXT);

				Iterator<Long> countIterator = theSb.createCountQuery(theParams, theSearchUuid, theRequestDetails, theRequestPartitionId);

				if (contentAndTerms != null) theParams.put(Constants.PARAM_CONTENT, contentAndTerms);
				if (textAndTerms != null) theParams.put(Constants.PARAM_TEXT, textAndTerms);

				count = countIterator.next();
				ourLog.trace("Got count {}", count);
			}

			if (wantOnlyCount) {
				SimpleBundleProvider bundleProvider = new SimpleBundleProvider();
				bundleProvider.setSize(count.intValue());
				return bundleProvider;
			}

			try (IResultIterator resultIter = theSb.createQuery(theParams, searchRuntimeDetails, theRequestDetails, theRequestPartitionId)) {
				while (resultIter.hasNext()) {
					pids.add(resultIter.next());
					if (theLoadSynchronousUpTo != null && pids.size() >= theLoadSynchronousUpTo) {
						break;
					}
					if (theParams.getLoadSynchronousUpTo() != null && pids.size() >= theParams.getLoadSynchronousUpTo()) {
						break;
					}
				}
			} catch (IOException e) {
				ourLog.error("IO failure during database access", e);
				throw new InternalErrorException(e);
			}

			JpaPreResourceAccessDetails accessDetails = new JpaPreResourceAccessDetails(pids, () -> theSb);
			HookParams params = new HookParams()
				.add(IPreResourceAccessDetails.class, accessDetails)
				.add(RequestDetails.class, theRequestDetails)
				.addIfMatchesType(ServletRequestDetails.class, theRequestDetails);
			CompositeInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequestDetails, Pointcut.STORAGE_PREACCESS_RESOURCES, params);

			for (int i = pids.size() - 1; i >= 0; i--) {
				if (accessDetails.isDontReturnResourceAtIndex(i)) {
					pids.remove(i);
				}
			}

			/*
			 * For synchronous queries, we load all the includes right away
			 * since we're returning a static bundle with all the results
			 * pre-loaded. This is ok because synchronous requests are not
			 * expected to be paged
			 *
			 * On the other hand for async queries we load includes/revincludes
			 * individually for pages as we return them to clients
			 */

			// _includes
			Integer maxIncludes = myDaoConfig.getMaximumIncludesToLoadPerPage();
			final Set<ResourcePersistentId> includedPids = theSb.loadIncludes(myContext, myEntityManager, pids, theParams.getRevIncludes(), true, theParams.getLastUpdated(), "(synchronous)", theRequestDetails, maxIncludes);
			if (maxIncludes != null) {
				maxIncludes -= includedPids.size();
			}
			pids.addAll(includedPids);
			List<ResourcePersistentId> includedPidsList = new ArrayList<>(includedPids);

			// _revincludes
			if (theParams.getEverythingMode() == null && (maxIncludes == null || maxIncludes > 0)) {
				Set<ResourcePersistentId> revIncludedPids = theSb.loadIncludes(myContext, myEntityManager, pids, theParams.getIncludes(), false, theParams.getLastUpdated(), "(synchronous)", theRequestDetails, maxIncludes);
				includedPids.addAll(revIncludedPids);
				pids.addAll(revIncludedPids);
				includedPidsList.addAll(revIncludedPids);
			}

			List<IBaseResource> resources = new ArrayList<>();
			theSb.loadResourcesByPid(pids, includedPidsList, resources, false, theRequestDetails);
			// Hook: STORAGE_PRESHOW_RESOURCES
			resources = InterceptorUtil.fireStoragePreshowResource(resources, theRequestDetails, myInterceptorBroadcaster);

			SimpleBundleProvider bundleProvider = new SimpleBundleProvider(resources);
			if (theParams.isOffsetQuery()) {
				bundleProvider.setCurrentPageOffset(theParams.getOffset());
				bundleProvider.setCurrentPageSize(theParams.getCount());
			}

			if (wantCount) {
				bundleProvider.setSize(count.intValue());
			} else {
				Integer queryCount = getQueryCount(theLoadSynchronousUpTo, theParams);
				if (queryCount == null || queryCount > resources.size()) {
					// No limit, last page or everything was fetched within the limit
					bundleProvider.setSize(getTotalCount(queryCount, theParams.getOffset(), resources.size()));
				} else {
					bundleProvider.setSize(null);
				}
			}

			return bundleProvider;
		});
	}

	private int getTotalCount(Integer queryCount, Integer offset, int queryResultCount) {
		if (queryCount != null) {
			if (offset != null) {
				return offset + queryResultCount;
			} else {
				return queryResultCount;
			}
		} else {
			return queryResultCount;
		}
	}

	private Integer getQueryCount(Integer theLoadSynchronousUpTo, SearchParameterMap theParams) {
		if (theLoadSynchronousUpTo != null) {
			return theLoadSynchronousUpTo;
		} else if (theParams.getCount() != null) {
			return theParams.getCount();
		} else if (myDaoConfig.getFetchSizeDefaultMaximum() != null) {
			return myDaoConfig.getFetchSizeDefaultMaximum();
		}
		return null;
	}

	@Nullable
	private Integer getLoadSynchronousUpToOrNull(CacheControlDirective theCacheControlDirective) {
		final Integer loadSynchronousUpTo;
		if (theCacheControlDirective != null && theCacheControlDirective.isNoStore()) {
			if (theCacheControlDirective.getMaxResults() != null) {
				loadSynchronousUpTo = theCacheControlDirective.getMaxResults();
				if (loadSynchronousUpTo > myDaoConfig.getCacheControlNoStoreMaxResultsUpperLimit()) {
					throw new InvalidRequestException(Constants.HEADER_CACHE_CONTROL + " header " + Constants.CACHE_CONTROL_MAX_RESULTS + " value must not exceed " + myDaoConfig.getCacheControlNoStoreMaxResultsUpperLimit());
				}
			} else {
				loadSynchronousUpTo = 100;
			}
		} else {
			loadSynchronousUpTo = null;
		}
		return loadSynchronousUpTo;
	}

	@VisibleForTesting
	void setContextForUnitTest(FhirContext theCtx) {
		myContext = theCtx;
	}

	@VisibleForTesting
	void setDaoConfigForUnitTest(DaoConfig theDaoConfig) {
		myDaoConfig = theDaoConfig;
	}

	@VisibleForTesting
	void setEntityManagerForUnitTest(EntityManager theEntityManager) {
		myEntityManager = theEntityManager;
	}

	@VisibleForTesting
	public void setLoadingThrottleForUnitTests(Integer theLoadingThrottleForUnitTests) {
		myLoadingThrottleForUnitTests = theLoadingThrottleForUnitTests;
	}

	@VisibleForTesting
	public void setNeverUseLocalSearchForUnitTests(boolean theNeverUseLocalSearchForUnitTests) {
		myNeverUseLocalSearchForUnitTests = theNeverUseLocalSearchForUnitTests;
	}

	@VisibleForTesting
	public void setSyncSizeForUnitTests(int theSyncSize) {
		mySyncSize = theSyncSize;
	}

	@VisibleForTesting
	void setTransactionManagerForUnitTest(PlatformTransactionManager theTxManager) {
		myManagedTxManager = theTxManager;
	}

	@VisibleForTesting
	void setDaoRegistryForUnitTest(DaoRegistry theDaoRegistry) {
		myDaoRegistry = theDaoRegistry;
	}

	@VisibleForTesting
	void setInterceptorBroadcasterForUnitTest(IInterceptorBroadcaster theInterceptorBroadcaster) {
		myInterceptorBroadcaster = theInterceptorBroadcaster;
	}

	@VisibleForTesting
	public void setSearchBuilderFactoryForUnitTest(SearchBuilderFactory theSearchBuilderFactory) {
		mySearchBuilderFactory = theSearchBuilderFactory;
	}

	@VisibleForTesting
	public void setPersistedJpaBundleProviderFactoryForUnitTest(PersistedJpaBundleProviderFactory thePersistedJpaBundleProviderFactory) {
		myPersistedJpaBundleProviderFactory = thePersistedJpaBundleProviderFactory;
	}

	@VisibleForTesting
	public void setRequestPartitionHelperService(IRequestPartitionHelperSvc theRequestPartitionHelperService) {
		myRequestPartitionHelperService = theRequestPartitionHelperService;
	}

	private boolean isWantCount(SearchParameterMap myParams, boolean wantOnlyCount) {
		return wantOnlyCount ||
			SearchTotalModeEnum.ACCURATE.equals(myParams.getSearchTotalMode()) ||
			(myParams.getSearchTotalMode() == null && SearchTotalModeEnum.ACCURATE.equals(myDaoConfig.getDefaultTotalMode()));
	}

	/**
	 * A search task is a Callable task that runs in
	 * a thread pool to handle an individual search. One instance
	 * is created for any requested search and runs from the
	 * beginning to the end of the search.
	 * <p>
	 * Understand:
	 * This class executes in its own thread separate from the
	 * web server client thread that made the request. We do that
	 * so that we can return to the client as soon as possible,
	 * but keep the search going in the background (and have
	 * the next page of results ready to go when the client asks).
	 */
	public class SearchTask implements Callable<Void> {
		private final SearchParameterMap myParams;
		private final IDao myCallingDao;
		private final String myResourceType;
		private final ArrayList<ResourcePersistentId> mySyncedPids = new ArrayList<>();
		private final CountDownLatch myInitialCollectionLatch = new CountDownLatch(1);
		private final CountDownLatch myCompletionLatch;
		private final ArrayList<ResourcePersistentId> myUnsyncedPids = new ArrayList<>();
		private final RequestDetails myRequest;
		private final RequestPartitionId myRequestPartitionId;
		private final SearchRuntimeDetails mySearchRuntimeDetails;
		private final Transaction myParentTransaction;
		private Search mySearch;
		private boolean myAbortRequested;
		private int myCountSavedTotal = 0;
		private int myCountSavedThisPass = 0;
		private int myCountBlockedThisPass = 0;
		private boolean myAdditionalPrefetchThresholdsRemaining;
		private List<ResourcePersistentId> myPreviouslyAddedResourcePids;
		private Integer myMaxResultsToFetch;

		/**
		 * Constructor
		 */
		protected SearchTask(Search theSearch, IDao theCallingDao, SearchParameterMap theParams, String theResourceType, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) {
			mySearch = theSearch;
			myCallingDao = theCallingDao;
			myParams = theParams;
			myResourceType = theResourceType;
			myCompletionLatch = new CountDownLatch(1);
			mySearchRuntimeDetails = new SearchRuntimeDetails(theRequest, mySearch.getUuid());
			mySearchRuntimeDetails.setQueryString(theParams.toNormalizedQueryString(theCallingDao.getContext()));
			myRequestPartitionId = theRequestPartitionId;
			myRequest = theRequest;
			myParentTransaction = ElasticApm.currentTransaction();
		}

		/**
		 * This method is called by the server HTTP thread, and
		 * will block until at least one page of results have been
		 * fetched from the DB, and will never block after that.
		 */
		Integer awaitInitialSync() {
			ourLog.trace("Awaiting initial sync");
			do {
				ourLog.trace("Search {} aborted: {}", getSearch().getUuid(), !isNotAborted());
				if (AsyncUtil.awaitLatchAndThrowInternalErrorExceptionOnInterrupt(getInitialCollectionLatch(), 250L, TimeUnit.MILLISECONDS)) {
					break;
				}
			} while (getSearch().getStatus() == SearchStatusEnum.LOADING);
			ourLog.trace("Initial sync completed");

			return getSearch().getTotalCount();
		}

		protected Search getSearch() {
			return mySearch;
		}

		CountDownLatch getInitialCollectionLatch() {
			return myInitialCollectionLatch;
		}

		void setPreviouslyAddedResourcePids(List<ResourcePersistentId> thePreviouslyAddedResourcePids) {
			myPreviouslyAddedResourcePids = thePreviouslyAddedResourcePids;
			myCountSavedTotal = myPreviouslyAddedResourcePids.size();
		}

		private ISearchBuilder newSearchBuilder() {
			Class<? extends IBaseResource> resourceTypeClass = myContext.getResourceDefinition(myResourceType).getImplementingClass();
			return mySearchBuilderFactory.newSearchBuilder(myCallingDao, myResourceType, resourceTypeClass);
		}

		@Nonnull
		List<ResourcePersistentId> getResourcePids(int theFromIndex, int theToIndex) {
			ourLog.debug("Requesting search PIDs from {}-{}", theFromIndex, theToIndex);

			boolean keepWaiting;
			do {
				synchronized (mySyncedPids) {
					ourLog.trace("Search status is {}", mySearch.getStatus());
					boolean haveEnoughResults = mySyncedPids.size() >= theToIndex;
					if (!haveEnoughResults) {
						switch (mySearch.getStatus()) {
							case LOADING:
								keepWaiting = true;
								break;
							case PASSCMPLET:
								/*
								 * If we get here, it means that the user requested resources that crossed the
								 * current pre-fetch boundary. For example, if the prefetch threshold is 50 and the
								 * user has requested resources 0-60, then they would get 0-50 back but the search
								 * coordinator would then stop searching.SearchCoordinatorSvcImplTest
								 */
								keepWaiting = false;
								break;
							case FAILED:
							case FINISHED:
							case GONE:
							default:
								keepWaiting = false;
								break;
						}
					} else {
						keepWaiting = false;
					}
				}

				if (keepWaiting) {
					ourLog.info("Waiting as we only have {} results - Search status: {}", mySyncedPids.size(), mySearch.getStatus());
					AsyncUtil.sleep(500L);
				}
			} while (keepWaiting);

			ourLog.debug("Proceeding, as we have {} results", mySyncedPids.size());

			ArrayList<ResourcePersistentId> retVal = new ArrayList<>();
			synchronized (mySyncedPids) {
				verifySearchHasntFailedOrThrowInternalErrorException(mySearch);

				int toIndex = theToIndex;
				if (mySyncedPids.size() < toIndex) {
					toIndex = mySyncedPids.size();
				}
				for (int i = theFromIndex; i < toIndex; i++) {
					retVal.add(mySyncedPids.get(i));
				}
			}

			ourLog.trace("Done syncing results - Wanted {}-{} and returning {} of {}", theFromIndex, theToIndex, retVal.size(), mySyncedPids.size());

			return retVal;
		}

		void saveSearch() {
			TransactionTemplate txTemplate = new TransactionTemplate(myManagedTxManager);
			txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
			txTemplate.execute(new TransactionCallbackWithoutResult() {
				@Override
				protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theArg0) {
					doSaveSearch();
				}

			});
		}

		private void saveUnsynced(final IResultIterator theResultIter) {
			TransactionTemplate txTemplate = new TransactionTemplate(myManagedTxManager);
			txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
			txTemplate.execute(new TransactionCallbackWithoutResult() {
				@Override
				protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theArg0) {
					if (mySearch.getId() == null) {
						doSaveSearch();
					}

					ArrayList<ResourcePersistentId> unsyncedPids = myUnsyncedPids;
					int countBlocked = 0;

					// Interceptor call: STORAGE_PREACCESS_RESOURCES
					// This can be used to remove results from the search result details before
					// the user has a chance to know that they were in the results
					if (mySearchRuntimeDetails.getRequestDetails() != null && unsyncedPids.isEmpty() == false) {
						JpaPreResourceAccessDetails accessDetails = new JpaPreResourceAccessDetails(unsyncedPids, () -> newSearchBuilder());
						HookParams params = new HookParams()
							.add(IPreResourceAccessDetails.class, accessDetails)
							.add(RequestDetails.class, mySearchRuntimeDetails.getRequestDetails())
							.addIfMatchesType(ServletRequestDetails.class, mySearchRuntimeDetails.getRequestDetails());
						CompositeInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, myRequest, Pointcut.STORAGE_PREACCESS_RESOURCES, params);

						for (int i = unsyncedPids.size() - 1; i >= 0; i--) {
							if (accessDetails.isDontReturnResourceAtIndex(i)) {
								unsyncedPids.remove(i);
								myCountBlockedThisPass++;
								myCountSavedTotal++;
								countBlocked++;
							}
						}
					}

					// Actually store the results in the query cache storage
					myCountSavedTotal += unsyncedPids.size();
					myCountSavedThisPass += unsyncedPids.size();
					mySearchResultCacheSvc.storeResults(mySearch, mySyncedPids, unsyncedPids);

					synchronized (mySyncedPids) {
						int numSyncedThisPass = unsyncedPids.size();
						ourLog.trace("Syncing {} search results - Have more: {}", numSyncedThisPass, theResultIter.hasNext());
						mySyncedPids.addAll(unsyncedPids);
						unsyncedPids.clear();

						if (theResultIter.hasNext() == false) {
							int skippedCount = theResultIter.getSkippedCount();
							int nonSkippedCount = theResultIter.getNonSkippedCount();
							int totalFetched = skippedCount + myCountSavedThisPass + myCountBlockedThisPass;
							ourLog.trace("MaxToFetch[{}] SkippedCount[{}] CountSavedThisPass[{}] CountSavedThisTotal[{}] AdditionalPrefetchRemaining[{}]", myMaxResultsToFetch, skippedCount, myCountSavedThisPass, myCountSavedTotal, myAdditionalPrefetchThresholdsRemaining);

							if (nonSkippedCount == 0 || (myMaxResultsToFetch != null && totalFetched < myMaxResultsToFetch)) {
								ourLog.trace("Setting search status to FINISHED");
								mySearch.setStatus(SearchStatusEnum.FINISHED);
								mySearch.setTotalCount(myCountSavedTotal - countBlocked);
							} else if (myAdditionalPrefetchThresholdsRemaining) {
								ourLog.trace("Setting search status to PASSCMPLET");
								mySearch.setStatus(SearchStatusEnum.PASSCMPLET);
								mySearch.setSearchParameterMap(myParams);
							} else {
								ourLog.trace("Setting search status to FINISHED");
								mySearch.setStatus(SearchStatusEnum.FINISHED);
								mySearch.setTotalCount(myCountSavedTotal - countBlocked);
							}
						}
					}

					mySearch.setNumFound(myCountSavedTotal);
					mySearch.setNumBlocked(mySearch.getNumBlocked() + countBlocked);

					int numSynced;
					synchronized (mySyncedPids) {
						numSynced = mySyncedPids.size();
					}

					if (myDaoConfig.getCountSearchResultsUpTo() == null ||
						myDaoConfig.getCountSearchResultsUpTo() <= 0 ||
						myDaoConfig.getCountSearchResultsUpTo() <= numSynced) {
						myInitialCollectionLatch.countDown();
					}

					doSaveSearch();

				}
			});

		}

		boolean isNotAborted() {
			return myAbortRequested == false;
		}

		void markComplete() {
			myCompletionLatch.countDown();
		}

		CountDownLatch getCompletionLatch() {
			return myCompletionLatch;
		}

		/**
		 * Request that the task abort as soon as possible
		 */
		void requestImmediateAbort() {
			myAbortRequested = true;
		}

		/**
		 * This is the method which actually performs the search.
		 * It is called automatically by the thread pool.
		 */
		@Override
		public Void call() {
			StopWatch sw = new StopWatch();
			Span span = myParentTransaction.startSpan("db", "query", "search");
			span.setName("FHIR Database Search");
			try {
				// Create an initial search in the DB and give it an ID
				saveSearch();

				TransactionTemplate txTemplate = new TransactionTemplate(myManagedTxManager);
				txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

				if (myCustomIsolationSupported) {
					txTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
				}

				txTemplate.execute(new TransactionCallbackWithoutResult() {
					@Override
					protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) {
						doSearch();
					}
				});

				mySearchRuntimeDetails.setSearchStatus(mySearch.getStatus());
				if (mySearch.getStatus() == SearchStatusEnum.FINISHED) {
					HookParams params = new HookParams()
						.add(RequestDetails.class, myRequest)
						.addIfMatchesType(ServletRequestDetails.class, myRequest)
						.add(SearchRuntimeDetails.class, mySearchRuntimeDetails);
					CompositeInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, myRequest, Pointcut.JPA_PERFTRACE_SEARCH_COMPLETE, params);
				} else {
					HookParams params = new HookParams()
						.add(RequestDetails.class, myRequest)
						.addIfMatchesType(ServletRequestDetails.class, myRequest)
						.add(SearchRuntimeDetails.class, mySearchRuntimeDetails);
					CompositeInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, myRequest, Pointcut.JPA_PERFTRACE_SEARCH_PASS_COMPLETE, params);
				}

				ourLog.trace("Have completed search for [{}{}] and found {} resources in {}ms - Status is {}", mySearch.getResourceType(), mySearch.getSearchQueryString(), mySyncedPids.size(), sw.getMillis(), mySearch.getStatus());

			} catch (Throwable t) {

				/*
				 * Don't print a stack trace for client errors (i.e. requests that
				 * aren't valid because the client screwed up).. that's just noise
				 * in the logs and who needs that.
				 */
				boolean logged = false;
				if (t instanceof BaseServerResponseException) {
					BaseServerResponseException exception = (BaseServerResponseException) t;
					if (exception.getStatusCode() >= 400 && exception.getStatusCode() < 500) {
						logged = true;
						ourLog.warn("Failed during search due to invalid request: {}", t.toString());
					}
				}

				if (!logged) {
					ourLog.error("Failed during search loading after {}ms", sw.getMillis(), t);
				}
				myUnsyncedPids.clear();
				Throwable rootCause = ExceptionUtils.getRootCause(t);
				rootCause = defaultIfNull(rootCause, t);

				String failureMessage = rootCause.getMessage();

				int failureCode = InternalErrorException.STATUS_CODE;
				if (t instanceof BaseServerResponseException) {
					failureCode = ((BaseServerResponseException) t).getStatusCode();
				}

				if (System.getProperty(UNIT_TEST_CAPTURE_STACK) != null) {
					failureMessage += "\nStack\n" + ExceptionUtils.getStackTrace(rootCause);
				}

				mySearch.setFailureMessage(failureMessage);
				mySearch.setFailureCode(failureCode);
				mySearch.setStatus(SearchStatusEnum.FAILED);

				mySearchRuntimeDetails.setSearchStatus(mySearch.getStatus());
				HookParams params = new HookParams()
					.add(RequestDetails.class, myRequest)
					.addIfMatchesType(ServletRequestDetails.class, myRequest)
					.add(SearchRuntimeDetails.class, mySearchRuntimeDetails);
				CompositeInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, myRequest, Pointcut.JPA_PERFTRACE_SEARCH_FAILED, params);

				saveSearch();
				span.captureException(t);
			} finally {

				myIdToSearchTask.remove(mySearch.getUuid());
				myInitialCollectionLatch.countDown();
				markComplete();
				span.end();

			}
			return null;
		}

		private void doSaveSearch() {
			Search newSearch = mySearchCacheSvc.save(mySearch);

			// mySearchDao.save is not supposed to return null, but in unit tests
			// it can if the mock search dao isn't set up to handle that
			if (newSearch != null) {
				mySearch = newSearch;
			}
		}

		/**
		 * This method actually creates the database query to perform the
		 * search, and starts it.
		 */
		private void doSearch() {
			/*
			 * If the user has explicitly requested a _count, perform a
			 *
			 * SELECT COUNT(*) ....
			 *
			 * before doing anything else.
			 */
			boolean wantOnlyCount = isWantOnlyCount(myParams);
			boolean wantCount = isWantCount(myParams, wantOnlyCount);
			if (wantCount) {
				ourLog.trace("Performing count");
				ISearchBuilder sb = newSearchBuilder();

				/*
				 * createCountQuery
				 * NB: (see createQuery below)
				 * Because FulltextSearchSvcImpl will (internally)
				 * mutate the myParams (searchmap),
				 * (specifically removing the _content and _text filters)
				 * we will have to clone those parameters here so that
				 * the "correct" params are used in createQuery below
				 */
				Iterator<Long> countIterator = sb.createCountQuery(myParams.clone(), mySearch.getUuid(), myRequest, myRequestPartitionId);
				Long count = countIterator.hasNext() ? countIterator.next() : 0L;
				ourLog.trace("Got count {}", count);

				TransactionTemplate txTemplate = new TransactionTemplate(myManagedTxManager);
				txTemplate.execute(new TransactionCallbackWithoutResult() {
					@Override
					protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theArg0) {
						mySearch.setTotalCount(count.intValue());
						if (wantOnlyCount) {
							mySearch.setStatus(SearchStatusEnum.FINISHED);
						}
						doSaveSearch();
					}
				});
				if (wantOnlyCount) {
					return;
				}
			}

			ourLog.trace("Done count");
			ISearchBuilder sb = newSearchBuilder();

			/*
			 * Figure out how many results we're actually going to fetch from the
			 * database in this pass. This calculation takes into consideration the
			 * "pre-fetch thresholds" specified in DaoConfig#getSearchPreFetchThresholds()
			 * as well as the value of the _count parameter.
			 */
			int currentlyLoaded = defaultIfNull(mySearch.getNumFound(), 0);
			int minWanted = 0;
			if (myParams.getCount() != null) {
				minWanted = myParams.getCount();
				minWanted = Math.min(minWanted, myPagingProvider.getMaximumPageSize());
				minWanted += currentlyLoaded;
			}

			for (Iterator<Integer> iter = myDaoConfig.getSearchPreFetchThresholds().iterator(); iter.hasNext(); ) {
				int next = iter.next();
				if (next != -1 && next <= currentlyLoaded) {
					continue;
				}

				if (next == -1) {
					sb.setMaxResultsToFetch(null);
				} else {
					myMaxResultsToFetch = Math.max(next, minWanted);
					sb.setMaxResultsToFetch(myMaxResultsToFetch);
				}

				if (iter.hasNext()) {
					myAdditionalPrefetchThresholdsRemaining = true;
				}

				// If we get here's we've found an appropriate threshold
				break;
			}

			/*
			 * Provide any PID we loaded in previous search passes to the
			 * SearchBuilder so that we don't get duplicates coming from running
			 * the same query again.
			 *
			 * We could possibly accomplish this in a different way by using sorted
			 * results in our SQL query and specifying an offset. I don't actually
			 * know if that would be faster or not. At some point should test this
			 * idea.
			 */
			if (myPreviouslyAddedResourcePids != null) {
				sb.setPreviouslyAddedResourcePids(myPreviouslyAddedResourcePids);
				mySyncedPids.addAll(myPreviouslyAddedResourcePids);
			}

			/*
			 * createQuery
			 * Construct the SQL query we'll be sending to the database
			 *
			 * NB: (See createCountQuery above)
			 * We will pass the original myParams here (not a copy)
			 * because we actually _want_ the mutation of the myParams to happen.
			 * Specifically because SearchBuilder itself will _expect_
			 * not to have these parameters when dumping back
			 * to our DB.
			 *
			 * This is an odd implementation behaviour, but the change
			 * for this will require a lot more handling at higher levels
			 */
			try (IResultIterator resultIterator = sb.createQuery(myParams, mySearchRuntimeDetails, myRequest, myRequestPartitionId)) {
				assert (resultIterator != null);

				/*
				 * The following loop actually loads the PIDs of the resources
				 * matching the search off of the disk and into memory. After
				 * every X results, we commit to the HFJ_SEARCH table.
				 */
				int syncSize = mySyncSize;
				while (resultIterator.hasNext()) {
					myUnsyncedPids.add(resultIterator.next());

					boolean shouldSync = myUnsyncedPids.size() >= syncSize;

					if (myDaoConfig.getCountSearchResultsUpTo() != null &&
						myDaoConfig.getCountSearchResultsUpTo() > 0 &&
						myDaoConfig.getCountSearchResultsUpTo() < myUnsyncedPids.size()) {
						shouldSync = false;
					}

					if (myUnsyncedPids.size() > 50000) {
						shouldSync = true;
					}

					// If no abort was requested, bail out
					Validate.isTrue(isNotAborted(), "Abort has been requested");

					if (shouldSync) {
						saveUnsynced(resultIterator);
					}

					if (myLoadingThrottleForUnitTests != null) {
						AsyncUtil.sleep(myLoadingThrottleForUnitTests);
					}

				}

				// If no abort was requested, bail out
				Validate.isTrue(isNotAborted(), "Abort has been requested");

				saveUnsynced(resultIterator);

			} catch (IOException e) {
				ourLog.error("IO failure during database access", e);
				throw new InternalErrorException(e);
			}
		}
	}

	public class SearchContinuationTask extends SearchTask {

		public SearchContinuationTask(Search theSearch, IDao theCallingDao, SearchParameterMap theParams, String theResourceType, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) {
			super(theSearch, theCallingDao, theParams, theResourceType, theRequest, theRequestPartitionId);
		}

		@Override
		public Void call() {
			try {
				TransactionTemplate txTemplate = new TransactionTemplate(myManagedTxManager);
				txTemplate.afterPropertiesSet();
				txTemplate.execute(t -> {
					List<ResourcePersistentId> previouslyAddedResourcePids = mySearchResultCacheSvc.fetchAllResultPids(getSearch());
					if (previouslyAddedResourcePids == null) {
						throw newResourceGoneException(getSearch().getUuid());
					}

					ourLog.trace("Have {} previously added IDs in search: {}", previouslyAddedResourcePids.size(), getSearch().getUuid());
					setPreviouslyAddedResourcePids(previouslyAddedResourcePids);
					return null;
				});
			} catch (Throwable e) {
				ourLog.error("Failure processing search", e);
				getSearch().setFailureMessage(e.getMessage());
				getSearch().setStatus(SearchStatusEnum.FAILED);
				if (e instanceof BaseServerResponseException) {
					getSearch().setFailureCode(((BaseServerResponseException) e).getStatusCode());
				}

				saveSearch();
				return null;
			}

			return super.call();
		}

	}

	private static boolean isWantOnlyCount(SearchParameterMap myParams) {
		return SummaryEnum.COUNT.equals(myParams.getSummaryMode())
			| INTEGER_0.equals(myParams.getCount());
	}

	public static void populateSearchEntity(SearchParameterMap theParams, String theResourceType, String theSearchUuid, String theQueryString, Search theSearch, RequestPartitionId theRequestPartitionId) {
		theSearch.setDeleted(false);
		theSearch.setUuid(theSearchUuid);
		theSearch.setCreated(new Date());
		theSearch.setTotalCount(null);
		theSearch.setNumFound(0);
		theSearch.setPreferredPageSize(theParams.getCount());
		theSearch.setSearchType(theParams.getEverythingMode() != null ? SearchTypeEnum.EVERYTHING : SearchTypeEnum.SEARCH);
		theSearch.setLastUpdated(theParams.getLastUpdated());
		theSearch.setResourceType(theResourceType);
		theSearch.setStatus(SearchStatusEnum.LOADING);
		theSearch.setSearchQueryString(theQueryString, theRequestPartitionId);

		if (theParams.hasIncludes()) {
			for (Include next : theParams.getIncludes()) {
				theSearch.addInclude(new SearchInclude(theSearch, next.getValue(), false, next.isRecurse()));
			}
		}

		for (Include next : theParams.getRevIncludes()) {
			theSearch.addInclude(new SearchInclude(theSearch, next.getValue(), true, next.isRecurse()));
		}
	}

	/**
	 * Creates a {@link Pageable} using a start and end index
	 */
	@SuppressWarnings("WeakerAccess")
	@Nullable
	public static Pageable toPage(final int theFromIndex, int theToIndex) {
		int pageSize = theToIndex - theFromIndex;
		if (pageSize < 1) {
			return null;
		}

		int pageIndex = theFromIndex / pageSize;

		Pageable page = new AbstractPageRequest(pageIndex, pageSize) {
			private static final long serialVersionUID = 1L;

			@Override
			public long getOffset() {
				return theFromIndex;
			}

			@Override
			public Sort getSort() {
				return Sort.unsorted();
			}

			@Override
			public Pageable next() {
				return null;
			}

			@Override
			public Pageable previous() {
				return null;
			}

			@Override
			public Pageable first() {
				return null;
			}

			@Override
			public Pageable withPage(int theI) {
				return null;
			}
		};

		return page;
	}

	static void verifySearchHasntFailedOrThrowInternalErrorException(Search theSearch) {
		if (theSearch.getStatus() == SearchStatusEnum.FAILED) {
			Integer status = theSearch.getFailureCode();
			status = defaultIfNull(status, 500);

			String message = theSearch.getFailureMessage();
			throw BaseServerResponseException.newInstance(status, message);
		}
	}

}


text:
namespace Kafka.Client.Cfg
{
    using System.Configuration;
    using System.Globalization;
    using System.Net;

    public class BrokerConfigurationElement : ConfigurationElement
    {
        protected override void PostDeserialize()
        {
            base.PostDeserialize();
            IPAddress ipAddress;
            if (!IPAddress.TryParse(this.Host, out ipAddress))
            {
                var addresses = Dns.GetHostAddresses(this.Host);
                if (addresses.Length > 0)
                {
                    this.Host = addresses[0].ToString();
                }
                else
                {
                    throw new ConfigurationErrorsException(string.Format(CultureInfo.CurrentCulture, "Could not resolve the address: {0}.", this.Host));
                }
            }
        }

        [ConfigurationProperty("id", IsRequired = true)]
        public int Id
        {
            get
            {
                return (int)this["id"];
            }
        }

        [ConfigurationProperty("host", IsRequired = true)]
        public string Host
        {
            get
            {
                return (string)this["host"];
            }

            set
            {
                this["host"] = value;
            }
        }

        [ConfigurationProperty("port", IsRequired = true)]
        public int Port
        {
            get
            {
                return (int)this["port"];
            }

            set
            {
                this["port"] = value;
            }
        }

        public string GetBrokerPartitionInfoAsString()
        {
            return string.Format(CultureInfo.InvariantCulture, "{0}:{1}:{2}", Id, Host, Port);
        }
    }
}


text:
<?php
/**
 * Created by JetBrains PhpStorm.
 * User: admin
 * Date: 05.10.2012
 * Time: 18:18
 * To change this template use File | Settings | File Templates.
 */

namespace Danvbe\ResmanBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;

/**
 * @ORM\Entity(repositoryClass="Danvbe\ResmanBundle\Repository\TypeOfConnectionTypeRepository")
 * @ORM\Table(name="rm_type_of_connection_type")
 */
class TypeOfConnectionType
{
    /**
     * @var integer $id
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @var string $name
     * @ORM\Column(type="string", length=100)
     */
    protected $type;

    /**
     * @var ArrayCollection $connection_types
     * @ORM\OneToMany(targetEntity="ConnectionType", mappedBy="type")
     */
    protected $connection_types;

    public function __construct()
    {
        $this->connection_types = new ArrayCollection();
    }

    ///////////////////////////////////
    //GENERATED CODE - CAN BE REMOVED//
    ///////////////////////////////////
    /**
     * Get id
     *
     * @return integer
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set type
     *
     * @param string $type
     * @return TypeOfConnectionType
     */
    public function setType($type)
    {
        $this->type = $type;

        return $this;
    }

    /**
     * Get type
     *
     * @return string
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * Add connection_types
     *
     * @param Danvbe\ResmanBundle\Entity\ConnectionType $connectionTypes
     * @return TypeOfConnectionType
     */
    public function addConnectionType(\Danvbe\ResmanBundle\Entity\ConnectionType $connectionTypes)
    {
        $this->connection_types[] = $connectionTypes;

        return $this;
    }

    /**
     * Remove connection_types
     *
     * @param Danvbe\ResmanBundle\Entity\ConnectionType $connectionTypes
     */
    public function removeConnectionType(\Danvbe\ResmanBundle\Entity\ConnectionType $connectionTypes)
    {
        $this->connection_types->removeElement($connectionTypes);
    }

    /**
     * Get connection_types
     *
     * @return Doctrine\Common\Collections\Collection
     */
    public function getConnectionTypes()
    {
        return $this->connection_types;
    }
}


text:
* {
  padding: 0;
  margin: 0;
  box-sizing: border-box; }

body {
  margin: 10px auto 20px;
  padding: 20px;
  font-size: 14px;
  font-family: Roboto, sans-serif;
  font-weight: 300;
  color: #666;
  width: 720px; }

strong {
  font-weight: 400; }

section#about {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  padding: 3px 20px;
  font-size: 90%;
  background: #eee;
  border-bottom: 1px solid #d3d3d3;
  z-index: 2; }
  section#about > div {
    margin: 0 auto;
    width: 660px; }
    section#about > div > span#copyright {
      float: right; }

div.box {
  margin-top: 30px; }
  div.box label {
    display: inline-block;
    vertical-align: top;
    font-weight: 400;
    margin-right: 15px; }
  div.box input {
    display: inline-block;
    vertical-align: top; }
    div.box input[type='checkbox'] {
      margin-top: 3px; }
  div.box select {
    display: inline-block;
    vertical-align: top;
    margin-top: -4px; }
  div.box span.subLabel {
    display: inline-block;
    vertical-align: top;
    font-style: italic;
    margin: 0 10px 0 20px; }
  div.box label + span.subLabel {
    margin-left: 0; }
  div.box input[type='range'] + span {
    margin-left: 5px; }
    div.box input[type='range'] + span::after {
      content: attr(data-unit);
      margin-left: 2px;
      font-size: 90%; }
  div.box p {
    font-size: 90%;
    color: #aaa;
    margin-top: 7px;
    font-style: italic; }

button#save {
  display: inline-block;
  margin-top: 50px;
  outline: none;
  border: none;
  text-shadow: none;
  box-shadow: none;
  color: #fff;
  background: #7b5fa4;
  padding: 3px 12px 4px;
  border-radius: 3px;
  cursor: pointer; }

button#restore {
  display: inline-block;
  outline: none;
  border: none;
  text-shadow: none;
  box-shadow: none;
  background: transparent;
  padding: 0 7px;
  margin-left: 15px;
  vertical-align: middle;
  font-size: 90%;
  cursor: pointer;
  transition: background 0.5s; }
  button#restore:hover {
    background: rgba(0, 0, 0, 0.05); }

p#copyright {
  position: absolute;
  right: 20px;
  bottom: 20px;
  font-size: 90%;
  color: #aaa; }

div#content {
  transition: opacity 0.3s; }

div#savedMsg, div#restoreMsg {
  position: fixed;
  top: calc(50% - 50px);
  left: 0;
  width: 100%;
  height: 55px;
  padding: 15px 0;
  font-size: 140%;
  text-align: center;
  color: #fff;
  background: #5eba7d;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s; }

body.saved div#content, body.restored div#content {
  opacity: 0.2;
  pointer-events: none; }

body.saved div#savedMsg {
  opacity: 1;
  pointer-events: auto; }

body.restored div#restoreMsg {
  opacity: 1;
  pointer-events: auto; }

/*# sourceMappingURL=settings.css.map */


text:
//------------------------------------------------------------------------------
// <auto-generated>
//     This code was not generated by a tool. but for stylecop suppression.
// </auto-generated>
//------------------------------------------------------------------------------
using Moq;

namespace StrixIT.Platform.Modules.Membership.Tests
{
    public class GroupServiceMock
    {
        #region Private Fields

        private Mock<IMembershipDataSource> _dataSourceMock = new Mock<IMembershipDataSource>();
        private Mock<IGroupManager> _groupManagerMock = new Mock<IGroupManager>();
        private IGroupService _groupService;
        private Mock<IRoleManager> _roleManagerMock = new Mock<IRoleManager>();
        private Mock<IUserManager> _userManagerMock = new Mock<IUserManager>();

        #endregion Private Fields

        #region Public Constructors

        public GroupServiceMock()
        {
            _groupService = new GroupService(_dataSourceMock.Object, _groupManagerMock.Object, _userManagerMock.Object, _roleManagerMock.Object);
        }

        #endregion Public Constructors

        #region Properties

        public Mock<IMembershipDataSource> DataSourceMock
        {
            get
            {
                return _dataSourceMock;
            }
        }

        public Mock<IGroupManager> GroupManagerMock
        {
            get
            {
                return _groupManagerMock;
            }
        }

        public IGroupService GroupService
        {
            get
            {
                return _groupService;
            }
        }

        public Mock<IRoleManager> RoleManagerMock
        {
            get
            {
                return _roleManagerMock;
            }
        }

        public Mock<IUserManager> UserManagerMock
        {
            get
            {
                return _userManagerMock;
            }
        }

        #endregion Properties
    }
}


text:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;

class Solution {
    public boolean pyramidTransition(String bottom, List<String> allowed) {
        HashMap<String, HashSet<Character>> cache = new HashMap<>();
        for (String a : allowed) {
            String k = a.substring(0, 2);
            cache.putIfAbsent(k, new HashSet<>());
            cache.get(k).add(a.charAt(2));
        }

        ArrayList<HashSet<Character>> list = new ArrayList<>();
        for (int i = 0; i < bottom.length(); i++) {
            HashSet<Character> tmp = new HashSet<>();
            tmp.add(bottom.charAt(i));
            list.add(tmp);
        }

        while (list.size() > 1) {
            ArrayList<HashSet<Character>> next = new ArrayList<>();
            for (int i = 0; i < list.size() - 1; i++) {
                HashSet<Character> tmp = new HashSet<>();
                for (Character m : list.get(i)) {
                    for (Character n : list.get(i + 1)) {
                        String k = String.valueOf(m) + String.valueOf(n);
                        if (cache.containsKey(k)) tmp.addAll(cache.get(k));
                    }
                }
                if (tmp.size() == 0) return false;
                next.add(tmp);
            }
            list = next;
        }
        return true;
    }
}


text:
using namespace morda;

renderer::renderer(std::unique_ptr<render_factory> factory, const renderer::params& params) :
		factory(std::move(factory)),
		shader(this->factory->create_shaders()),
		quad_01_vbo(this->factory->create_vertex_buffer(utki::make_span(std::array<r4::vector2<float>, 4>({{
			r4::vector2<float>(0, 0), r4::vector2<float>(0, 1), r4::vector2<float>(1, 1), r4::vector2<float>(1, 0)
		}})))),
		quad_indices(this->factory->create_index_buffer(utki::make_span(std::array<std::uint16_t, 4>({{0, 1, 2, 3}})))),
		pos_quad_01_vao(this->factory->create_vertex_array({this->quad_01_vbo}, this->quad_indices, vertex_array::mode::triangle_fan)),
		pos_tex_quad_01_vao(this->factory->create_vertex_array({this->quad_01_vbo, this->quad_01_vbo}, this->quad_indices, vertex_array::mode::triangle_fan)),
		max_texture_size(params.max_texture_size),
		initial_matrix(params.initial_matrix)
{
}

void renderer::set_framebuffer(std::shared_ptr<frame_buffer> fb){
	this->set_framebuffer_internal(fb.get());
	this->curFB = std::move(fb);
}


text:
cask 'bettertouchtool' do
  if MacOS.version <= :snow_leopard
    version '0.939'
    sha256 'fad5e9d36259c379bdb33188cf15d179fd9ff73023035c98f5734e7e3e13bb75'

    # bettertouchtool.net was verified as official when first introduced to the cask
    url "https://bettertouchtool.net/btt#{version}.zip"

    license :gratis
  else
    version '1.84'
    sha256 'f405f7e3252fc97e08c2e2e9e0122c81bc7337bae3cb3aba40ecdfaedc4e189f'

    url "https://boastr.net/releases/btt#{version}.zip"
    appcast 'http://appcast.boastr.net',
            checkpoint: '53420365a59bf4b6bf96e270fda99d1cc339be77b2c34002f4c3a76571674f1e'

    license :commercial
  end

  name 'BetterTouchTool'
  homepage 'https://boastr.net/'

  auto_updates true
  accessibility_access true

  app 'BetterTouchTool.app'

  uninstall login_item: 'BetterTouchTool'

  zap delete: [
                '~/Library/Preferences/com.hegenberg.BetterTouchTool.plist',
                '~/Library/Application Support/BetterTouchTool',
              ]
end


text:
:root {
  --font: 'Fira Sans', Arial, sans-serif;
  --accentColor: #33c1e3;
  --textColor: #414141;
}
@custom-media --mobile-widths-only (max-width: 767px);
@custom-media --tablet-widths (min-width: 768px);
@custom-media --desktop-widths (min-width: 1200px);