All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.marklogic.mgmt.resource.tasks.TaskManager Maven / Gradle / Ivy

Go to download

Java client for the MarkLogic REST Management API and for deploying applications to MarkLogic

There is a newer version: 5.0.0
Show newest version
/*
 * Copyright (c) 2023 MarkLogic Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.marklogic.mgmt.resource.tasks;

import com.marklogic.mgmt.ManageClient;
import com.marklogic.mgmt.PayloadParser;
import com.marklogic.mgmt.SaveReceipt;
import com.marklogic.mgmt.api.API;
import com.marklogic.mgmt.api.task.Task;
import com.marklogic.mgmt.resource.AbstractResourceManager;
import com.marklogic.mgmt.resource.requests.RequestManager;
import com.marklogic.rest.util.Fragment;

import java.net.URI;
import java.util.ArrayList;
import java.util.List;

/**
 * The "groupName" property of this class corresponds to the "group-id" querystring parameter. It's called
 * "groupName" because "group-id" is misleading - it's a name, not an ID.
 */
public class TaskManager extends AbstractResourceManager {

	private String groupName = "Default";

	public TaskManager(ManageClient client) {
		super(client);
	}

	public TaskManager(ManageClient client, String groupName) {
		super(client);
		this.groupName = groupName;
	}

	/**
	 * Tasks are tricky because their unique field is "task-id", but that's generated by MarkLogic - it's not expected
	 * to be in a payload. So this method has to do some extra work to determine what the task-id is for the given
	 * payload. It does so by looking for an existing task with the same task-path and task-database as what's in the
	 * given payload. If one exists, then the task-id from the existing task is used. Otherwise, null is returned.
	 *
	 * @param payload
	 * @return
	 */
	@Override
	protected String getResourceId(String payload) {
		final String taskId = payloadParser.getPayloadFieldValue(payload, "task-id", false);
		if (taskId != null) {
			return taskId;
		}

		final String taskPath = payloadParser.getPayloadFieldValue(payload, "task-path");
		final String taskDatabase = payloadParser.getPayloadFieldValue(payload, "task-database", false);

		final String xpath = taskDatabase != null ?
			format("/t:tasks-default-list/t:list-items/t:list-item[t:task-path = '%s' and t:task-database = '%s']/t:idref", taskPath, taskDatabase) :
			format("/t:tasks-default-list/t:list-items/t:list-item[t:task-path = '%s']/t:idref", taskPath);

		final List resourceIds = getAsXml().getElementValues(xpath);
		if (resourceIds == null || resourceIds.isEmpty()) {
			return null;
		}

		// Check each matching resource ID until we fine one with the same taskRoot
		final String taskRoot = payloadParser.getPayloadFieldValue(payload, "task-root", false);
		if (taskRoot == null) {
			throw new RuntimeException("Unable to determine ID for task, as multiple existing tasks have the same " +
				"task-path and task-database, but payload is missing a task-root to determine which existing task is " +
				"the same root; payload: " + payload);
		}

		for (String resourceId : resourceIds) {
			String json = getManageClient().getJson(appendGroupId(super.getResourcesPath() + "/" + resourceId + "/properties"));
			String thisTaskRoot = payloadParser.getPayloadFieldValue(json, "task-root", false);
			if (taskRoot.equals(thisTaskRoot)) {
				return resourceId;
			}
		}

		// If no matching task has the same taskRoot, then this is a new task
		return null;
	}

	@Override
	public String getResourcesPath() {
		return appendGroupId(super.getResourcesPath());
	}

	protected String appendGroupId(String path) {
		if (groupName != null) {
			if (path.contains("?")) {
				return path + "&group-id=" + groupName;
			}
			return path + "?group-id=" + groupName;
		}
		return path;
	}

	@Override
	public String getResourcePath(String resourceNameOrId, String... resourceUrlParams) {
		return super.getResourcesPath() + "/" + getTaskIdForTaskPath(resourceNameOrId);
	}

