--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.core.activity.db.LogMapper">
+
+ <insert id="insert" parameterType="Log" useGeneratedKeys="false" lang="raw">
+ insert into logs
+ (created_at, log_type,execution_time_field,user_login,data_field, log_message)
+ values (#{createdAt}, #{type}, #{executionTime}, #{author}, #{data}, #{message})
+ </insert>
+
+ <select id="selectByKey" parameterType="map" resultType="Log" lang="raw">
+ SELECT
+ l.created_at as "createdAt",
+ l.log_type as "type",
+ l.execution_time_field as "executionTime",
+ l.user_login as "author",
+ l.data_field as "data",
+ l.log_message as "message"
+ FROM logs l
+ WHERE l.created_at=#{key.createdAt}
+ AND l.user_login=#{key.author}
+ AND l.log_type=#{key.type}
+ </select>
+
+ <select id="selectAll" parameterType="map" resultType="Log" lang="raw">
+ SELECT
+ l.created_at as "createdAt",
+ l.log_type as "type",
+ l.execution_time_field as "executionTime",
+ l.user_login as "author",
+ l.data_field as "data",
+ l.log_message as "message"
+ FROM logs l
+ </select>
+</mapper>
+
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.activity;
+
+import org.sonar.core.activity.Activity;
+import org.sonar.core.activity.ActivityLog;
+import org.sonar.core.activity.db.ActivityDto;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.server.activity.index.ActivityIndex;
+import org.sonar.server.activity.index.ActivityQuery;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.search.IndexClient;
+import org.sonar.server.search.QueryOptions;
+import org.sonar.server.search.Result;
+import org.sonar.server.user.UserSession;
+
+import java.util.List;
+
+/**
+ * Log service is used to log Activity classes which represents an event to DB and Index.
+ *
+ * @see org.sonar.core.activity.ActivityLog
+ * @since 4.4
+ */
+public class ActivityService {
+
+ private final DbClient dbClient;
+ private final IndexClient indexClient;
+
+ public ActivityService(DbClient dbClient, IndexClient indexClient) {
+ this.dbClient = dbClient;
+ this.indexClient = indexClient;
+ }
+
+ private String getAuthor() {
+ return (UserSession.get().login() != null) ? UserSession.get().login() : "UNKNOWN";
+ }
+
+ private void save(DbSession session, ActivityDto log) {
+ dbClient.activityDao().insert(session,
+ log.setAuthor(getAuthor()));
+ }
+
+ public void write(DbSession session, Activity.Type type, String message) {
+ this.write(session, type, message, null);
+ }
+
+ public void write(DbSession session, Activity.Type type, String message, Integer time) {
+ this.save(session, ActivityDto.createFor(message)
+ .setType(type)
+ .setExecutionTime(time));
+ }
+
+ public <L extends ActivityLog> void write(DbSession session, Activity.Type type, List<L> logs) {
+ for (ActivityLog log : logs) {
+ this.write(session, type, log);
+ }
+ }
+
+ public <L extends ActivityLog> void write(DbSession session, Activity.Type type, L log) {
+ this.save(session, ActivityDto.createFor(log)
+ .setType(type));
+ }
+
+ public ActivityQuery newActivityQuery() {
+ return new ActivityQuery();
+ }
+
+ public Result<Activity> search(ActivityQuery query, QueryOptions options) {
+ return indexClient.get(ActivityIndex.class).search(query, options);
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.activity.db;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.sonar.api.utils.System2;
+import org.sonar.core.activity.db.ActivityDto;
+import org.sonar.core.activity.db.ActivityKey;
+import org.sonar.core.activity.db.ActivityMapper;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.server.db.BaseDao;
+import org.sonar.server.search.IndexDefinition;
+
+import java.util.List;
+
+/**
+ * @since 4.4
+ */
+public class ActivityDao extends BaseDao<ActivityMapper, ActivityDto, ActivityKey> {
+
+ public ActivityDao() {
+ this(System2.INSTANCE);
+ }
+
+ @VisibleForTesting
+ public ActivityDao(System2 system) {
+ super(IndexDefinition.LOG, ActivityMapper.class, system);
+ }
+
+ @Override
+ protected ActivityDto doGetNullableByKey(DbSession session, ActivityKey key) {
+ return mapper(session).selectByKey(key);
+ }
+
+ @Override
+ protected ActivityDto doInsert(DbSession session, ActivityDto item) {
+ mapper(session).insert(item);
+ return item;
+ }
+
+ @Override
+ protected ActivityDto doUpdate(DbSession session, ActivityDto item) {
+ throw new IllegalStateException("Cannot update Log!");
+ }
+
+ @Override
+ protected void doDeleteByKey(DbSession session, ActivityKey key) {
+ throw new IllegalStateException("Cannot delete Log!");
+ }
+
+ public List<ActivityDto> findAll(DbSession session) {
+ return mapper(session).selectAll();
+ }
+
+ @Override
+ public void synchronizeAfter(DbSession session, long timestamp) {
+ throw new IllegalStateException("Log Index does not synchronize!");
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.activity.index;
+
+import org.apache.commons.lang.builder.ReflectionToStringBuilder;
+import org.sonar.core.activity.Activity;
+import org.sonar.server.search.BaseDoc;
+
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * @since 4.4
+ */
+public class ActivityDoc extends BaseDoc implements Activity {
+
+ protected ActivityDoc(Map<String, Object> fields) {
+ super(fields);
+ }
+
+ @Override
+ public Date time() {
+ return this.getField(ActivityNormalizer.LogFields.DATE.field());
+ }
+
+ @Override
+ public String author() {
+ return this.getField(ActivityNormalizer.LogFields.AUTHOR.field());
+ }
+
+ @Override
+ public Integer executionTime() {
+ return this.getField(ActivityNormalizer.LogFields.EXECUTION.field());
+ }
+
+ @Override
+ public Map<String, String> details() {
+ return this.getField(ActivityNormalizer.LogFields.DETAILS.field());
+ }
+
+ @Override
+ public String message() {
+ return this.getField(ActivityNormalizer.LogFields.MESSAGE.field());
+ }
+
+ @Override
+ public String toString() {
+ return ReflectionToStringBuilder.toString(this);
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.activity.index;
+
+import org.elasticsearch.action.search.SearchRequestBuilder;
+import org.elasticsearch.action.search.SearchResponse;
+import org.elasticsearch.action.search.SearchType;
+import org.elasticsearch.common.settings.ImmutableSettings;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.unit.TimeValue;
+import org.elasticsearch.index.query.QueryBuilders;
+import org.sonar.core.activity.Activity;
+import org.sonar.core.activity.db.ActivityDto;
+import org.sonar.core.activity.db.ActivityKey;
+import org.sonar.core.cluster.WorkQueue;
+import org.sonar.core.profiling.Profiling;
+import org.sonar.server.search.BaseIndex;
+import org.sonar.server.search.ESNode;
+import org.sonar.server.search.IndexDefinition;
+import org.sonar.server.search.IndexField;
+import org.sonar.server.search.QueryOptions;
+import org.sonar.server.search.Result;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @since 4.4
+ */
+public class ActivityIndex extends BaseIndex<Activity, ActivityDto, ActivityKey> {
+
+ public ActivityIndex(Profiling profiling, ActivityNormalizer normalizer, WorkQueue workQueue, ESNode node) {
+ super(IndexDefinition.LOG, normalizer, workQueue, node, profiling);
+ }
+
+ @Override
+ protected String getKeyValue(ActivityKey key) {
+ // FIXME too many collision with key.toString() due to lack of time precision
+ return null;// return key.toString();
+ }
+
+ @Override
+ protected Map mapKey() {
+ return null;
+ // Map<String, Object> mapping = new HashMap<String, Object>();
+ // return mapping;
+ }
+
+ @Override
+ protected Settings getIndexSettings() throws IOException {
+ return ImmutableSettings.builder().build();
+ }
+
+ @Override
+ protected Map mapProperties() {
+ Map<String, Object> mapping = new HashMap<String, Object>();
+ for (IndexField field : ActivityNormalizer.LogFields.ALL_FIELDS) {
+ mapping.put(field.field(), mapField(field));
+ }
+ return mapping;
+ }
+
+ @Override
+ protected Activity toDoc(final Map<String, Object> fields) {
+ return new ActivityDoc(fields);
+ }
+
+ public Result<Activity> findAll() {
+ return new Result<Activity>(this, getClient().prepareSearch(this.getIndexName())
+ .setQuery(QueryBuilders.matchAllQuery())
+ .setTypes(this.getIndexType())
+ .setSize(Integer.MAX_VALUE)
+ .get());
+ }
+
+ public Result<Activity> search(ActivityQuery query, QueryOptions options) {
+ SearchRequestBuilder esSearch = getClient()
+ .prepareSearch(this.getIndexName())
+ .setTypes(this.getIndexType())
+ .setIndices(this.getIndexName());
+
+ // TODO implement query and filters based on LogQuery
+ esSearch.setQuery(QueryBuilders.matchAllQuery());
+
+ if (options.isScroll()) {
+ esSearch.setSearchType(SearchType.SCAN);
+ esSearch.setScroll(TimeValue.timeValueMinutes(3));
+ }
+
+ SearchResponse esResult = esSearch.get();
+
+ return new Result<Activity>(this, esResult);
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.activity.index;
+
+import com.google.common.collect.ImmutableList;
+import org.elasticsearch.action.support.replication.ReplicationType;
+import org.elasticsearch.action.update.UpdateRequest;
+import org.sonar.api.utils.KeyValueFormat;
+import org.sonar.core.activity.db.ActivityDto;
+import org.sonar.core.activity.db.ActivityKey;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.search.BaseNormalizer;
+import org.sonar.server.search.IndexDefinition;
+import org.sonar.server.search.IndexField;
+import org.sonar.server.search.Indexable;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @since 4.4
+ */
+public class ActivityNormalizer extends BaseNormalizer<ActivityDto, ActivityKey> {
+
+
+ public static final class LogFields extends Indexable {
+
+ public final static IndexField KEY = addSortableAndSearchable(IndexField.Type.STRING, "key");
+ public final static IndexField TYPE = addSortable(IndexField.Type.STRING, "type");
+ public final static IndexField DATE = addSortable(IndexField.Type.DATE, "date");
+ public final static IndexField EXECUTION = add(IndexField.Type.NUMERIC, "executionTime");
+ public final static IndexField AUTHOR = addSearchable(IndexField.Type.STRING, "author");
+ public final static IndexField DETAILS = addSearchable(IndexField.Type.OBJECT, "details");
+ public final static IndexField MESSAGE = addSearchable(IndexField.Type.STRING, "message");
+
+ public static Set<IndexField> ALL_FIELDS = getAllFields();
+
+ private static Set<IndexField> getAllFields() {
+ Set<IndexField> fields = new HashSet<IndexField>();
+ for (Field classField : LogFields.class.getDeclaredFields()) {
+ if (Modifier.isFinal(classField.getModifiers()) && Modifier.isStatic(classField.getModifiers())) {
+ try {
+ fields.add(IndexField.class.cast(classField.get(null)));
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ return fields;
+ }
+ }
+
+ public ActivityNormalizer(DbClient db) {
+ super(IndexDefinition.LOG, db);
+ }
+
+ @Override
+ public List<UpdateRequest> normalize(ActivityKey activityKey) {
+ DbSession dbSession = db.openSession(false);
+ List<UpdateRequest> requests = new ArrayList<UpdateRequest>();
+ try {
+ requests.addAll(normalize(db.activityDao().getNullableByKey(dbSession, activityKey)));
+ } finally {
+ dbSession.close();
+ }
+ return requests;
+ }
+
+ @Override
+ public List<UpdateRequest> normalize(ActivityDto dto) {
+
+ Map<String, Object> logDoc = new HashMap<String, Object>();
+ logDoc.put(LogFields.KEY.field(), dto.getKey());
+ logDoc.put(LogFields.TYPE.field(), dto.getType());
+ logDoc.put(LogFields.AUTHOR.field(), dto.getAuthor());
+ logDoc.put(LogFields.MESSAGE.field(), dto.getMessage());
+ logDoc.put(LogFields.EXECUTION.field(), dto.getExecutionTime());
+ logDoc.put(LogFields.DATE.field(), dto.getCreatedAt());
+
+ logDoc.put(LogFields.DETAILS.field(), KeyValueFormat.parse(dto.getData()));
+
+ /* Creating updateRequest */
+ return ImmutableList.of(new UpdateRequest()
+ //Need to make a UUID because Key does not insure unicity
+ .replicationType(ReplicationType.ASYNC)
+ .doc(logDoc)
+ .upsert(logDoc));
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.activity.index;
+
+import org.sonar.core.activity.Activity;
+
+import java.util.Collection;
+import java.util.Date;
+
+/**
+ * @since 4.4
+ */
+public class ActivityQuery {
+
+ private Date since;
+ private Date to;
+ private Collection<Activity.Type> types;
+
+ public ActivityQuery() {
+ }
+
+ public Date getSince() {
+ return since;
+ }
+
+ public void setSince(Date since) {
+ this.since = since;
+ }
+
+ public Date getTo() {
+ return to;
+ }
+
+ public void setTo(Date to) {
+ this.to = to;
+ }
+
+ public Collection<Activity.Type> getTypes() {
+ return types;
+ }
+
+ public void setTypes(Collection<Activity.Type> types) {
+ this.types = types;
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.activity.ws;
+
+import org.sonar.api.server.ws.WebService;
+
+public class ActivitiesWebService implements WebService {
+
+ public static final String API_ENDPOINT = "api/activities";
+
+ private final SearchAction search;
+
+ public ActivitiesWebService(SearchAction search) {
+ this.search = search;
+ }
+
+ @Override
+ public void define(Context context) {
+ NewController controller = context
+ .createController(API_ENDPOINT)
+ .setDescription("Logs search and views");
+
+ search.define(controller);
+ controller.done();
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.activity.ws;
+
+import org.sonar.api.resources.Languages;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.core.activity.Activity;
+import org.sonar.server.activity.index.ActivityNormalizer;
+import org.sonar.server.search.ws.BaseMapping;
+import org.sonar.server.text.MacroInterpreter;
+
+import java.util.Map;
+
+/**
+ * Conversion between Log and WS JSON response
+ */
+public class ActivityMapping extends BaseMapping {
+
+
+ public ActivityMapping(Languages languages, MacroInterpreter macroInterpreter) {
+ super();
+ addIndexStringField("key", ActivityNormalizer.LogFields.KEY.field());
+ addIndexStringField("type", ActivityNormalizer.LogFields.TYPE.field());
+ addIndexDatetimeField("createdAt", ActivityNormalizer.LogFields.DATE.field());
+ addIndexStringField("userLogin", ActivityNormalizer.LogFields.AUTHOR.field());
+ addIndexStringField("message", ActivityNormalizer.LogFields.MESSAGE.field());
+ addIndexStringField("executionTime", ActivityNormalizer.LogFields.EXECUTION.field());
+ addField("details", new DetailField());
+ }
+
+ private static class DetailField extends IndexField<Activity> {
+ DetailField() {
+ super(ActivityNormalizer.LogFields.DETAILS.field());
+ }
+
+ @Override
+ public void write(JsonWriter json, Activity activity) {
+ json.name("details").beginObject();
+ for (Map.Entry<String, String> detail : activity.details().entrySet()) {
+ json.prop(detail.getKey(), detail.getValue());
+ }
+ json.endObject();
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.activity.ws;
+
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.RequestHandler;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.core.activity.Activity;
+import org.sonar.server.activity.ActivityService;
+import org.sonar.server.activity.index.ActivityDoc;
+import org.sonar.server.activity.index.ActivityQuery;
+import org.sonar.server.search.QueryOptions;
+import org.sonar.server.search.Result;
+import org.sonar.server.search.ws.SearchOptions;
+
+/**
+ * @since 4.4
+ */
+public class SearchAction implements RequestHandler {
+
+ public static final String PARAM_TYPE = "type";
+
+ public static final String SEARCH_ACTION = "search";
+
+ private final ActivityService logService;
+ private final ActivityMapping mapping;
+
+ public SearchAction(ActivityService logService, ActivityMapping mapping) {
+ this.logService = logService;
+ this.mapping = mapping;
+ }
+
+ void define(WebService.NewController controller) {
+ WebService.NewAction action = controller
+ .createAction(SEARCH_ACTION)
+ .setDescription("Search for a logs")
+ .setSince("4.4")
+ .setHandler(this);
+
+ // Other parameters
+ action.createParam(PARAM_TYPE)
+ .setDescription("Select types of log to search")
+ .setPossibleValues(Activity.Type.values())
+ .setDefaultValue(StringUtils.join(Activity.Type.values(), ","));
+
+ // Generic search parameters
+ SearchOptions.defineFieldsParam(action, mapping.supportedFields());
+
+ SearchOptions.definePageParams(action);
+ }
+
+ @Override
+ public void handle(Request request, Response response) {
+ ActivityQuery query = createLogQuery(logService.newActivityQuery(), request);
+ SearchOptions searchOptions = SearchOptions.create(request);
+ QueryOptions queryOptions = mapping.newQueryOptions(searchOptions);
+
+ Result<Activity> results = logService.search(query, queryOptions);
+
+ JsonWriter json = response.newJsonWriter().beginObject();
+ searchOptions.writeStatistics(json, results);
+ writeLogs(results, json, searchOptions);
+ json.endObject().close();
+ }
+
+ public static ActivityQuery createLogQuery(ActivityQuery query, Request request) {
+ // query.setTypes(request.param(SearchOptions.PARAM_TEXT_QUERY));
+ return query;
+ }
+
+ private void writeLogs(Result<Activity> result, JsonWriter json, SearchOptions options) {
+ json.name("logs").beginArray();
+ for (Activity log : result.getHits()) {
+ mapping.write((ActivityDoc) log, json, options);
+ }
+ json.endArray();
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.activity;
+
+
+import com.google.common.collect.ImmutableMap;
+import org.elasticsearch.common.collect.Iterables;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.sonar.core.activity.Activity;
+import org.sonar.core.activity.ActivityLog;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.server.activity.db.ActivityDao;
+import org.sonar.server.activity.index.ActivityIndex;
+import org.sonar.server.activity.index.ActivityQuery;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.search.QueryOptions;
+import org.sonar.server.tester.ServerTester;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class ActivityBackendMediumTest {
+
+ @ClassRule
+ public static ServerTester tester = new ServerTester();
+
+ ActivityService service = tester.get(ActivityService.class);
+ ActivityDao dao = tester.get(ActivityDao.class);
+ ActivityIndex index = tester.get(ActivityIndex.class);
+ DbClient db;
+ DbSession dbSession;
+
+ @Before
+ public void before() {
+ tester.clearDbAndIndexes();
+ db = tester.get(DbClient.class);
+ dbSession = tester.get(DbClient.class).openSession(false);
+ }
+
+ @After
+ public void after() {
+ dbSession.close();
+ }
+
+ @Test
+ public void insert_find_text_log() throws InterruptedException {
+ final String testValue = "hello world";
+ service.write(dbSession, Activity.Type.ACTIVE_RULE, testValue);
+ dbSession.commit();
+ assertThat(index.findAll().getTotal()).isEqualTo(1);
+
+ Activity activity = Iterables.getFirst(index.findAll().getHits(), null);
+ assertThat(activity).isNotNull();
+ assertThat(activity.message()).isEqualTo(testValue);
+ }
+
+ @Test
+ public void insert_find_loggable_log() {
+ final String testKey = "message";
+ final String testValue = "hello world";
+ service.write(dbSession, Activity.Type.ACTIVE_RULE, new ActivityLog() {
+
+ @Override
+ public Map<String, String> getDetails() {
+ return ImmutableMap.of(testKey, testValue);
+ }
+
+ @Override
+ public int getExecutionTime() {
+ return 12;
+ }
+ });
+ dbSession.commit();
+
+ assertThat(index.findAll().getTotal()).isEqualTo(1);
+
+ Activity activity = Iterables.getFirst(index.findAll().getHits(), null);
+ assertThat(activity).isNotNull();
+ assertThat(activity.details().get(testKey)).isEqualTo(testValue);
+ assertThat(activity.executionTime()).isEqualTo(12);
+ }
+
+ @Test
+ public void massive_insert() {
+
+ // 0 Assert no logs in DB
+ assertThat(dao.findAll(dbSession)).hasSize(0);
+ int max = 200;
+ final String testValue = "hello world";
+ for (int i = 0; i < max; i++) {
+ service.write(dbSession, Activity.Type.ACTIVE_RULE, testValue + "_" + i);
+ }
+ dbSession.commit();
+
+ // 1. assert both backends have all logs
+ assertThat(dao.findAll(dbSession)).hasSize(max);
+ assertThat(index.findAll().getHits()).hasSize(max);
+
+ // 2. assert scrollable
+ int count = 0;
+ Iterator<Activity> logs = index.search(new ActivityQuery(), new QueryOptions().setScroll(true)).scroll();
+ while (logs.hasNext()) {
+ logs.next();
+ count++;
+ }
+ assertThat(count).isEqualTo(max);
+
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.activity;
+
+import org.elasticsearch.common.collect.Iterables;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.sonar.core.activity.Activity;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.server.activity.db.ActivityDao;
+import org.sonar.server.activity.index.ActivityIndex;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.search.QueryOptions;
+import org.sonar.server.search.Result;
+import org.sonar.server.tester.ServerTester;
+
+import java.util.Iterator;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class ActivityServiceMediumTest {
+
+ @ClassRule
+ public static ServerTester tester = new ServerTester();
+
+ ActivityService service = tester.get(ActivityService.class);
+ ActivityDao dao = tester.get(ActivityDao.class);
+ ActivityIndex index = tester.get(ActivityIndex.class);
+ DbClient db;
+ DbSession dbSession;
+
+ @Before
+ public void before() {
+ tester.clearDbAndIndexes();
+ db = tester.get(DbClient.class);
+ dbSession = tester.get(DbClient.class).openSession(false);
+ }
+
+ @After
+ public void after() {
+ dbSession.close();
+ }
+
+ @Test
+ public void find_all() throws InterruptedException {
+ final String testValue = "hello world";
+ service.write(dbSession, Activity.Type.ACTIVE_RULE, testValue);
+ dbSession.commit();
+ assertThat(index.findAll().getTotal()).isEqualTo(1);
+
+ Activity activity = Iterables.getFirst(index.findAll().getHits(), null);
+ assertThat(activity).isNotNull();
+ assertThat(activity.message()).isEqualTo(testValue);
+ }
+
+ @Test
+ public void search_all() throws InterruptedException {
+ final String testValue = "hello world";
+ service.write(dbSession, Activity.Type.ACTIVE_RULE, testValue);
+ dbSession.commit();
+ assertThat(index.findAll().getTotal()).isEqualTo(1);
+
+ Result<Activity> result = index.search(service.newActivityQuery(), new QueryOptions());
+ assertThat(result.getTotal()).isEqualTo(1L);
+ }
+
+ @Test
+ @Ignore
+ // TODO fix missing logs in ES.
+ public void iterate_all() throws InterruptedException {
+ int max = QueryOptions.DEFAULT_LIMIT + 3;
+ final String testValue = "hello world";
+ for (int i = 0; i < max; i++) {
+ service.write(dbSession, Activity.Type.ACTIVE_RULE, testValue + "_" + i);
+ }
+ dbSession.commit();
+
+ // 0. assert Base case
+ assertThat(dao.findAll(dbSession)).hasSize(max);
+
+ Result<Activity> result = index.search(service.newActivityQuery(), new QueryOptions().setScroll(true));
+ assertThat(result.getTotal()).isEqualTo(max);
+ assertThat(result.getHits()).hasSize(0);
+ int count = 0;
+ Iterator<Activity> logIterator = result.scroll();
+ while (logIterator.hasNext()) {
+ count++;
+ logIterator.next();
+ }
+ assertThat(count).isEqualTo(max);
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.activity.db;
+
+
+import com.google.common.collect.ImmutableMap;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.utils.KeyValueFormat;
+import org.sonar.api.utils.System2;
+import org.sonar.core.activity.Activity;
+import org.sonar.core.activity.ActivityLog;
+import org.sonar.core.activity.db.ActivityDto;
+import org.sonar.core.persistence.AbstractDaoTestCase;
+import org.sonar.core.persistence.DbSession;
+
+import java.util.Map;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class ActivityDaoTest extends AbstractDaoTestCase {
+
+
+ private ActivityDao dao;
+ private DbSession session;
+ private System2 system2;
+
+ @Before
+ public void before() throws Exception {
+ this.session = getMyBatis().openSession(false);
+ this.system2 = mock(System2.class);
+ this.dao = new ActivityDao(system2);
+ }
+
+ @After
+ public void after() {
+ session.close();
+ }
+
+ @Test
+ public void fail_insert_missing_type() {
+ String testValue = "hello world";
+ ActivityDto log = ActivityDto.createFor(testValue);
+ try {
+ dao.insert(session, log);
+ } catch (IllegalArgumentException e) {
+ assertThat(e.getMessage()).isEqualTo("Type must be set");
+ }
+ }
+
+ @Test
+ public void fail_insert_missing_author() {
+ String testValue = "hello world";
+ ActivityDto log = ActivityDto.createFor(testValue)
+ .setType(Activity.Type.ACTIVE_RULE);
+ try {
+ dao.insert(session, log);
+ } catch (IllegalArgumentException e) {
+ assertThat(e.getMessage()).isEqualTo("Type must be set");
+ }
+ }
+
+ @Test
+ public void insert_text_log() {
+ String testValue = "hello world";
+ ActivityDto log = ActivityDto.createFor(testValue)
+ .setType(Activity.Type.ACTIVE_RULE)
+ .setAuthor("jUnit");
+ dao.insert(session, log);
+
+ assertThat(dao.findAll(session)).hasSize(1);
+ ActivityDto newDto = dao.getByKey(session, log.getKey());
+ assertThat(newDto.getAuthor()).isEqualTo(log.getAuthor());
+ assertThat(newDto.getMessage()).isEqualTo(testValue);
+ }
+
+ @Test
+ public void insert_loggable_log() {
+ final String testKey = "message";
+ final String testValue = "hello world";
+ ActivityDto log = ActivityDto.createFor(new ActivityLog() {
+
+ @Override
+ public Map<String, String> getDetails() {
+ return ImmutableMap.of(testKey, testValue);
+ }
+
+ @Override
+ public int getExecutionTime() {
+ return 12;
+ }
+ })
+ .setAuthor("jUnit")
+ .setType(Activity.Type.ACTIVE_RULE);
+
+ dao.insert(session, log);
+
+ assertThat(dao.findAll(session)).hasSize(1);
+ ActivityDto newDto = dao.getByKey(session, log.getKey());
+ assertThat(newDto.getAuthor()).isEqualTo(log.getAuthor());
+ assertThat(newDto.getExecutionTime()).isEqualTo(12);
+ assertThat(newDto.getData()).isNotNull();
+ Map<String, String> details = KeyValueFormat.parse(newDto.getData());
+ assertThat(details.get(testKey)).isEqualTo(testValue);
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.activity.ws;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.core.activity.Activity;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.activity.ActivityService;
+import org.sonar.server.tester.ServerTester;
+import org.sonar.server.user.MockUserSession;
+import org.sonar.server.ws.WsTester;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class ActivitiesWebServiceMediumTest {
+
+ @ClassRule
+ public static ServerTester tester = new ServerTester();
+
+ private ActivitiesWebService ws;
+ private ActivityService service;
+ private DbSession session;
+
+
+ @Before
+ public void setUp() throws Exception {
+ tester.clearDbAndIndexes();
+ ws = tester.get(ActivitiesWebService.class);
+ service = tester.get(ActivityService.class);
+ session = tester.get(DbClient.class).openSession(false);
+ }
+
+ @After
+ public void after() {
+ session.close();
+ }
+
+ @Test
+ public void define() throws Exception {
+ WebService.Context context = new WebService.Context();
+ ws.define(context);
+
+ WebService.Controller controller = context.controller(ActivitiesWebService.API_ENDPOINT);
+
+ assertThat(controller).isNotNull();
+ assertThat(controller.actions()).hasSize(1);
+ assertThat(controller.action(SearchAction.SEARCH_ACTION)).isNotNull();
+ }
+
+ @Test
+ public void search_logs() throws Exception {
+ service.write(session, Activity.Type.ACTIVE_RULE, "Hello World");
+ session.commit();
+
+ MockUserSession.set();
+
+ // 1. List single Text log
+ WsTester.TestRequest request = tester.wsTester().newGetRequest(ActivitiesWebService.API_ENDPOINT, SearchAction.SEARCH_ACTION);
+ WsTester.Result result = request.execute();
+ System.out.println("result = " + result.outputAsString());
+ }
+
+
+}