<commit_before>/*
 * Copyright 2012-2014 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.samplestack.dbclient;

import static com.marklogic.samplestack.SamplestackConstants.QUESTIONS_DIRECTORY;
import static com.marklogic.samplestack.SamplestackConstants.QUESTIONS_OPTIONS;
import static com.marklogic.samplestack.SamplestackConstants.SEARCH_RESPONSE_TRANSFORM;
import static com.marklogic.samplestack.SamplestackConstants.SINGLE_QUESTION_OPTIONS;

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.UUID;

import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.marklogic.client.MarkLogicIOException;
import com.marklogic.client.Transaction;
import com.marklogic.client.document.DocumentMetadataPatchBuilder.Call;
import com.marklogic.client.document.DocumentPage;
import com.marklogic.client.document.DocumentPatchBuilder;
import com.marklogic.client.document.DocumentPatchBuilder.Position;
import com.marklogic.client.document.JSONDocumentManager;
import com.marklogic.client.document.ServerTransform;
import com.marklogic.client.io.DocumentMetadataHandle.Capability;
import com.marklogic.client.io.JacksonHandle;
import com.marklogic.client.io.ValuesHandle;
import com.marklogic.client.io.marker.DocumentPatchHandle;
import com.marklogic.client.query.QueryDefinition;
import com.marklogic.client.query.QueryManager;
import com.marklogic.client.query.QueryManager.QueryView;
import com.marklogic.client.query.RawCombinedQueryDefinition;
import com.marklogic.client.query.RawQueryDefinition;
import com.marklogic.client.query.ValuesDefinition;
import com.marklogic.samplestack.SamplestackConstants;
import com.marklogic.samplestack.SamplestackConstants.ISO8601Formatter;
import com.marklogic.samplestack.domain.Answer;
import com.marklogic.samplestack.domain.Comment;
import com.marklogic.samplestack.domain.Contributor;
import com.marklogic.samplestack.domain.InitialQuestion;
import com.marklogic.samplestack.domain.QnADocument;
import com.marklogic.samplestack.domain.SparseContributor;
import com.marklogic.samplestack.exception.SampleStackDataIntegrityException;
import com.marklogic.samplestack.exception.SamplestackIOException;
import com.marklogic.samplestack.exception.SamplestackNotFoundException;
import com.marklogic.samplestack.exception.SamplestackSearchException;
import com.marklogic.samplestack.security.ClientRole;
import com.marklogic.samplestack.service.ContributorService;
import com.marklogic.samplestack.service.QnAService;

@Component
/**
 * Implementation of the QnAService interface.
 */
