/*******************************************************************************
* Copyright (c) 2006-2010 Vienna University of Technology,
* Department of Software Technology and Interactive Systems
*
* All rights reserved. This program and the accompanying
* materials are made available under the terms of the
* Apache License, Version 2.0 which accompanies
* this distribution, and is available at
* http://www.apache.org/licenses/LICENSE-2.0
*******************************************************************************/
package eu.planets_project.pp.plato.application;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import javax.ejb.Remove;
import javax.ejb.Stateful;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.dom4j.Document;
import org.dom4j.io.XMLWriter;
import org.jboss.annotation.ejb.cache.Cache;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Destroy;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Out;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.faces.FacesMessages;
import org.junit.runner.Computer;
import eu.planets_project.pp.plato.action.IProjectCleaner;
import eu.planets_project.pp.plato.action.IProjectExport;
import eu.planets_project.pp.plato.action.interfaces.IAdmin;
import eu.planets_project.pp.plato.action.interfaces.IMessages;
import eu.planets_project.pp.plato.action.project.LoadPlanAction;
import eu.planets_project.pp.plato.model.Alternative;
import eu.planets_project.pp.plato.model.Plan;
import eu.planets_project.pp.plato.model.PlanProperties;
import eu.planets_project.pp.plato.model.Values;
import eu.planets_project.pp.plato.model.tree.Leaf;
import eu.planets_project.pp.plato.model.tree.TemplateTree;
import eu.planets_project.pp.plato.model.values.Value;
import eu.planets_project.pp.plato.util.MemoryTest;
import eu.planets_project.pp.plato.util.PlatoLogger;
import eu.planets_project.pp.plato.xml.ProjectExporter;
/**
* Performs several admin actions such as removing all projects from database or exporting/importing
* all projects. Most actions require input of admin passcode.
*
* @author Hannes Kulovits
*/
@Stateful
@Scope(ScopeType.APPLICATION)
@Name("admin")
@Cache(org.jboss.ejb3.cache.NoPassivationCache.class)
public class AdminAction implements IAdmin, Serializable {
/**
*
*/
private static final long serialVersionUID = 8226431137018309922L;
private static final Log log = PlatoLogger.getLogger(AdminAction.class);
@PersistenceContext
EntityManager em;
/**
* List of messages displayed to the Plato users.
*/
@In(required = false, create = true)
@Out
IMessages allmessages;
@In(create=true)
private LoadPlanAction loadPlan;
/**
* News message entered by the user
*/
@In(required = false)
@Out
private String news = "";
/**
* Name of the author who entered the news text
*/
@In(required = false)
@Out
private String author = "";
/**
* Importance level of entered message (hard coded to 'Info')
*/
private String importance = "Info";
/**
* Switch for turning on/off news.
*/
@Out
boolean showNews = true;
@Out (required = false)
private String exportDir = "";
private MemoryTest memTest = new MemoryTest();
private ProjectExporter exporter = new ProjectExporter();
public void throwException() {
throw new RuntimeException("Test Exception");
}
/**
* Password enterd by the user.
*/
@In(required=false)
String password;
/**
* Predefined hash coded passcode computed by SHA-1
*/
private String code = "d1f686a6914ac3925ba26732abc96d8878465746";
/**
* Checks if entered passcode equals the predefined administration password.
* Administration password stored in @see {@link #code}.
* @return true if the entered password equals the administration passcode.
*/
public boolean check() {
log.info(computeSHA(password));
boolean allow = (password != null && (computeSHA(password).equals(code)));
if (!allow) {
FacesMessages.instance().add(FacesMessage.SEVERITY_INFO, "Sorry, wrong passcode.");
}
return allow;
}
private static String convertToHex(byte[] data) {
StringBuffer buf = new StringBuffer();
for (int i = 0; i < data.length; i++) {
int halfbyte = (data[i] >>> 4) & 0x0F;
int two_halfs = 0;
do {
if ((0 <= halfbyte) && (halfbyte <= 9))
buf.append((char) ('0' + halfbyte));
else
buf.append((char) ('a' + (halfbyte - 10)));
halfbyte = data[i] & 0x0F;
} while(two_halfs++ < 1);
}
return buf.toString();
}
private static String computeSHA(String input) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(input.getBytes("UTF-8"));
byte[] code = md.digest();
return convertToHex(code);
} catch (NoSuchAlgorithmException e) {
log.error("Algorithm SHA-1 not found!",e);
e.printStackTrace();
return null;
} catch (UnsupportedEncodingException e) {
log.error("Encoding problem: UTF-8 not supported!",e);
e.printStackTrace();
return null;
}
}
/**
* Unlocks all projects. The user must provide the correct administration passcode.
* @see eu.planets_project.pp.plato.action.project.LoadPlanAction#unlockAll()
* @return always null to user stays on same screen after action
*/
public String unlockAll() {
if (check()) {
loadPlan.unlockAll();
}
return null;
}
/**
* Unlocks project with provided projectID.
* @see #projectID
*/
public String unlockUsingProjectID() {
Query q = em.createQuery("update PlanProperties pp set pp.openHandle = 0 where pp.id = " + projectID);
if (q.executeUpdate() < 1) {
FacesMessages.instance().add(FacesMessage.SEVERITY_ERROR, "Unlocking project "+projectID+" failed.");
log.info("Unlocking project "+projectID+" failed.");
} else
FacesMessages.instance().add(FacesMessage.SEVERITY_ERROR, "Unlocked project " + this.projectID);
log.info("Unlocked project "+projectID);
return null;
}
/**
* Adds a news entry to list of messages displayed to user.
* @see #allmessages
*/
public void addNews() {
if (check()) {
allmessages.addNewsMessage(new NewsClass(news, importance,
author));
news = "";
}
System.gc();
}
/**
* Clears list containing exception messages occured during Plato runtime.
*/
public void clearErrors() {
if (check()) {
allmessages.clearErrors();
}
return;
}
/**
* Clears list of news entered by the administrator.
*/
public void clearNews() {
if (check()) {
allmessages.clearNews();
}
}
@Remove
@Destroy
public void destroy() {
}
@In(create=true)
IProjectExport projectExport;
@In(create=true)
IProjectCleaner projectCleaner;
public String cleanupValues() {
List<PlanProperties> ppList = em.createQuery("select p from PlanProperties p").getResultList();
int total = 0;
int i = 0;
for (PlanProperties pp : ppList) {
int number = projectCleaner.cleanupProject(pp.getId());
FacesMessages.instance().add(FacesMessage.SEVERITY_INFO, "Plan "+pp.getName()+": removed "+ number+" values.");
total += number;
i++;
if ((i%5)== 0) {
System.gc();
}
}
FacesMessages.instance().add(FacesMessage.SEVERITY_INFO, "All projects cleaned up, removed "+total+" Values objects in total");
return null;
}
/**
* Clears all projects in database.
* @return null Stay on same screen after action performed.
*/
public String clearData() {
if (check()) {
List<Plan> projectList = em.createQuery("select p from Plan p").getResultList();
for (Plan p : projectList) {
log.info("deleting plan "+p.getPlanProperties().getName());
log.debug("removing value scale linkage...");
for (Leaf l: p.getTree().getRoot().getAllLeaves()) {
for (Alternative a: p.getAlternativesDefinition().getAlternatives()) {
Values values = l.getValues(a.getName());
if (values != null) {
for (Value v : values.getList()) {
if (v!= null) {
v.setScale(null);
}
}
}
}
}
log.debug("removing entity... ");
em.remove(p);
log.debug("plan removed");
}
em.flush();
FacesMessages.instance().add(FacesMessage.SEVERITY_INFO, "All projects deleted!");
}
return null;
}
/**
* Plan ID entered by the user
*/
@In(required=false)
private String projectID;
/**
* Removes a project matching project ID entered by user.
* @see #projectID
*/
public String clearDataUsingProjectID() {
if (check()) {
List<Plan> projectList = em.createQuery("select p from Plan p where p.planProperties.id = "+this.projectID).getResultList();
if (!projectList.isEmpty())
{
Plan p = projectList.get(0);
log.info("Deleting project "+p.getPlanProperties().getId());
em.remove(p);
em.flush();
FacesMessages.instance().add(FacesMessage.SEVERITY_INFO, "Plan " + this.projectID + " deleted!");
}
else
{
FacesMessages.instance().add(FacesMessage.SEVERITY_INFO, "Plan " + this.projectID +" not found!");
}
}
return null;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
/**
*
* @return null Always returns null, so user stays on same screen after action performed
*/
public String clearAllData() {
if (check()) {
this.clearData();
this.resetPublicLibraries();
this.deleteUserLibraries();
}
return null;
}
/**
*
* @return null Always returns null, so user stays on same screen after action performed
*/
public String clearKB() {
if (check()) {
this.resetPublicLibraries();
this.deleteUserLibraries();
}
return null;
}
/**
* Exports all projects into a single xml file. Projects are exported using {@link eu.planets_project.pp.plato.xml.ProjectExporter#exportToXml(Plan)}
* @return null Always returns null, so user stays on same screen after action performed
*/
public String exportAllProjectsToZip(){
if (check()) {
return projectExport.exportAllProjectsToZip();
}
return null;
}
/**
* Exports private templates stored in database to xml
* Templates are exported using {@link eu.planets_project.pp.plato.xml.ProjectExporter#exportTemplates(java.util.List)}
*/
public String exportPrivateTemplates(){
if (check()) {
List<TemplateTree> templates = em.createQuery("select n from TemplateTree n where owner is not null").getResultList();
if (!templates.isEmpty()){
this.returnXMLExport(exporter.exportTemplates(templates));
} else
FacesMessages.instance().add(FacesMessage.SEVERITY_ERROR, "No Templates found!");
}
return null;
}
/**
* Renders the given exported XML-Document as HTTP response
*/
private String returnXMLExport(Document doc) {
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd_kkmmss");
String timestamp = format.format(new Date(System.currentTimeMillis()));
String filename = "export_" + timestamp + ".xml";
HttpServletResponse response = (HttpServletResponse)FacesContext.getCurrentInstance().getExternalContext().getResponse();
response.setContentType("application/x-download");
response.setHeader("Content-Disposition", "attachement; filename=\""+filename+"\"");
//response.setContentLength(xml.length());
try {
BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
XMLWriter writer = new XMLWriter(out,ProjectExporter.prettyFormat);
writer.write(doc);
writer.flush();
writer.close();
out.flush();
out.close();
} catch (IOException e) {
FacesMessages.instance().add(FacesMessage.SEVERITY_ERROR, "An error occured while generating the export file.");
log.error("Could not open response-outputstream: ", e);
}
FacesContext.getCurrentInstance().responseComplete();
return null;
}
/**
* Exports all templates stored in database to xml
* Templates are exported using {@link eu.planets_project.pp.plato.xml.ProjectExporter#exportTemplates(java.util.List)}
*/
public String exportAllTemplates(){
if (check()) {
List<TemplateTree> templates = em.createQuery("select n from TemplateTree n").getResultList();
if (!templates.isEmpty()){
this.returnXMLExport(exporter.exportTemplates(templates));
} else
FacesMessages.instance().add(FacesMessage.SEVERITY_ERROR, "No Templates found!");
}
return null;
}
/**
* removes all public templates from database
* @return null Always returns null so user stays on same screen after action performed.
*/
public String resetPublicLibraries() {
if (check()) {
List<TemplateTree> templates = em.createQuery("select n from TemplateTree n where owner is null").getResultList();
for (TemplateTree t : templates) {
log.info("Deleting Template Tree "+t.getName());
em.remove(t);
}
em.flush();
FacesMessages.instance().add(FacesMessage.SEVERITY_INFO, "Public Templates removed!");
}
return null;
}
/**
* Removes all templates created by arbitrary user.
* @return null Always returns null.
*/
// TODO add per-user option for removing templates of a certain user
public String deleteUserLibraries() {
if (check()) {
List<TemplateTree> templates = em.createQuery("select n from TemplateTree n where owner is not null").getResultList();
for (TemplateTree t : templates) {
log.info("Deleting Template Tree "+t.getName() + " of user " + t.getOwner());
em.remove(t);
}
em.flush();
FacesMessages.instance().add(FacesMessage.SEVERITY_INFO, "Private Templates removed!");
}
return null;
}
public String refresh() {
if (exportDir == null)
return exportDir;
else
return null;
}
public void munchMem(int mb) {
log.info("Munching " + mb + " MB");
memTest.munchMem(mb);
FacesMessages.instance().add(FacesMessage.SEVERITY_INFO, "Munched " + mb + " MB");
}
public void releaseMem() {
log.info("Releasing memory...");
memTest.releaseMem();
}
/**
* prints a hashcode for a provided String (arg[0]) using SHA-1 encryption
* Use this as a quick way of computing a password hash and pasting
* the code that you get into the variable {@link #code}
* @param args one: args[0] must be the passcode for which you want to compute the hash
*/
public static void main (String[] args) {
System.out.println(computeSHA(args[0]));
}
}