	@Override
	protected String[] getUpdateResourceParams(String payload) {
		List params = new ArrayList<>();
		params.add("group-id");
		params.add(groupName);
		return params.toArray(new String[]{});
	}

	@Override
	protected String getIdFieldName() {
		return "task-id";
	}

	/**
	 * Previously, this method only accepted a task-path because that was previously used as the unique ID for a task.
	 * But since two or more tasks can have the same task-path, this class now uses task-id. To preserve backwards
	 * compatibility then, this method accepts a task-path or a task-id.
	 *
	 * @param taskPathOrTaskId
	 * @return
	 */
	public String getTaskIdForTaskPath(String taskPathOrTaskId) {
		Fragment f = getAsXml();
		String xpath = "/t:tasks-default-list/t:list-items/t:list-item[t:task-path = '%s' or t:idref = '%s']/t:idref";
		xpath = String.format(xpath, taskPathOrTaskId, taskPathOrTaskId);
		List resourceIds = f.getElementValues(xpath);
		if (resourceIds == null || resourceIds.isEmpty()) {
			throw new RuntimeException("Could not find a scheduled task with a task-path or task-id of: " + taskPathOrTaskId);
		}
		if (resourceIds.size() == 1) {
			return resourceIds.get(0);
		}
		throw new RuntimeException(format("Found multiple task IDs with the same task-path of %s; IDs: %s", taskPathOrTaskId, resourceIds));
	}

	/**
	 * This accounts for an existing task with either task-path or task-id equal to the given resourceNameOrId.
	 * This preserves backwards compatibility to when this class used task-path as the ID property.
	 *
	 * @param resourceNameOrId
	 * @param resourceUrlParams
	 * @return
	 */
	@Override
	public boolean exists(String resourceNameOrId, String... resourceUrlParams) {
		if (logger.isInfoEnabled()) {
			logger.info("Checking for existence of resource: " + resourceNameOrId);
		}
		Fragment f = getAsXml();
		return f.elementExists(format(
			"/t:tasks-default-list/t:list-items/t:list-item[t:task-path = '%s' or t:idref = '%s']",
			resourceNameOrId, resourceNameOrId));
	}

	/**
	 * When a task is being created, its resourceId - its task-id - will always be null because MarkLogic generates it.
	 * This method only needs the resourceId for logging purposes. So it's overridden so that the task-path can be used
	 * as the resourceId so that the logging is more useful.
	 *
	 * @param payload
	 * @param resourceId
	 * @return
	 */
	@Override
	protected SaveReceipt createNewResource(String payload, String resourceId) {
		final String taskPath = payloadParser.getPayloadFieldValue(payload, "task-path", false);

		SaveReceipt receipt = super.createNewResource(payload, taskPath);
		updateNewTaskIfItShouldBeDisabled(payload, receipt);
		return receipt;
	}

	/**
	 * This accounts for a bug in the Manage API where when a new task is created and it has task-enabled=false, the
	 * task isn't actually disabled. So an update call is made to the task right after it's created.
	 *
	 * @param payload
	 * @param receipt
	 */
	protected void updateNewTaskIfItShouldBeDisabled(String payload, SaveReceipt receipt) {
		String enabled = payloadParser.getPayloadFieldValue(payload, "task-enabled", false);
		if ("false".equalsIgnoreCase(enabled)) {
			// We don't reuse updateResource here since that first deletes the task
			URI uri = receipt.getResponse().getHeaders().getLocation();
			// Expecting a path of "/manage/(version)/tasks/(taskId)"
			String[] tokens = uri.getPath().split("/");
			final String taskId = tokens[tokens.length - 1];

			Task task = new Task(new API(getManageClient()), taskId);
			task.setTaskEnabled(false);

			String path = getPropertiesPath(taskId);
			path = appendParamsAndValuesToPath(path, getUpdateResourceParams(payload));
			logger.info("Updating new scheduled task so it is disabled; task ID: " + taskId);
			putPayload(getManageClient(), path, task.getJson());
		}
	}