public class MarkLogicQnAService extends MarkLogicBaseService implements
		QnAService {

	@Autowired
	private ContributorService contributorService;

	private final Logger logger = LoggerFactory
			.getLogger(MarkLogicQnAService.class);

	private static String idFromUri(String uri) {
		return uri.replace("/questions/", "").replace(".json", "");
	}

	private static String uriFromId(String id) {
		return "/questions/" + id + ".json";
	}

	private String generateUri() {

		return QUESTIONS_DIRECTORY + UUID.randomUUID() + ".json";

	}

	/**
	 * Start a transaction
	 * 
	 * @param role
	 *            Role to search with
	 * @return A transaction to use in subsequent calls to MarkLogic
	 */
	private Transaction startTransaction(ClientRole role) {
		return clients.get(role).openTransaction();
	}

	@Override
	public QnADocument findOne(ClientRole role, String queryString, long start) {
		QueryManager queryManager = queryManager(role);
		QueryDefinition stringQuery = queryManager.newStringDefinition(
				SINGLE_QUESTION_OPTIONS).withCriteria(queryString);

		stringQuery.setDirectory(QUESTIONS_DIRECTORY);
		DocumentPage page = jsonDocumentManager(role)
				.search(stringQuery, start);
		if (page.hasNext()) {
			JacksonHandle handle = new JacksonHandle();
			handle = page.nextContent(handle);
			QnADocument newDocument = new QnADocument((ObjectNode) handle.get());
			return newDocument;
		} else {
			return null;
		}
	}

	@Override
	public QnADocument ask(Contributor user, InitialQuestion question) {
		String documentUri = generateUri();
		question.setId(idFromUri(documentUri));

		Date now = new Date();
		question.setCreationDate(now);
		question.updateLastActivityDate();
		question.setAcceptedAnswerId(null);
		question.setOwner(user.asSparseContributor());
		question.setComments(new Comment[0]);
		question.setAnswers(new Answer[0]);
		question.setAnswerCount(0);

		JsonNode jsonNode = mapper.convertValue(question, JsonNode.class);
		jsonDocumentManager(ClientRole.SAMPLESTACK_CONTRIBUTOR).write(
				documentUri, new JacksonHandle(jsonNode));

		return new QnADocument((ObjectNode) getJsonDocument(
				ClientRole.SAMPLESTACK_CONTRIBUTOR, documentUri));
	}

	@Override
	/**
	 * Uses DocumentPatchBuilder to send a change to the QnADocument JSON.
	 * @param contributor
	 * 			The owner of the answer.
	 * @param toAnswerId
	 * 			The ID of the question that's being answererd.
	 * @param answer
	 * 			Markdown of this answer's body.
	 */
	public QnADocument answer(Contributor contributor, String toAnswerId,
			String answerText) {
		String documentUri = uriFromId(toAnswerId);
		DocumentPatchBuilder patchBuilder = jsonDocumentManager(
				ClientRole.SAMPLESTACK_CONTRIBUTOR).newPatchBuilder();

		Answer answer = new Answer();
		answer.setText(answerText);
		answer.setId(UUID.randomUUID().toString());
		answer.setItemTally(0);
		answer.setComments(new ArrayList<Comment>());
		answer.setCreationDate(new Date());

		// put ths sparse contributor data on this node
		SparseContributor owner = contributor.asSparseContributor();
		answer.setOwner(owner);

		try {
			patchBuilder.insertFragment("/node('answers')",
					Position.LAST_CHILD, mapper.writeValueAsString(answer));
			patchBuilder.replaceValue("/lastActivityDate",
					ISO8601Formatter.format(new Date()));
			Call call = patchBuilder.call().add(1);
			patchBuilder.replaceValue("/answerCount", call);
			DocumentPatchHandle patch = patchBuilder.build();
			logger.debug(patch.toString());
			jsonDocumentManager(ClientRole.SAMPLESTACK_CONTRIBUTOR).patch(
					documentUri, patch);
		} catch (MarkLogicIOException e) {
			throw new SamplestackIOException(e);
		} catch (JsonProcessingException e) {
			throw new SamplestackIOException(e);
		}
		return get(ClientRole.SAMPLESTACK_CONTRIBUTOR, idFromUri(documentUri));
	}

	@Override
	/**
	 * Use DocumentPatchBuilder to accept an answer.<commit_msg>Fixes issue with PR 261<commit_after>/*
 * Copyright 2012-2014 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.samplestack.dbclient;

import static com.marklogic.samplestack.SamplestackConstants.QUESTIONS_DIRECTORY;
import static com.marklogic.samplestack.SamplestackConstants.QUESTIONS_OPTIONS;
import static com.marklogic.samplestack.SamplestackConstants.SEARCH_RESPONSE_TRANSFORM;
import static com.marklogic.samplestack.SamplestackConstants.SINGLE_QUESTION_OPTIONS;

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.UUID;

import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.marklogic.client.MarkLogicIOException;
import com.marklogic.client.Transaction;
import com.marklogic.client.document.DocumentMetadataPatchBuilder.Call;
import com.marklogic.client.document.DocumentPage;
import com.marklogic.client.document.DocumentPatchBuilder;
import com.marklogic.client.document.DocumentPatchBuilder.Position;
import com.marklogic.client.document.JSONDocumentManager;
import com.marklogic.client.document.ServerTransform;
import com.marklogic.client.io.DocumentMetadataHandle.Capability;
import com.marklogic.client.io.JacksonHandle;
import com.marklogic.client.io.ValuesHandle;
import com.marklogic.client.io.marker.DocumentPatchHandle;
import com.marklogic.client.query.QueryDefinition;
import com.marklogic.client.query.QueryManager;
import com.marklogic.client.query.QueryManager.QueryView;
import com.marklogic.client.query.RawCombinedQueryDefinition;
import com.marklogic.client.query.RawQueryDefinition;
import com.marklogic.client.query.ValuesDefinition;
import com.marklogic.samplestack.SamplestackConstants;
import com.marklogic.samplestack.SamplestackConstants.ISO8601Formatter;
import com.marklogic.samplestack.domain.Answer;
import com.marklogic.samplestack.domain.Comment;
import com.marklogic.samplestack.domain.Contributor;
import com.marklogic.samplestack.domain.InitialQuestion;
import com.marklogic.samplestack.domain.QnADocument;
import com.marklogic.samplestack.domain.SparseContributor;
import com.marklogic.samplestack.exception.SampleStackDataIntegrityException;
import com.marklogic.samplestack.exception.SamplestackIOException;
import com.marklogic.samplestack.exception.SamplestackNotFoundException;
import com.marklogic.samplestack.exception.SamplestackSearchException;
import com.marklogic.samplestack.security.ClientRole;
import com.marklogic.samplestack.service.ContributorService;
import com.marklogic.samplestack.service.QnAService;

@Component
/**
 * Implementation of the QnAService interface.
 */
public class MarkLogicQnAService extends MarkLogicBaseService implements
		QnAService {

	@Autowired
	private ContributorService contributorService;

	private final Logger logger = LoggerFactory
			.getLogger(MarkLogicQnAService.class);

	private static String idFromUri(String uri) {
		return uri.replace("/questions/", "").replace(".json", "");
	}

	private static String uriFromId(String id) {
		return "/questions/" + id + ".json";
	}

	private String generateUri() {

		return QUESTIONS_DIRECTORY + UUID.randomUUID() + ".json";

	}

	/**
	 * Start a transaction
	 * 
	 * @param role
	 *            Role to search with
	 * @return A transaction to use in subsequent calls to MarkLogic
	 */
	private Transaction startTransaction(ClientRole role) {
		return clients.get(role).openTransaction();
	}

	@Override
	public QnADocument findOne(ClientRole role, String queryString, long start) {
		QueryManager queryManager = queryManager(role);
		QueryDefinition stringQuery = queryManager.newStringDefinition(
				SINGLE_QUESTION_OPTIONS).withCriteria(queryString);

		stringQuery.setDirectory(QUESTIONS_DIRECTORY);
		DocumentPage page = jsonDocumentManager(role)
				.search(stringQuery, start);
		if (page.hasNext()) {
			JacksonHandle handle = new JacksonHandle();
			handle = page.nextContent(handle);
			QnADocument newDocument = new QnADocument((ObjectNode) handle.get());
			return newDocument;
		} else {
			return null;
		}
	}

	@Override
	public QnADocument ask(Contributor user, InitialQuestion question) {
		String documentUri = generateUri();
		question.setId(idFromUri(documentUri));

		Date now = new Date();
		question.setCreationDate(now);
		question.updateLastActivityDate();
		question.setAcceptedAnswerId(null);
		question.setOwner(user.asSparseContributor());
		question.setComments(new Comment[0]);
		question.setAnswers(new Answer[0]);
		question.setAnswerCount(0);

		JsonNode jsonNode = mapper.convertValue(question, JsonNode.class);
		jsonDocumentManager(ClientRole.SAMPLESTACK_CONTRIBUTOR).write(
				documentUri, new JacksonHandle(jsonNode));

		return new QnADocument((ObjectNode) getJsonDocument(
				ClientRole.SAMPLESTACK_CONTRIBUTOR, documentUri));
	}

	@Override
	/**
	 * Uses DocumentPatchBuilder to send a change to the QnADocument JSON.
	 * @param contributor
	 * 			The owner of the answer.
	 * @param toAnswerId
	 * 			The ID of the question that's being answererd.
	 * @param answer
	 * 			Markdown of this answer's body.
	 */
	public QnADocument answer(Contributor contributor, String toAnswerId,
			String answerText) {
		String documentUri = uriFromId(toAnswerId);
		DocumentPatchBuilder patchBuilder = jsonDocumentManager(
				ClientRole.SAMPLESTACK_CONTRIBUTOR).newPatchBuilder();

		Answer answer = new Answer();
		answer.setText(answerText);
		answer.setId(UUID.randomUUID().toString());
		answer.setItemTally(0);
		answer.setComments(new ArrayList<Comment>());
		answer.setCreationDate(new Date());

		// put ths sparse contributor data on this node
		SparseContributor owner = contributor.asSparseContributor();
		answer.setOwner(owner);

		try {
			patchBuilder.insertFragment("/node('answers')",
					Position.LAST_CHILD, mapper.writeValueAsString(answer));
			patchBuilder.replaceValue("/lastActivityDate",
					ISO8601Formatter.format(new Date()));
			Call call = patchBuilder.call().add(1);
			patchBuilder.replaceApply("/answerCount", call);
			DocumentPatchHandle patch = patchBuilder.build();
			logger.debug(patch.toString());
			jsonDocumentManager(ClientRole.SAMPLESTACK_CONTRIBUTOR).patch(
					documentUri, patch);
		} catch (MarkLogicIOException e) {
			throw new SamplestackIOException(e);
		} catch (JsonProcessingException e) {
			throw new SamplestackIOException(e);
		}
		return get(ClientRole.SAMPLESTACK_CONTRIBUTOR, idFromUri(documentUri));
	}

	@Override
	/**
	 * Use DocumentPatchBuilder to accept an answer.
{'fraction_non_alphanumeric': 0.09534127843986999, 'fraction_numerical': 0.0038596966413867824, 'mean_word_length': 5.673746046091279, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 8}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2775112', 'n_tokens_mistral': 4722, 'n_tokens_neox': 4250, 'n_words': 909}