/******************************************************************************* * Copyright 2006 - 2012 Vienna University of Technology, * Department of Software Technology and Interactive Systems, IFS * * 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 eu.scape_project.planning.application; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.UUID; import javax.ejb.Stateless; import javax.inject.Inject; import javax.persistence.EntityManager; import javax.persistence.Query; import org.slf4j.Logger; import eu.scape_project.planning.bean.PrepareChangesForPersist; import eu.scape_project.planning.exception.PlanningException; import eu.scape_project.planning.manager.PlanManager; import eu.scape_project.planning.model.Alternative; import eu.scape_project.planning.model.Notification; import eu.scape_project.planning.model.Plan; import eu.scape_project.planning.model.PlanProperties; import eu.scape_project.planning.model.PlatoException; import eu.scape_project.planning.model.User; import eu.scape_project.planning.utils.MemoryTest; import eu.scape_project.planning.utils.OS; import eu.scape_project.planning.xml.PlanXMLConstants; import eu.scape_project.planning.xml.ProjectExportAction; import eu.scape_project.planning.xml.ProjectImporter; @Stateless public class AdminActions implements Serializable { private static final long serialVersionUID = -5811809194521269245L; @Inject private Logger log; @Inject private EntityManager em; @Inject private User user; @Inject private ProjectExportAction projectExportAction; @Inject private ProjectImporter projectImporter; @Inject private PlanManager planManager; @Inject private MemoryTest memoryTest; /** * Exports all plans into a single xml file. * * @return True if export was successful, false otherwise. */ public boolean exportAllPlansToZip() { return projectExportAction.exportAllProjectsToZip(); } /** * Exports all plans with planproperty-ids between fromPlanPropertiesId and * toPlanProperitesId into a single xml file. * * @param fromPlanPropertiesId * Start of the id range to export. * @param toPlanProperitesId * End of the id range to export. * @return True if export was successful, false otherwise. */ public boolean exportSomePlansToZip(int fromPlanPropertiesId, int toPlanProperitesId) { return projectExportAction.exportSomeProjectsToZip(fromPlanPropertiesId, toPlanProperitesId); } /** * Method responsible for retrieving the path where the last project export * was put into. * * @return Path where the last project export was put into, or null if no * project export was done in this session yet. */ public String getLastProjectExportPath() { return projectExportAction.getLastProjectExportPath(); } /** * Method responsible for deleting all plans from database. */ public boolean deleteAllPlans() { @SuppressWarnings("unchecked") List<Plan> planList = em.createQuery("select p from Plan p").getResultList(); boolean gotError = false; for (Plan p : planList) { try { planManager.deletePlan(p); } catch (PlanningException e) { // just log the error, and try to delete the other plans gotError = true; log.error(e.getMessage(), e); } } em.flush(); return !gotError; } /** * Method responsible for cleaning-up/removing all loose Plan Values. * * @return Number of cleaned-up/removed Values objects. */ public int cleanUpLoosePlanValues() { @SuppressWarnings("unchecked") List<PlanProperties> ppList = em.createQuery("select p from PlanProperties p").getResultList(); int total = 0; int i = 0; for (PlanProperties pp : ppList) { int number = cleanupProject(pp.getId()); log.info("Plan " + pp.getName() + ": removed " + number + " values."); total += number; i++; if ((i % 5) == 0) { System.gc(); } } return total; } /** * Method responsible for unlocking a specific plan. * * @param planPropertiesId * PlanProperties-id of the plan to unlock. * @return True if unlocking was successful, false otherwise. */ public boolean unlockPlan(int planPropertiesId) { Query q = em.createQuery("update PlanProperties pp set pp.openHandle = 0 where pp.id = " + planPropertiesId); if (q.executeUpdate() < 1) { log.info("Unlocking project with PlanPropertiesId " + planPropertiesId + " failed."); return false; } else { log.info("Unlocked project with PlanPropertiesId " + planPropertiesId); return true; } } /** * Method responsible for cloning a specific plan. * * @param planPropertiesId * PlanProperties-id of the plan to clone. * @return True if cloning was successful, false otherwise. */ public boolean clonePlan(Integer planPropertiesId, String newOwner) { boolean success = false; Plan selectedPlan; try { selectedPlan = (Plan) em .createQuery("select p from Plan p where p.planProperties.id = " + planPropertiesId).getSingleResult(); String binarydataTempPath = OS.getTmpPath() + "cloneplan_" + System.currentTimeMillis() + "/"; File binarydataTempDir = new File(binarydataTempPath); binarydataTempDir.mkdirs(); try { String tempFile = binarydataTempPath + "plan.xml"; projectExportAction .exportComplete(planPropertiesId, new FileOutputStream(tempFile), binarydataTempPath); List<Plan> plans = projectImporter.importPlans(new FileInputStream(tempFile)); Notification notification = null; if (newOwner != null) { User user = em.createQuery("Select u from User u where u.username = :username", User.class) .setParameter("username", newOwner).getSingleResult(); for (Plan p : plans) { PlanProperties prop = p.getPlanProperties(); prop.setDescription(newOwner + "'s copy of: " + prop.getDescription() + " (originally created by " + prop.getOwner() + ")"); prop.setOwner(newOwner); // mark this plan as a playground copy prop.setPlayground(true); prop.touch(); PrepareChangesForPersist prep = new PrepareChangesForPersist(newOwner); prep.prepare(prop); String message = "A copy has been created: <em>" + prop.getName() + " - " + prop.getDescription() + "</em>" + "<br/>It is marked as playground. If you want to use it for serious planning, please change this in Plan Settings."; notification = new Notification(UUID.randomUUID().toString(), new Date(), "PLATO", message, user); } } // store project storePlans(plans); success = true; log.debug("Plan '" + selectedPlan.getPlanProperties().getName() + "' successfully cloned."); if (notification != null) { // and store notification as well em.persist(notification); } } catch (Exception e) { log.error("Could not clone project: '" + selectedPlan.getPlanProperties().getName() + "'.", e); } finally { OS.deleteDirectory(binarydataTempDir); } return success; } catch (Exception e1) { log.error("Failed to retrieve plan for cloning: " + planPropertiesId); } return false; } /** * Method responsible for deleting a specific plan. * * @param planPropertiesId * PlanPropertiesId of the plan to delete. * @return True if deletion was successful, false otherwise. */ public boolean deletePlan(int planPropertiesId) { try { Plan plan = (Plan) em.createQuery("select p from Plan p where p.planProperties.id = " + planPropertiesId) .getSingleResult(); planManager.deletePlan(plan); return true; } catch (Exception e) { log.error(e.getMessage(), e); return false; } } /** * Method responsible for unlocking all locked plans. */ public void unlockAllPlans() { planManager.unlockAll(); } /** * Method responsible for throwing a RuntimeException. */ public void throwRuntimeException() { throw new RuntimeException("AdminUtils Test-Exception"); } /** * Method responsible for munching memory. * * @param mb * Memory to munch (in MB). */ public void munchMem(int mb) { log.info("Munching " + mb + " MB of memory."); memoryTest.munchMem(mb); } /** * Method responsible for releasing memory. */ public void releaseMem() { log.info("Releasing memory..."); memoryTest.releaseMem(); } /** * Method responsible for importing Plans from a given directory. * * @param directory * Import directory * @return Number of plans imported successfully. */ public int importPlansFromDirectory(String directory) { int count = 0; try { count = projectImporter.importAllProjectsFromDir(directory); } catch (PlatoException e) { log.error("failed to import plans from " + directory, e); return 0; } return count; } /** * Method responsible for importing Plans from a given file. * * @param file * File which contains plan in xml-format. * @param changeUser * Indicates if the imported plan should be assigned to the * current user (thus be imported for only this user) * @return Number of plans imported successfully. */ public int importPlansFromFile(byte[] fileData, boolean changeUser) { // check input if (fileData == null || fileData.length == 0) { log.error("Invalid file passed for import."); return 0; } log.debug("Try to import plans from file"); int nrOrPlans = 0; List<Plan> plansToImport = new ArrayList<Plan>(); // start import try { plansToImport = projectImporter.importPlans(new ByteArrayInputStream(fileData)); nrOrPlans = plansToImport.size(); // if the plans are imported by a NORMAL USER in the web interface, // they // will be // assigned to this user, i.e. the owner is set to the current user. // If they are imported by an ADMIN, they stay property of the // original // user, // unless the admin uses a different button if (!user.isAdmin() || changeUser) { for (Plan p : plansToImport) { p.getPlanProperties().setOwner(user.getUsername()); } } // store plans storePlans(plansToImport); } catch (Exception e) { log.error("failed to import plans from file.", e); return 0; } return nrOrPlans; } /** * Method responsible for importing plans via their given xml * representation. * * @param xml * Xml representation of the plans. * @return Number of plans imported successfully. */ public int importPlansFromXml(String xml) { int importedPlans = 0; List<Plan> plansToImport = new ArrayList<Plan>(); try { plansToImport = projectImporter.importPlans(new ByteArrayInputStream(xml .getBytes(PlanXMLConstants.ENCODING))); importedPlans = plansToImport.size(); storePlans(plansToImport); } catch (Exception e) { log.error("failed to import plans from xml.", e); return 0; } return importedPlans; } /** * Method responsible for cleaning-up/removing loose Plan Values for a given * Plan. * * @param pid * PlanProperties id of the Plan to operate on. * @return Number of cleanedUp/removed Values objects. */ private int cleanupProject(int pid) { try { Plan p = (Plan) em.createQuery("select p from Plan p where p.planProperties.id = " + pid).getSingleResult(); List<String> alternativeNames = new ArrayList<String>(); for (Alternative a : p.getAlternativesDefinition().getAlternatives()) { alternativeNames.add(a.getName()); } int number = p.getTree().removeLooseValues(alternativeNames, p.getSampleRecordsDefinition().getRecords().size()); log.info("cleaned up values for plan " + p.getPlanProperties().getName() + " - removed " + number + " Value(s) instances."); if (number > 0) { em.persist(p.getTree()); } em.clear(); return number; } catch (Exception e) { log.error("Failed to retrieve plan for clean-up. id: " + pid, e); return 0; } } public boolean fixAlternativeNames(int pid) { Plan p = (Plan) em.createQuery("select p from Plan p where p.planProperties.id = " + pid).getSingleResult(); log.debug("fixing alternative names of plan {}, {}", pid, p.getPlanProperties().getName()); boolean fixed = false; for (Alternative a : p.getAlternativesDefinition().getAlternatives()) { String oldName = a.getName(); String name = oldName.trim(); if (!name.equals(oldName)) { name = p.getAlternativesDefinition().createUniqueName(name); try { log.debug("Renaming alternative {} to {}", oldName, name); p.renameAlternative(a, name); a.setDescription(a.getDescription() + "\r\n(PLATO: Alternative name normalization: '" + oldName + "' to '" + name + "')"); fixed = true; } catch (PlanningException e) { log.error("Failed to rename alternative for plan " + pid, e); return false; } } } if (fixed) { em.persist(p.getAlternativesDefinition()); em.persist(p.getTree()); } return true; } /** * Method responsible for storing plans in database. * * @param plans * Plans to store. * @throws PlatoException */ private void storePlans(List<Plan> plans) throws PlatoException { while (!plans.isEmpty()) { Plan plan = plans.get(0); projectImporter.storeDigitalObjects(plan); em.persist(plan); em.flush(); plans.remove(plan); plan = null; em.clear(); System.gc(); } } public void addNotification(String source, String message) { Date now = new Date(); String uuid = UUID.randomUUID().toString(); List<User> users = em.createQuery("select p from User p", User.class).getResultList(); for (User u : users) { Notification note = new Notification(uuid, now, source, message, u); em.persist(note); } } public void removeNotification(String uuid) { int numRemoved = em.createQuery("delete from Notification where uuid = :uuid").setParameter("uuid", uuid) .executeUpdate(); log.debug("Removed {} notifications with uuid = {}", numRemoved, uuid); } public List<Notification> getNotifications() { return em.createQuery("select n from Notification n group by n.uuid order by n.timestamp", Notification.class) .getResultList(); } }