diff --git a/lessonbuilder/tool/src/java/org/sakaiproject/lessonbuildertool/ccexport/CCConfig.java b/lessonbuilder/tool/src/java/org/sakaiproject/lessonbuildertool/ccexport/CCConfig.java index a56afa2b2abe..93395798ebb6 100644 --- a/lessonbuilder/tool/src/java/org/sakaiproject/lessonbuildertool/ccexport/CCConfig.java +++ b/lessonbuilder/tool/src/java/org/sakaiproject/lessonbuildertool/ccexport/CCConfig.java @@ -42,6 +42,8 @@ public class CCConfig { private Map assignmentMap = new HashMap<>(); // Assignments private Map forumsMap = new HashMap<>(); // Forums private Map bltiMap = new HashMap<>(); // BLTI instances + private Map archiveMap = new HashMap<>(); // Archive entries + private Map attachMap = new HashMap<>(); // Arrachment entries private Set pagesDone = new HashSet<>(); // to prevent pages from being output more than once private Map embedMap = new HashMap<>(); // Embed codes with fixups done private Set linkSet = new HashSet<>(); // Links diff --git a/lessonbuilder/tool/src/java/org/sakaiproject/lessonbuildertool/ccexport/CCExport.java b/lessonbuilder/tool/src/java/org/sakaiproject/lessonbuildertool/ccexport/CCExport.java index 92f6ea2db0e7..3d21d41bc800 100644 --- a/lessonbuilder/tool/src/java/org/sakaiproject/lessonbuildertool/ccexport/CCExport.java +++ b/lessonbuilder/tool/src/java/org/sakaiproject/lessonbuildertool/ccexport/CCExport.java @@ -25,14 +25,21 @@ import java.io.InputStream; import java.util.ArrayList; import java.util.List; +import java.util.Collection; +import java.util.Collections; import java.util.Locale; import java.util.Map; +import java.util.Stack; import java.util.Optional; import java.util.zip.ZipEntry; import javax.servlet.http.HttpServletResponse; +import org.w3c.dom.Element; +import org.w3c.dom.Document; + import org.apache.commons.io.IOUtils; +import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.text.StringEscapeUtils; import org.sakaiproject.component.api.ServerConfigurationService; @@ -40,6 +47,8 @@ import org.sakaiproject.content.api.ContentEntity; import org.sakaiproject.content.api.ContentHostingService; import org.sakaiproject.content.api.ContentResource; +import org.sakaiproject.entity.api.EntityProducer; +import org.sakaiproject.entity.api.EntityManager; import org.sakaiproject.entity.api.ResourceProperties; import org.sakaiproject.exception.IdUnusedException; import org.sakaiproject.lessonbuildertool.SimplePageItem; @@ -48,9 +57,12 @@ import org.sakaiproject.lessonbuildertool.util.ResourceLoaderMessageSource; import org.sakaiproject.site.api.Site; import org.sakaiproject.site.api.SiteService; +import org.sakaiproject.time.api.Time; +import org.sakaiproject.time.api.TimeService; import org.sakaiproject.tool.api.SessionManager; import org.sakaiproject.tool.api.ToolSession; import org.sakaiproject.user.api.PreferencesService; +import org.sakaiproject.util.Xml; import lombok.Setter; import lombok.extern.slf4j.Slf4j; @@ -70,6 +82,9 @@ public class CCExport { @Setter private SessionManager sessionManager; @Setter private SimplePageToolDao simplePageToolDao; @Setter private SiteService siteService; + @Setter private EntityManager entityManager; + @Setter private TimeService timeService; + @Setter private String storagePath; private ResourceLoaderMessageSource messageSource; @@ -133,6 +148,8 @@ public void doExport(HttpServletResponse response, String siteId, String version setErrKey("simplepage.exportcc-fileerr", e.getMessage(), ccConfig.getLocale()); } + Time now = timeService.newTime(); + try (ZipPrintStream out = new ZipPrintStream(response.getOutputStream())) { out.setLevel(serverConfigurationService.getInt("zip.compression.level", 1)); response.setHeader("Content-disposition", "inline; filename=sakai-export.imscc"); @@ -144,6 +161,124 @@ public void doExport(HttpServletResponse response, String siteId, String version outputAllForums(ccConfig, out); outputAllBlti(ccConfig, out); outputAllTexts(ccConfig, out); + + // common/archive-impl/impl2/src/java/org/sakaiproject/archive/impl/SiteArchiver.java + + // this is the folder we are writing files to + // String archiveStorage = storagePath + siteId + "-archive/"; + String archiveStorage = serverConfigurationService.getSakaiHomePath() + "archive/" + siteId + "-archive/"; + + // create the directory for the archive + File dir = new File(archiveStorage); + + // clear the directory (if site already archived) so resources are not duplicated + try { + FileUtils.deleteDirectory(dir); + } catch (IOException e) { + log.warn("Could not clear existing archive: {}: {}", dir, e.toString()); + } + + dir.mkdirs(); + + // collect all the attachments we need + List attachments = entityManager.newReferenceList(); + List exportedLabels = new ArrayList(); // LTI registers two providers + Collection producers = entityManager.getEntityProducers(); + for (EntityProducer producer : producers) { + if (producer == null) continue; + + final String serviceName = producer.getClass().getCanonicalName(); + final String serviceLabel = producer.getLabel(); + + if (!producer.willArchiveMerge()) continue; + if ( exportedLabels.contains(serviceLabel) ) continue; + + Document doc = Xml.createDocument(); + Stack stack = new Stack(); + Element root = doc.createElement("archive"); + doc.appendChild(root); + root.setAttribute("source", siteId); + + stack.push(root); + + try { + String archive = producer.archive(siteId, doc, stack, archiveStorage, attachments); + String xml = Xml.writeDocumentToString(doc); + + String zipId = "archive-"+serviceLabel; + String zipName = "sakai_archive/"+serviceLabel+".xml"; + ZipEntry archiveEntry = new ZipEntry(zipName); + out.putNextEntry(archiveEntry); + out.print(xml); + + String resourceId = ccConfig.getResourceId(); + CCResourceItem res = new CCResourceItem(zipId, resourceId, zipName, null, null, null); + ccConfig.getArchiveMap().put(zipId, res); + + exportedLabels.add(serviceLabel); + + } catch (java.lang.Throwable t) { + String archiveError = "Error archiving "+serviceLabel+" "+serviceName+" "+t.toString(); + log.error(archiveError); + t.printStackTrace(); + ccConfig.getResults().add(archiveError); + } + + } + +// TODO: Handle attachments... +System.out.println("attachments="+attachments); + + + // archive the collected attachments + if (attachments.size() > 0) + { + Document doc = Xml.createDocument(); + Stack stack = new Stack(); + Element root = doc.createElement("archive"); + doc.appendChild(root); + root.setAttribute("source", siteId); + root.setAttribute("server", serverConfigurationService.getServerId()); + root.setAttribute("date", now.toString()); + // root.setAttribute("xmlns:sakai", ArchiveService.SAKAI_ARCHIVE_NS); + // root.setAttribute("xmlns:CHEF", ArchiveService.SAKAI_ARCHIVE_NS.concat("CHEF")); + // root.setAttribute("xmlns:DAV", ArchiveService.SAKAI_ARCHIVE_NS.concat("DAV")); + + stack.push(root); + + ccConfig.getResults().add("<===== Attachments =====>\n"); + ccConfig.getResults().add(contentHostingService.archiveResources(attachments, doc, stack, archiveStorage)); + ccConfig.getResults().add("<===== End =====>\n\n"); + + stack.pop(); + + String xml = Xml.writeDocumentToString(doc); + + String zipName = "sakai_archive/attachments.xml"; + String zipId = "archive-attachments"; + ZipEntry archiveEntry = new ZipEntry(zipName); + out.putNextEntry(archiveEntry); + out.print(xml); + + String resourceId = ccConfig.getResourceId(); + CCResourceItem res = new CCResourceItem(zipId, resourceId, zipName, null, null, null); + ccConfig.getArchiveMap().put(zipId, res); + + addAllAttachments(out, ccConfig, dir, ""); + + + } + + // clear the directory since we are done with it + /* + try { + FileUtils.deleteDirectory(dir); + } catch (IOException e) { + log.warn("Could not clear existing archive: {}: {}", dir, e.toString()); + } + */ +System.out.println("======= Did not clear ====== "+dir); + outputManifest(ccConfig, out); ZipEntry zipEntry = new ZipEntry("cc-objects/export-errors.txt"); @@ -153,6 +288,7 @@ public void doExport(HttpServletResponse response, String siteId, String version log.error("Lessons export error streaming file, {}", ioe.toString()); setErrKey("simplepage.exportcc-fileerr", ioe.getMessage(), ccConfig.getLocale()); } + } public boolean addAllFiles(CCConfig ccConfig) { @@ -656,6 +792,13 @@ public void outputManifest(CCConfig ccConfig, ZipPrintStream out) { out.println(" "); } + for (Map.Entry entry : ccConfig.getArchiveMap().entrySet()) { + out.println(" "); + out.println(" "); + entry.getValue().getDependencies().forEach(d -> out.println(" ")); + out.println(" "); + } + // add error log at the very end String errId = ccConfig.getResourceId(); @@ -717,5 +860,39 @@ public void outputManifest(CCConfig ccConfig, ZipPrintStream out) { setErrKey("simplepage.exportcc-fileerr", e.getMessage(), ccConfig.getLocale()); } } + + /** + * Creates a zip entry for the path specified with a name built from the base passed in and the file/directory + * name. If the path is a directory, a recursive call is made such that the full directory is added to the zip. + * + * @param zOut The zip file's output stream + * @param path The filesystem path of the file/directory being added + * @param base The base prefix to for the name of the zip file entry + * + * @throws IOException If anything goes wrong + */ + private static void addAllAttachments(ZipPrintStream out, CCConfig ccConfig, File dir, String path) throws IOException { + + File[] children = dir.listFiles(); + if (children != null) { + for (File child : children) { + if (child.isFile()) { + ZipEntry attachmentEntry = new ZipEntry(path + child.getName()); + out.putNextEntry(attachmentEntry); + + FileInputStream fInputStream = null; + try { + fInputStream = new FileInputStream(child.getAbsolutePath()); + IOUtils.copyLarge(fInputStream, out); + } finally { + IOUtils.closeQuietly(fInputStream); + } + } else { + String newPath = path + child.getName() + "/"; + addAllAttachments(out, ccConfig, child, newPath); + } + } + } + } } diff --git a/lessonbuilder/tool/src/webapp/WEB-INF/applicationContext.xml b/lessonbuilder/tool/src/webapp/WEB-INF/applicationContext.xml index ddcb10802f87..2e65f84c4d15 100644 --- a/lessonbuilder/tool/src/webapp/WEB-INF/applicationContext.xml +++ b/lessonbuilder/tool/src/webapp/WEB-INF/applicationContext.xml @@ -399,6 +399,9 @@ simplePageBean.addForumSummary, + + + ${sakai.home}archive/