	/**
	 * Per ticket #367, when a task is updated, it is first deleted. This is to workaround Manage API behavior which
	 * does not allow for any property besides "task-enabled" to be updated on a scheduled task. But it's often handy
	 * during a deployment to update some other property of a scheduled task. Unfortunately, that throws an error.
	 * 

* So to work around that, when a scheduled task is updated, it's first deleted. Then the task is created, which * includes making another call to disable the task if task-enabled is set to false. * * @param payload * @param resourceId * @return */ @Override public SaveReceipt updateResource(String payload, String resourceId) { logger.info("Deleting scheduled task first since updates are not allowed except for task-enabled; task ID: " + resourceId); deleteByIdField(resourceId); PayloadParser parser = new PayloadParser(); payload = parser.excludeProperties(payload, "task-id"); String taskPath = parser.getPayloadFieldValue(payload, "task-path"); SaveReceipt receipt = super.createNewResource(payload, taskPath); updateNewTaskIfItShouldBeDisabled(payload, receipt); return receipt; } public List getTaskPaths() { return getAsXml().getListItemValues("task-path"); } public void disableAllTasks() { for (String id : getAsXml().getListItemIdRefs()) { disableTask(id); } } public void enableAllTasks() { for (String id : getAsXml().getListItemIdRefs()) { enableTask(id); } } public void disableTask(String taskId) { String json = format("{\"task-id\":\"%s\", \"task-enabled\":false}", taskId); String path = appendGroupId(super.getResourcesPath() + "/" + taskId + "/properties"); putPayload(getManageClient(), path, json); } public void enableTask(String taskId) { String json = format("{\"task-id\":\"%s\", \"task-enabled\":true}", taskId); String path = appendGroupId(super.getResourcesPath() + "/" + taskId + "/properties"); putPayload(getManageClient(), path, json); } public void deleteAllTasks() { deleteAllScheduledTasks(); } /** * @param taskPath */ // Deprecated since 4.0.2, as the taskPath may not suffice for uniquely identifying a task. // This now assumes a task root of '/' to at least avoid an error being thrown. @Deprecated public void deleteTaskWithPath(String taskPath) { deleteTaskWithPath(taskPath, "/"); } public void deleteTaskWithPath(String taskPath, String taskRoot) { String json = format("{\"task-path\":\"%s\", \"task-root\":\"%s\"}", taskPath, taskRoot); delete(json, "group-id", groupName); } public String getTaskId(String taskPath) { return getAsXml().getElementValue(format( "/t:tasks-default-list/t:list-items/t:list-item[t:task-path = '%s']/t:idref", taskPath) ); } public void deleteAllScheduledTasks() { for (String id : getAsXml().getListItemIdRefs()) { deleteAtPath(appendGroupId(super.getResourcesPath() + "/" + id)); } } public void waitForTasksToComplete(String group, int retryInMilliseconds) { Fragment servers = getManageClient().getXml("/manage/v2/task-servers"); String taskServerId = servers.getElementValue(format("//ts:list-item[ts:groupnameref = '%s']/ts:idref", group)); if (taskServerId == null) { logger.warn(format("Could not find task server ID for group %s, so not waiting for tasks to complete", group)); return; } RequestManager mgr = new RequestManager(getManageClient()); if (logger.isInfoEnabled()) { logger.info("Waiting for tasks to complete on task server"); } int count = mgr.getRequestCountForRelationId(taskServerId); while (count > 0) { if (logger.isInfoEnabled()) { logger.info("Waiting for tasks to complete on task server, count: " + count); } try { Thread.sleep(retryInMilliseconds); } catch (InterruptedException e) { } count = mgr.getRequestCountForRelationId(taskServerId); } if (logger.isInfoEnabled()) { logger.info("Finished waiting for tasks to complete on task server"); } } public void setGroupName(String groupName) { this.groupName = groupName; } public String getGroupName() { return groupName; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy