diff --git a/src/main/java/org/wikitolearn/wikirating/controller/MaintenanceController.java b/src/main/java/org/wikitolearn/wikirating/controller/MaintenanceController.java index 01d51e1..9c2b081 100644 --- a/src/main/java/org/wikitolearn/wikirating/controller/MaintenanceController.java +++ b/src/main/java/org/wikitolearn/wikirating/controller/MaintenanceController.java @@ -1,111 +1,119 @@ /** * */ package org.wikitolearn.wikirating.controller; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStream; +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; import java.util.Properties; -import org.json.JSONException; -import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.util.DefaultPropertiesPersister; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; +import org.wikitolearn.wikirating.model.api.ApiResponse; +import org.wikitolearn.wikirating.model.api.ApiResponseError; +import org.wikitolearn.wikirating.model.api.ApiResponseFail; +import org.wikitolearn.wikirating.model.api.ApiResponseSuccess; import org.wikitolearn.wikirating.service.MaintenanceService; /** * * @author aletundo * */ @RestController public class MaintenanceController { private static final Logger LOG = LoggerFactory.getLogger(MaintenanceController.class); @Autowired private MaintenanceService maintenanceService; /** * Secured endpoint to enable or disable read only mode. When read only mode * is enabled only GET requests are allowed. To change this behavior @see * org.wikitolearn.wikirating.wikirating.filter.MaintenanceFilter. * * @param active * String requested parameter that toggle the re. Binary values * are accepted. * @return a JSON object with the response */ @RequestMapping(value = "${maintenance.readonlymode.uri}", method = RequestMethod.POST, produces = "application/json") - public String toggleReadOnlyMode(@RequestParam(value = "active") String active) { - JSONObject response = new JSONObject(); + @ResponseBody + @ResponseStatus(HttpStatus.ACCEPTED) + public ApiResponse toggleReadOnlyMode(@RequestParam(value = "active") String active) { int mode = Integer.parseInt(active); + ApiResponse body; + if (mode == 0) { + // Delete maintenance lock file if it exists + File f = new File("maintenance.lock"); + if (f.exists()) { + f.delete(); + LOG.debug("Deleted maintenance lock file."); + } + LOG.info("Application is live now."); + body = new ApiResponseSuccess("Application is live now", Instant.now().toEpochMilli()); + } else if (mode == 1) { + try { + // Create maintenance lock file with a maintenance.active + // property set to true + Properties props = new Properties(); + props.setProperty("maintenance.active", "true"); + File lockFile = new File("maintenance.lock"); + OutputStream out = new FileOutputStream(lockFile); + DefaultPropertiesPersister p = new DefaultPropertiesPersister(); + p.store(props, out, "Maintenance mode lock file"); - try { - if (mode == 0) { - response.put("status", "success"); - response.put("message", "Application is live now."); - // Delete maintenance lock file if it exists - File f = new File("maintenance.lock"); - if (f.exists()) { - f.delete(); - LOG.debug("Deleted maintenance lock file."); - } - LOG.info("Application is live now."); - } else if (mode == 1) { - try { - response.put("status", "success"); - response.put("message", "Application is in maintenance mode now."); - // Create maintenance lock file with a maintenance.active - // property set to true - Properties props = new Properties(); - props.setProperty("maintenance.active", "true"); - File lockFile = new File("maintenance.lock"); - OutputStream out = new FileOutputStream(lockFile); - DefaultPropertiesPersister p = new DefaultPropertiesPersister(); - p.store(props, out, "Maintenance mode lock file"); - - LOG.debug("Created maintenance lock file."); - LOG.info("Application is in maintenance mode now."); - } catch (Exception e) { - LOG.error("Something went wrong. {}", e.getMessage()); - } - - } else { - // The active parameter value is not supported - response.put("status", "error"); - response.put("message", "Parameter value not supported"); + LOG.debug("Created maintenance lock file."); + LOG.info("Application is in maintenance mode now."); + body = new ApiResponseSuccess("Application is in maintenance mode now", Instant.now().toEpochMilli()); + } catch (Exception e) { + LOG.error("Something went wrong. {}", e.getMessage()); + Map data = new HashMap<>(); + data.put("error", HttpStatus.NOT_FOUND.name()); + data.put("exception", e.getClass().getCanonicalName()); + data.put("stacktrace", e.getStackTrace()); + + return new ApiResponseError(data, e.getMessage(), HttpStatus.NOT_FOUND.value(), Instant.now().toEpochMilli()); } - } catch (JSONException e) { - LOG.error("Something went wrong using JSON API. {}", e.getMessage()); + + } else { + // The active parameter value is not supported + body = new ApiResponseFail("Parameter value not supported", Instant.now().toEpochMilli()); } - return response.toString(); + return body; } /** * Secured endpoint that handles initialization request for the given * language * * @return true if the initialization is completed without errors, false * otherwise */ @RequestMapping(value = "${maintenance.init.uri}", method = RequestMethod.POST, produces = "application/json") public boolean initialize() { return maintenanceService.initializeGraph(); } /** * */ @RequestMapping(value = "${maintenance.wipe.uri}", method = RequestMethod.DELETE, produces = "application/json") public void wipeDatabase() { // TODO } } diff --git a/src/main/java/org/wikitolearn/wikirating/model/UpdateInfo.java b/src/main/java/org/wikitolearn/wikirating/model/UpdateInfo.java index ca095d3..8627080 100644 --- a/src/main/java/org/wikitolearn/wikirating/model/UpdateInfo.java +++ b/src/main/java/org/wikitolearn/wikirating/model/UpdateInfo.java @@ -1,132 +1,132 @@ package org.wikitolearn.wikirating.model; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import org.wikitolearn.wikirating.util.enums.UpdateType; +//import org.wikitolearn.wikirating.util.enums.UpdateType; import java.util.Date; /** * This class represents a RecentChange entry during the fetch Process. * It stores the information fetched from the API before the update of the DB. * Created by valsdav on 30/03/17. */ @JsonIgnoreProperties(ignoreUnknown = true) public class UpdateInfo { private String type; private String title; private String newTitle; private int pageid; private int revid; private int old_revid; private String user; private int userid; private int oldlen; private int newlen; private String ns; private Date timestamp; public UpdateInfo(){} public String getType() { return type; } public void setType(String type) { this.type = type; } public int getPageid() { return pageid; } public void setPageid(int pageid) { this.pageid = pageid; } public int getRevid() { return revid; } public void setRevid(int revid) { this.revid = revid; } public int getOld_revid() { return old_revid; } public void setOld_revid(int old_revid) { this.old_revid = old_revid; } public int getUserid() { return userid; } public void setUserid(int userid) { this.userid = userid; } public int getOldlen() { return oldlen; } public void setOldlen(int oldlen) { this.oldlen = oldlen; } public int getNewlen() { return newlen; } public void setNewlen(int newlen) { this.newlen = newlen; } public Date getTimestamp() { return timestamp; } public void setTimestamp(Date timestamp) { this.timestamp = timestamp; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getUser() { return user; } public void setUser(String user) { this.user = user; } public String getNewTitle() { return newTitle; } public void setNewTitle(String newTitle) { this.newTitle = newTitle; } /** * @return the ns */ public String getNs() { return ns; } /** * @param ns the ns to set */ public void setNs(String ns) { this.ns = ns; } } diff --git a/src/main/java/org/wikitolearn/wikirating/model/api/ApiResponseFail.java b/src/main/java/org/wikitolearn/wikirating/model/api/ApiResponseFail.java new file mode 100644 index 0000000..de15ff0 --- /dev/null +++ b/src/main/java/org/wikitolearn/wikirating/model/api/ApiResponseFail.java @@ -0,0 +1,21 @@ +/** + * + */ +package org.wikitolearn.wikirating.model.api; + +/** + * @author aletundo + * + */ +public class ApiResponseFail extends ApiResponse{ + + /** + * @param status + * @param data + * @param timestamp + */ + public ApiResponseFail(Object data, long timestamp) { + super("fail", data, timestamp); + } + +} diff --git a/src/main/java/org/wikitolearn/wikirating/model/graph/TemporaryVote.java b/src/main/java/org/wikitolearn/wikirating/model/graph/TemporaryVote.java index c207a40..db1daec 100644 --- a/src/main/java/org/wikitolearn/wikirating/model/graph/TemporaryVote.java +++ b/src/main/java/org/wikitolearn/wikirating/model/graph/TemporaryVote.java @@ -1,88 +1,89 @@ package org.wikitolearn.wikirating.model.graph; import org.neo4j.ogm.annotation.GraphId; import org.neo4j.ogm.annotation.NodeEntity; import org.neo4j.ogm.annotation.typeconversion.DateLong; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.Date; /** * This entity represents a Vote that has to be validating, * connecting the user and the Revision after a fetch of the mediawiki api. - * @author valsadav + * @author valsdav * @author aletundo */ +@NodeEntity public class TemporaryVote { @GraphId @JsonIgnore private Long graphId; private double value; private double reliability; @JsonProperty("userid") private int userId; @JsonProperty("revid") private int revId; private String langRevId; @DateLong private Date timestamp; public TemporaryVote(double value, double reliability, int userId, int revId, String langRevId, Date timestamp) { this.value = value; this.reliability = reliability; this.userId = userId; this.revId = revId; this.langRevId = langRevId; this.timestamp = timestamp; } public double getValue() { return value; } public void setValue(double value) { this.value = value; } public double getReliability() { return reliability; } public void setReliability(double reliability) { this.reliability = reliability; } public int getUserId() { return userId; } public void setUserid(int userId) { this.userId = userId; } public int getRevId() { return revId; } public void setRevId(int revId) { this.revId = revId; } public String getLangRevId() { return langRevId; } public void setLangRevId(String langRevId) { this.langRevId = langRevId; } public Date getTimestamp() { return timestamp; } public void setTimestamp(Date timestamp) { this.timestamp = timestamp; } } diff --git a/src/main/java/org/wikitolearn/wikirating/repository/RevisionRepository.java b/src/main/java/org/wikitolearn/wikirating/repository/RevisionRepository.java index ef8c5f4..f97e241 100644 --- a/src/main/java/org/wikitolearn/wikirating/repository/RevisionRepository.java +++ b/src/main/java/org/wikitolearn/wikirating/repository/RevisionRepository.java @@ -1,57 +1,56 @@ /** * */ package org.wikitolearn.wikirating.repository; -import java.util.List; import java.util.Set; import org.springframework.data.neo4j.annotation.Query; import org.springframework.data.neo4j.repository.GraphRepository; import org.wikitolearn.wikirating.model.graph.Revision; /** * @author aletundo * */ public interface RevisionRepository extends GraphRepository { /** * * @param langRevId * @return */ Revision findByLangRevId(String langRevId); /** * * @param lang * @return */ Set findByLang(String lang); /** * * @param userId * @return */ Set findByUserId(int userId); /** * This query returns all the Revisions of a Page. * The direction -> of the link is important to traverse * only the chain of Revisions of the page without reaching other nodes. * @param langPageId * @return */ @Query("MATCH (p:Page {langPageId:{0}})-[:LAST_REVISION|PREVIOUS_REVISION*]->(r:Revision) RETURN r") Set findAllRevisionOfPage(String langPageId); /** * This query return the previous revision of a Revision identified * by langRevId. * @param langRevId langRevId of the requested revision * @return the previous revision */ @Query("MATCH (r:Revision {langRevId:{0}})-[:PREVIOUS_REVISION]->(a:Revision) RETURN a") Revision findPreviousRevision(String langRevId); } diff --git a/src/main/java/org/wikitolearn/wikirating/service/RevisionService.java b/src/main/java/org/wikitolearn/wikirating/service/RevisionService.java index 7d32dc8..19fb367 100644 --- a/src/main/java/org/wikitolearn/wikirating/service/RevisionService.java +++ b/src/main/java/org/wikitolearn/wikirating/service/RevisionService.java @@ -1,209 +1,209 @@ /** * */ package org.wikitolearn.wikirating.service; import java.util.Date; import java.util.List; import java.util.ListIterator; import java.util.Set; import java.util.concurrent.CompletableFuture; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.wikitolearn.wikirating.exception.RevisionNotFoundException; import org.wikitolearn.wikirating.model.graph.Page; import org.wikitolearn.wikirating.model.graph.Revision; import org.wikitolearn.wikirating.repository.PageRepository; import org.wikitolearn.wikirating.repository.RevisionRepository; import org.wikitolearn.wikirating.service.mediawiki.RevisionMediaWikiService; import static java.lang.Math.exp; /** * * @author aletundo, valsdav * */ @Service public class RevisionService { private static final Logger LOG = LoggerFactory.getLogger(RevisionService.class); @Autowired private RevisionMediaWikiService revisionMediaWikiService; @Autowired private RevisionRepository revisionRepository; @Autowired private PageRepository pageRepository; /** * Initialize the revisions for the first time querying the MediaWiki API. * This method adds the revisions and sets the FIRST_REVISION, * LAST_REVISION and PREVIOUS_REVISION relationships. * @param lang the domain language * @param apiUrl the MediaWiki API url * @return CompletableFuture */ @Async public CompletableFuture initRevisions(String lang, String apiUrl) { List pages = pageRepository.findAllByLang(lang); for(Page page : pages){ List revisions = revisionMediaWikiService.getAllRevisionByPageId(apiUrl, page.getPageId()); // Set the first and the last revisions for the current page page.setFistRevision(revisions.get(0)); page.setLastRevision(revisions.get(revisions.size() - 1)); ListIterator it = revisions.listIterator(); while(it.hasNext()){ Revision rev = it.next(); rev.setLangRevId(lang + "_" + rev.getRevId()); rev.setLang(lang); if (it.previousIndex() != 0) { rev.setPreviousRevision(revisions.get(it.previousIndex() - 1)); } } // Saving all the revisions node and the page node revisionRepository.save(revisions); pageRepository.save(page); LOG.info("Inserted revisions for page {}", page.getLangPageId()); } return CompletableFuture.completedFuture(true); } @Async public CompletableFuture calculateChangeCoefficientAllRevisions(String lang, String apiUrl){ LOG.info("Calculating changeCoefficient for all the revisions..."); Set revisions = revisionRepository.findByLang(lang); for (Revision rev : revisions){ double changeCoefficient = calculateChangeCoefficient(apiUrl, rev); rev.setChangeCoefficient(changeCoefficient); } revisionRepository.save(revisions); LOG.info("ChangeCoefficient calculated for all revisions."); return CompletableFuture.completedFuture(true); } /** * Add a new Revision to the graph * @param revid * @param lang * @param userid * @param parentid * @param length * @param timestamp * @return */ public Revision addRevision(int revid, String lang, int userid, int parentid, int length, Date timestamp){ Revision rev = new Revision(revid, lang, userid, parentid, length, timestamp); revisionRepository.save(rev); return rev; } /** * Delete a revision given its langPageId * @param langPageId the langPageId of the revision */ public void deleteRevisionsOfPage(String langPageId) throws RevisionNotFoundException{ Set revisions = revisionRepository.findAllRevisionOfPage(langPageId); if (revisions.size() == 0){ LOG.error("Revisions of page {} not found", langPageId); throw new RevisionNotFoundException("Revisions of page "+langPageId+" not found"); } revisionRepository.delete(revisions); } public Set getRevisionsOfPage(String langPageId) throws RevisionNotFoundException{ Set revisions = revisionRepository.findAllRevisionOfPage(langPageId); if (revisions.size() == 0){ LOG.error("Revisions of page {} not found", langPageId); throw new RevisionNotFoundException("Revisions of page "+langPageId+" not found"); } return revisions; } /** * Get the requested revision * @param langRevId the langRevId of the revision * @return the revision * @throws RevisionNotFoundException */ public Revision getRevision(String langRevId) throws RevisionNotFoundException{ Revision revision = revisionRepository.findByLangRevId(langRevId); if(revision == null){ LOG.error("Revision {} not found", langRevId); throw new RevisionNotFoundException(); } return revision; } /** * Update the given revision * @param revision * @return the updated revision */ public Revision updateRevision(Revision revision){ revisionRepository.save(revision); return revision; } /** * Calculate and set the changeCoefficient of a Revision. * This method also persist the Revision changes. * @param apiUrl * @param revision */ public void setChangeCoefficient(String apiUrl, Revision revision){ double cc = calculateChangeCoefficient(apiUrl, revision); revision.setChangeCoefficient(cc); revisionRepository.save(revision); } /** * Calculate the changeCoefficient for a given Revision querying mediawiki * and returns the number. It doesn't store it in the Revision. * @param revision * @return */ public double calculateChangeCoefficient(String apiUrl, Revision revision){ double previousLength = 0.0; double changeCoefficient = 0.0; // Get the previous Revision Revision previousRevision = revision.getPreviousRevision(); if (previousRevision == null){ previousLength = 1.0; changeCoefficient = 0.0; LOG.info("Change coefficient of revision {} (first-rev): {}", revision.getLangRevId(), changeCoefficient); } else{ double prevL = (double) previousRevision.getLength(); // Suppose the mean line length of 120 characters and that the length is in bytes. // We want a "lenght" in n° of lines if (prevL == 0){ previousLength = 1.0; }else { previousLength = (prevL < 120) ? 1 : prevL / 120; } // Query mediawiki for diff text String diffText = revisionMediaWikiService.getDiffPreviousRevision(apiUrl, previousRevision.getRevId(), revision.getRevId()); int addedLines = StringUtils.countMatches(diffText, "diff-addedline"); int deletedLines = StringUtils.countMatches(diffText, "diff-deletedline"); - int inlineChanges = StringUtils.countMatches(diffText, "diffchange-inline"); + //int inlineChanges = StringUtils.countMatches(diffText, "diffchange-inline"); // Finally calculation of change Coefficient double t = ((1.2 * deletedLines + addedLines) ) / previousLength; changeCoefficient = 1 / exp(0.7 * t); LOG.info("Change coefficient of revision {} (+{}-{}/{}): {}", revision.getLangRevId(), addedLines, deletedLines, previousLength, changeCoefficient); } return changeCoefficient; } }