/** * Copyright (C) 2010-2017 Structr GmbH * * This file is part of Structr <http://structr.org>. * * Structr is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * Structr 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Structr. If not, see <http://www.gnu.org/licenses/>. */ package org.structr.web.maintenance; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Reader; import java.io.Writer; import java.net.URISyntaxException; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.structr.common.GraphObjectComparator; import org.structr.common.PropertyView; import org.structr.common.SecurityContext; import org.structr.common.error.FrameworkException; import org.structr.core.app.App; import org.structr.core.app.StructrApp; import org.structr.core.entity.AbstractNode; import org.structr.core.entity.AbstractRelationship; import org.structr.core.entity.Localization; import org.structr.core.entity.MailTemplate; import org.structr.core.entity.Principal; import org.structr.core.entity.ResourceAccess; import org.structr.core.entity.SchemaMethod; import org.structr.core.entity.Security; import org.structr.core.entity.relationship.PrincipalOwnsNode; import org.structr.core.graph.MaintenanceCommand; import org.structr.core.graph.NodeInterface; import org.structr.core.graph.NodeServiceCommand; import org.structr.core.graph.Tx; import org.structr.core.property.PropertyKey; import org.structr.core.property.PropertyMap; import org.structr.core.script.Scripting; import org.structr.rest.resource.MaintenanceParameterResource; import org.structr.schema.action.ActionContext; import org.structr.schema.export.StructrSchema; import org.structr.schema.json.JsonSchema; import org.structr.web.common.FileHelper; import org.structr.web.common.RenderContext; import org.structr.web.entity.AbstractFile; import org.structr.web.entity.FileBase; import org.structr.web.entity.Folder; import org.structr.web.entity.Image; import org.structr.web.entity.Widget; import org.structr.web.entity.dom.Content; import org.structr.web.entity.dom.DOMNode; import org.structr.web.entity.dom.Page; import org.structr.web.entity.dom.ShadowDocument; import org.structr.web.entity.dom.Template; import org.structr.web.entity.html.relation.ResourceLink; import org.structr.web.entity.relation.FileChildren; import org.structr.web.entity.relation.FileSiblings; import org.structr.web.entity.relation.FolderChildren; import org.structr.web.entity.relation.Folders; import org.structr.web.entity.relation.Images; import org.structr.web.entity.relation.MinificationSource; import org.structr.web.entity.relation.Thumbnails; import org.structr.web.entity.relation.UserFavoriteFavoritable; import org.structr.web.entity.relation.UserFavoriteFile; import org.structr.web.entity.relation.UserWorkDir; import org.structr.web.maintenance.deploy.ComponentImportVisitor; import org.structr.web.maintenance.deploy.FileImportVisitor; import org.structr.web.maintenance.deploy.PageImportVisitor; import org.structr.web.maintenance.deploy.SchemaImportVisitor; import org.structr.web.maintenance.deploy.TemplateImportVisitor; /** * */ public class DeployCommand extends NodeServiceCommand implements MaintenanceCommand { private static final Logger logger = LoggerFactory.getLogger(DeployCommand.class.getName()); private static final Pattern pattern = Pattern.compile("[a-f0-9]{32}"); private static final Set<String> exportFileTypes = new HashSet<>(Arrays.asList(new String[] { "File", "Folder", "Image" } )); static { MaintenanceParameterResource.registerMaintenanceCommand("deploy", DeployCommand.class); } @Override public void execute(final Map<String, Object> attributes) throws FrameworkException { final String mode = (String) attributes.get("mode"); if (mode != null && "export".equals(mode)) { doExport(attributes); } else { // default is "import" doImport(attributes); } } @Override public boolean requiresEnclosingTransaction() { return false; } @Override public boolean requiresFlushingOfCaches() { return false; } public Map<String, Object> readConfigMap(final Path pagesConf) { if (Files.exists(pagesConf)) { try (final Reader reader = Files.newBufferedReader(pagesConf, Charset.forName("utf-8"))) { return new HashMap<>(getGson().fromJson(reader, Map.class)); } catch (IOException ioex) { logger.warn("", ioex); } } return new HashMap<>(); } public Gson getGson() { return new GsonBuilder().setPrettyPrinting().create(); } // ----- public static methods ----- public static boolean isUuid(final String name) { return pattern.matcher(name).matches(); } // ----- private methods ----- private void doImport(final Map<String, Object> attributes) throws FrameworkException { final String path = (String) attributes.get("source"); final App app = StructrApp.getInstance(); final Map<String, Object> componentsConf = new HashMap<>(); final Map<String, Object> templatesConf = new HashMap<>(); final Map<String, Object> pagesConf = new HashMap<>(); final Map<String, Object> filesConf = new HashMap<>(); if (StringUtils.isBlank(path)) { throw new FrameworkException(422, "Please provide 'source' attribute for deployment source directory path."); } final Path source = Paths.get(path); if (!Files.exists(source)) { throw new FrameworkException(422, "Source path " + path + " does not exist."); } if (!Files.isDirectory(source)) { throw new FrameworkException(422, "Source path " + path + " is not a directory."); } // apply configuration final Path preDeployConf = source.resolve("pre-deploy.conf"); if (Files.exists(preDeployConf)) { try (final Tx tx = StructrApp.getInstance().tx()) { info("Applying pre-deployment configuration from {}..", preDeployConf); final String confSource = new String(Files.readAllBytes(preDeployConf), Charset.forName("utf-8")); Scripting.evaluate(new ActionContext(SecurityContext.getSuperUserInstance()), null, confSource.trim(), "pre-deploy.conf"); tx.success(); } catch (Throwable t) { logger.warn("", t); } } // read grants.json final Path grantsConf = source.resolve("security/grants.json"); if (Files.exists(grantsConf)) { info("Reading {}..", grantsConf); importListData(ResourceAccess.class, readConfigList(grantsConf)); } // read schema-methods.json final Path schemaMethodsConf = source.resolve("schema-methods.json"); if (Files.exists(schemaMethodsConf)) { info("Reading {}..", schemaMethodsConf); importListData(SchemaMethod.class, readConfigList(schemaMethodsConf)); } // read mail-templates.json final Path mailTemplatesConf = source.resolve("mail-templates.json"); if (Files.exists(mailTemplatesConf)) { info("Reading {}..", mailTemplatesConf); importListData(MailTemplate.class, readConfigList(mailTemplatesConf)); } // read widgets.json final Path widgetsConf = source.resolve("widgets.json"); if (Files.exists(widgetsConf)) { info("Reading {}..", widgetsConf); importListData(Widget.class, readConfigList(widgetsConf)); } // read localizations.json final Path localizationsConf = source.resolve("localizations.json"); if (Files.exists(localizationsConf)) { info("Reading {}..", localizationsConf); importListData(Localization.class, readConfigList(localizationsConf)); } // read files.conf final Path filesConfFile = source.resolve("files.json"); if (Files.exists(filesConfFile)) { info("Reading {}..", filesConfFile); filesConf.putAll(readConfigMap(filesConfFile)); } // read pages.conf final Path pagesConfFile = source.resolve("pages.json"); if (Files.exists(pagesConfFile)) { info("Reading {}..", pagesConfFile); pagesConf.putAll(readConfigMap(pagesConfFile)); } // read components.conf final Path componentsConfFile = source.resolve("components.json"); if (Files.exists(componentsConfFile)) { info("Reading {}..", componentsConfFile); componentsConf.putAll(readConfigMap(componentsConfFile)); } // read templates.conf final Path templatesConfFile = source.resolve("templates.json"); if (Files.exists(templatesConfFile)) { info("Reading {}..", templatesConfFile); templatesConf.putAll(readConfigMap(templatesConfFile)); } // import schema final Path schema = source.resolve("schema"); if (Files.exists(schema)) { try { info("Importing data from schema/ directory.."); Files.walkFileTree(schema, new SchemaImportVisitor(schema)); } catch (IOException ioex) { logger.warn("Exception while importing schema", ioex); } } // import files final Path files = source.resolve("files"); if (Files.exists(files)) { try { info("Importing files..."); Files.walkFileTree(files, new FileImportVisitor(files, filesConf)); } catch (IOException ioex) { logger.warn("Exception while importing files", ioex); } } // remove all DOMNodes from the database (clean webapp for import) try (final Tx tx = app.tx(true, true, false)) { info("Removing pages, templates and components.."); for (final DOMNode node : app.nodeQuery(DOMNode.class)) { app.delete(node); } tx.success(); } // import templates, must be done before pages so the templates exist final Path templates = source.resolve("templates"); if (Files.exists(templates)) { try { info("Importing templates.."); Files.walkFileTree(templates, new TemplateImportVisitor(templatesConf)); } catch (IOException ioex) { logger.warn("Exception while importing templates", ioex); } } // import components, must be done before pages so the shared components exist final Path components = source.resolve("components"); if (Files.exists(components)) { try { info("Importing shared components.."); Files.walkFileTree(components, new ComponentImportVisitor(componentsConf)); } catch (IOException ioex) { logger.warn("Exception while importing shared components", ioex); } } // import pages final Path pages = source.resolve("pages"); if (Files.exists(pages)) { try { info("Importing pages.."); Files.walkFileTree(pages, new PageImportVisitor(pages, pagesConf)); } catch (IOException ioex) { logger.warn("Exception while importing pages", ioex); } } // apply configuration final Path postDeployConf = source.resolve("post-deploy.conf"); if (Files.exists(postDeployConf)) { try (final Tx tx = StructrApp.getInstance().tx()) { info("Applying post-deployment configuration from {}..", postDeployConf); final String confSource = new String(Files.readAllBytes(postDeployConf), Charset.forName("utf-8")); Scripting.evaluate(new ActionContext(SecurityContext.getSuperUserInstance()), null, confSource.trim(), "post-deploy.conf"); tx.success(); } catch (Throwable t) { logger.warn("", t); } } info("Import from {} done.", source.toString()); } private void doExport(final Map<String, Object> attributes) throws FrameworkException { final String path = (String) attributes.get("target"); if (StringUtils.isBlank(path)) { throw new FrameworkException(422, "Please provide target path for deployment export."); } final Path target = Paths.get(path); try { Files.createDirectories(target); final Path components = Files.createDirectories(target.resolve("components")); final Path files = Files.createDirectories(target.resolve("files")); final Path pages = Files.createDirectories(target.resolve("pages")); final Path schema = Files.createDirectories(target.resolve("schema")); final Path security = Files.createDirectories(target.resolve("security")); final Path templates = Files.createDirectories(target.resolve("templates")); final Path schemaJson = schema.resolve("schema.json"); final Path grants = security.resolve("grants.json"); final Path filesConf = target.resolve("files.json"); final Path pagesConf = target.resolve("pages.json"); final Path componentsConf = target.resolve("components.json"); final Path templatesConf = target.resolve("templates.json"); final Path schemaMethods = target.resolve("schema-methods.json"); final Path mailTemplates = target.resolve("mail-templates.json"); final Path localizations = target.resolve("localizations.json"); final Path widgets = target.resolve("widgets.json"); exportFiles(files, filesConf); exportPages(pages, pagesConf); exportComponents(components, componentsConf); exportTemplates(templates, templatesConf); exportResourceAccessGrants(grants); exportSchema(schemaJson); exportSchemaMethods(schemaMethods); exportMailTemplates(mailTemplates); exportLocalizations(localizations); exportWidgets(widgets); // config import order is "users, grants, pages, components, templates" // data import order is "schema, files, templates, components, pages" } catch (IOException ex) { logger.warn("", ex); } } private void exportFiles(final Path target, final Path configTarget) throws FrameworkException { final Map<String, Object> config = new TreeMap<>(); final App app = StructrApp.getInstance(); try (final Tx tx = app.tx()) { // fetch toplevel folders and recurse for (final Folder folder : app.nodeQuery(Folder.class).and(Folder.parent, null).sort(Folder.name).and(AbstractFile.includeInFrontendExport, true).getAsList()) { exportFilesAndFolders(target, folder, config); } // fetch toplevel files that are marked for export or for use as a javascript library for (final FileBase file : app.nodeQuery(FileBase.class) .and(Folder.parent, null) .sort(FileBase.name) .and() .or(AbstractFile.includeInFrontendExport, true) .or(FileBase.useAsJavascriptLibrary, true) .getAsList()) { exportFile(target, file, config); } tx.success(); } catch (IOException ioex) { logger.warn("", ioex); } try (final Writer fos = new OutputStreamWriter(new FileOutputStream(configTarget.toFile()))) { getGson().toJson(config, fos); } catch (IOException ioex) { logger.warn("", ioex); } } private void exportFilesAndFolders(final Path target, final Folder folder, final Map<String, Object> config) throws IOException { final String name = folder.getName(); final Path path = target.resolve(name); // make sure that only frontend data is exported, ignore extended // types and those with relationships to user data. if (DeployCommand.okToExport(folder)) { final Map<String, Object> properties = new TreeMap<>(); Files.createDirectories(path); exportFileConfiguration(folder, properties); if (!properties.isEmpty()) { config.put(folder.getPath(), properties); } } final List<Folder> folders = folder.getProperty(Folder.folders); Collections.sort(folders, new GraphObjectComparator(AbstractNode.name, false)); for (final Folder child : folders) { exportFilesAndFolders(path, child, config); } final List<FileBase> files = folder.getProperty(Folder.files); Collections.sort(files, new GraphObjectComparator(AbstractNode.name, false)); for (final FileBase file : files) { exportFile(path, file, config); } } private void exportFile(final Path target, final FileBase file, final Map<String, Object> config) throws IOException { if (!DeployCommand.okToExport(file)) { return; } final Map<String, Object> properties = new TreeMap<>(); final String name = file.getName(); final Path src = file.getFileOnDisk().toPath(); Path targetPath = target.resolve(name); boolean doExport = true; // modify file name if there are duplicates in the database if (Files.exists(targetPath)) { // compare checksum final Long checksumOfExistingFile = FileHelper.getChecksum(targetPath.toFile()); final Long checksumOfExportFile = file.getChecksum(); if (checksumOfExistingFile.equals(checksumOfExportFile)) { logger.info("Skipping export of file {}, no changes.", name); doExport = false; } } // export only if file is if (doExport) { try { Files.copy(src, targetPath, StandardCopyOption.REPLACE_EXISTING); } catch (IOException ioex) { logger.warn("Unable to write file {}: {}", targetPath.toString(), ioex.getMessage()); } } exportFileConfiguration(file, properties); if (!properties.isEmpty()) { config.put(file.getPath(), properties); } } private void exportPages(final Path target, final Path configTarget) throws FrameworkException { final Map<String, Object> pagesConfig = new TreeMap<>(); final App app = StructrApp.getInstance(); try (final Tx tx = app.tx()) { for (final Page page : app.nodeQuery(Page.class).sort(Page.name).getAsList()) { if (!(page instanceof ShadowDocument)) { final String content = page.getContent(RenderContext.EditMode.DEPLOYMENT); if (content != null) { final Map<String, Object> properties = new TreeMap<>(); final String name = page.getName(); final Path pageFile = target.resolve(name + ".html"); boolean doExport = true; if (Files.exists(pageFile)) { try { final String existingContent = new String(Files.readAllBytes(pageFile), "utf-8"); if (existingContent.equals(content)) { logger.info("Skipping export of page {}, no changes.", name); doExport = false; } } catch (IOException ignore) { logger.warn("", ignore); } } pagesConfig.put(name, properties); exportConfiguration(page, properties); if (doExport) { try (final OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(pageFile.toFile()))) { writer.write(content); writer.flush(); writer.close(); } catch (IOException ioex) { logger.warn("", ioex); } } } } } tx.success(); } try (final Writer fos = new OutputStreamWriter(new FileOutputStream(configTarget.toFile()))) { getGson().toJson(pagesConfig, fos); } catch (IOException ioex) { logger.warn("", ioex); } } private void exportComponents(final Path target, final Path configTarget) throws FrameworkException { final Map<String, Object> configuration = new TreeMap<>(); final App app = StructrApp.getInstance(); try (final Tx tx = app.tx()) { final ShadowDocument shadowDocument = app.nodeQuery(ShadowDocument.class).getFirst(); if (shadowDocument != null) { for (final DOMNode node : shadowDocument.getProperty(Page.elements)) { final boolean hasParent = node.getProperty(DOMNode.parent) != null; final boolean inTrash = node.inTrash(); boolean doExport = true; // skip nodes in trash and non-toplevel nodes if (inTrash || hasParent) { continue; } final String content = node.getContent(RenderContext.EditMode.DEPLOYMENT); if (content != null) { String name = node.getProperty(AbstractNode.name); if (name == null) { name = node.getUuid(); } final Map<String, Object> properties = new TreeMap<>(); final Path targetFile = target.resolve(name + ".html"); if (Files.exists(targetFile)) { try { final String existingContent = new String(Files.readAllBytes(targetFile), "utf-8"); if (existingContent.equals(content)) { logger.info("Skipping export of component {}, no changes.", name); doExport = false; } } catch (IOException ignore) {} } configuration.put(name, properties); exportConfiguration(node, properties); if (doExport) { try (final OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(targetFile.toFile()))) { writer.write(content); writer.flush(); writer.close(); } catch (IOException ioex) { logger.warn("", ioex); } } } } } tx.success(); } try (final Writer fos = new OutputStreamWriter(new FileOutputStream(configTarget.toFile()))) { getGson().toJson(configuration, fos); } catch (IOException ioex) { logger.warn("", ioex); } } private void exportTemplates(final Path target, final Path configTarget) throws FrameworkException { final Map<String, Object> configuration = new TreeMap<>(); final App app = StructrApp.getInstance(); try (final Tx tx = app.tx()) { // export template nodes anywhere in the pages tree which are not related to shared components for (final Template template : app.nodeQuery(Template.class).getAsList()) { final boolean inTrash = template.inTrash(); final boolean isShared = template.getProperty(DOMNode.sharedComponent) != null; if (inTrash || isShared) { continue; } exportTemplateSource(target, template, configuration); } tx.success(); } try (final Writer fos = new OutputStreamWriter(new FileOutputStream(configTarget.toFile()))) { getGson().toJson(configuration, fos); } catch (IOException ioex) { logger.warn("", ioex); } } private void exportTemplateSource(final Path target, final DOMNode template, final Map<String, Object> configuration) throws FrameworkException { final Map<String, Object> properties = new TreeMap<>(); boolean doExport = true; final String content = template.getProperty(Template.content); if (content != null) { // name or uuid String name = template.getProperty(AbstractNode.name); if (name == null) { name = template.getUuid(); } final Path targetFile = target.resolve(name + ".html"); if (Files.exists(targetFile)) { try { final String existingContent = new String(Files.readAllBytes(targetFile), "utf-8"); if (existingContent.equals(content)) { logger.info("Skipping export of template {}, no changes.", name); doExport = false; } } catch (IOException ignore) {} } configuration.put(name, properties); exportConfiguration(template, properties); if (doExport) { try (final OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(targetFile.toFile()))) { writer.write(content); writer.flush(); writer.close(); } catch (IOException ioex) { logger.warn("", ioex); } } } } private void exportResourceAccessGrants(final Path target) throws FrameworkException { final List<Map<String, Object>> grants = new LinkedList<>(); final App app = StructrApp.getInstance(); try (final Tx tx = app.tx()) { for (final ResourceAccess res : app.nodeQuery(ResourceAccess.class).sort(ResourceAccess.signature).getAsList()) { final Map<String, Object> grant = new TreeMap<>(); grants.add(grant); grant.put("signature", res.getProperty(ResourceAccess.signature)); grant.put("flags", res.getProperty(ResourceAccess.flags)); } tx.success(); } try (final Writer fos = new OutputStreamWriter(new FileOutputStream(target.toFile()))) { getGson().toJson(grants, fos); } catch (IOException ioex) { logger.warn("", ioex); } } private void exportSchema(final Path target) throws FrameworkException { try { final JsonSchema schema = StructrSchema.createFromDatabase(StructrApp.getInstance()); try (final Writer writer = new FileWriter(target.toFile())) { writer.append(schema.toString()); writer.append("\n"); writer.flush(); } catch (IOException ioex) { logger.warn("", ioex); } } catch (URISyntaxException x) { logger.warn("", x); } } private void exportConfiguration(final DOMNode node, final Map<String, Object> config) throws FrameworkException { if (node.isVisibleToPublicUsers()) { putIf(config, "visibleToPublicUsers", true); } if (node.isVisibleToAuthenticatedUsers()) { putIf(config, "visibleToAuthenticatedUsers", true); } putIf(config, "contentType", node.getProperty(Content.contentType)); if (node instanceof Template) { // mark this template as being shared putIf(config, "shared", Boolean.toString(node.isSharedComponent())); } if (node instanceof Page) { putIf(config, "position", node.getProperty(Page.position)); putIf(config, "showOnErrorCodes", node.getProperty(Page.showOnErrorCodes)); putIf(config, "showConditions", node.getProperty(Page.showConditions)); putIf(config, "hideConditions", node.getProperty(Page.hideConditions)); putIf(config, "dontCache", node.getProperty(Page.dontCache)); putIf(config, "cacheForSeconds", node.getProperty(Page.cacheForSeconds)); putIf(config, "pageCreatesRawData", node.getProperty(Page.pageCreatesRawData)); putIf(config, "basicAuthRealm", node.getProperty(Page.basicAuthRealm)); putIf(config, "enableBasicAuth", node.getProperty(Page.enableBasicAuth)); } // export all dynamic properties for (final PropertyKey key : StructrApp.getConfiguration().getPropertySet(node.getClass(), PropertyView.All)) { // only export dynamic (=> additional) keys if (key.isDynamic()) { System.out.println("################################################################################## EXPORTING " + key.jsonName()); putIf(config, key.jsonName(), node.getProperty(key)); } } exportOwnershipAndSecurity(node, config); } private void exportFileConfiguration(final AbstractFile file, final Map<String, Object> config) { if (file.isVisibleToPublicUsers()) { putIf(config, "visibleToPublicUsers", true); } if (file.isVisibleToAuthenticatedUsers()) { putIf(config, "visibleToAuthenticatedUsers", true); } putIf(config, "contentType", file.getProperty(FileBase.contentType)); putIf(config, "cacheForSeconds", file.getProperty(FileBase.cacheForSeconds)); putIf(config, "useAsJavascriptLibrary", file.getProperty(FileBase.useAsJavascriptLibrary)); putIf(config, "includeInFrontendExport", file.getProperty(FileBase.includeInFrontendExport)); putIf(config, "basicAuthRealm", file.getProperty(FileBase.basicAuthRealm)); putIf(config, "enableBasicAuth", file.getProperty(FileBase.enableBasicAuth)); if (file instanceof Image) { putIf(config, "isThumbnail", file.getProperty(Image.isThumbnail)); putIf(config, "isImage", file.getProperty(Image.isImage)); putIf(config, "width", file.getProperty(Image.width)); putIf(config, "height", file.getProperty(Image.height)); } // export all dynamic properties for (final PropertyKey key : StructrApp.getConfiguration().getPropertySet(file.getClass(), PropertyView.All)) { // only export dynamic (=> additional) keys if (key.isDynamic()) { putIf(config, key.jsonName(), file.getProperty(key)); } } exportOwnershipAndSecurity(file, config); } private void exportOwnershipAndSecurity(final AbstractNode node, final Map<String, Object> config) { // export unique name of owner node to pages.json final Principal owner = node.getOwnerNode(); if (owner != null) { final Map<String, Object> map = new HashMap<>(); map.put("name", owner.getName()); config.put("owner", map); } // export security grants final List<Map<String, Object>> grantees = new LinkedList<>(); for (final Security security : node.getSecurityRelationships()) { if (security != null) { final Map<String, Object> grant = new TreeMap<>(); grant.put("name", security.getSourceNode().getProperty(AbstractNode.name)); grant.put("allowed", StringUtils.join(security.getPermissions(), ",")); grantees.add(grant); } } // export non-empty collection only if (!grantees.isEmpty()) { config.put("grantees", grantees); } } private void exportMailTemplates(final Path target) throws FrameworkException { final List<Map<String, Object>> mailTemplates = new LinkedList<>(); final App app = StructrApp.getInstance(); try (final Tx tx = app.tx()) { for (final MailTemplate mailTemplate : app.nodeQuery(MailTemplate.class).sort(MailTemplate.name).getAsList()) { final Map<String, Object> entry = new TreeMap<>(); mailTemplates.add(entry); entry.put("name", mailTemplate.getProperty(MailTemplate.name)); entry.put("text", mailTemplate.getProperty(MailTemplate.text)); entry.put("locale", mailTemplate.getProperty(MailTemplate.locale)); entry.put("visibleToAuthenticatedUsers", mailTemplate.getProperty(MailTemplate.visibleToAuthenticatedUsers)); entry.put("visibleToPublicUsers", mailTemplate.getProperty(MailTemplate.visibleToPublicUsers)); } tx.success(); } try (final Writer fos = new OutputStreamWriter(new FileOutputStream(target.toFile()))) { getGson().toJson(mailTemplates, fos); } catch (IOException ioex) { logger.warn("", ioex); } } private void exportWidgets(final Path target) throws FrameworkException { final List<Map<String, Object>> widgets = new LinkedList<>(); final App app = StructrApp.getInstance(); try (final Tx tx = app.tx()) { for (final Widget widget : app.nodeQuery(Widget.class).sort(Widget.name).getAsList()) { final Map<String, Object> entry = new TreeMap<>(); widgets.add(entry); entry.put("name", widget.getProperty(Widget.name)); entry.put("source", widget.getProperty(Widget.source)); entry.put("description", widget.getProperty(Widget.description)); entry.put("isWidget", widget.getProperty(Widget.isWidget)); entry.put("treePath", widget.getProperty(Widget.treePath)); entry.put("pictures", widget.getProperty(Widget.pictures)); entry.put("configuration", widget.getProperty(Widget.configuration)); entry.put("visibleToAuthenticatedUsers", widget.getProperty(Widget.visibleToAuthenticatedUsers)); entry.put("visibleToPublicUsers", widget.getProperty(Widget.visibleToPublicUsers)); } tx.success(); } try (final Writer fos = new OutputStreamWriter(new FileOutputStream(target.toFile()))) { getGson().toJson(widgets, fos); } catch (IOException ioex) { logger.warn("", ioex); } } private void exportSchemaMethods(final Path target) throws FrameworkException { final List<Map<String, Object>> schemaMethods = new LinkedList<>(); final App app = StructrApp.getInstance(); try (final Tx tx = app.tx()) { for (final SchemaMethod schemaMethod : app.nodeQuery(SchemaMethod.class).and(SchemaMethod.schemaNode, null).sort(SchemaMethod.name).getAsList()) { final Map<String, Object> entry = new TreeMap<>(); schemaMethods.add(entry); entry.put("name", schemaMethod.getProperty(SchemaMethod.name)); entry.put("comment", schemaMethod.getProperty(SchemaMethod.comment)); entry.put("source", schemaMethod.getProperty(SchemaMethod.source)); entry.put("virtualFileName", schemaMethod.getProperty(SchemaMethod.virtualFileName)); entry.put("visibleToAuthenticatedUsers", schemaMethod.getProperty(SchemaMethod.visibleToAuthenticatedUsers)); entry.put("visibleToPublicUsers", schemaMethod.getProperty(SchemaMethod.visibleToPublicUsers)); } tx.success(); } try (final Writer fos = new OutputStreamWriter(new FileOutputStream(target.toFile()))) { getGson().toJson(schemaMethods, fos); } catch (IOException ioex) { logger.warn("", ioex); } } private void exportLocalizations(final Path target) throws FrameworkException { final List<Map<String, Object>> localizations = new LinkedList<>(); final App app = StructrApp.getInstance(); try (final Tx tx = app.tx()) { for (final Localization localization : app.nodeQuery(Localization.class).sort(Localization.name).getAsList()) { final Map<String, Object> entry = new TreeMap<>(); localizations.add(entry); entry.put("name", localization.getProperty(Localization.name)); entry.put("localizedName", localization.getProperty(Localization.localizedName)); entry.put("domain", localization.getProperty(Localization.domain)); entry.put("locale", localization.getProperty(Localization.locale)); entry.put("imported", localization.getProperty(Localization.imported)); entry.put("visibleToAuthenticatedUsers", localization.getProperty(MailTemplate.visibleToAuthenticatedUsers)); entry.put("visibleToPublicUsers", localization.getProperty(MailTemplate.visibleToPublicUsers)); } tx.success(); } try (final Writer fos = new OutputStreamWriter(new FileOutputStream(target.toFile()))) { getGson().toJson(localizations, fos); } catch (IOException ioex) { logger.warn("", ioex); } } private void putIf(final Map<String, Object> target, final String key, final Object value) { if (value != null) { target.put(key, value); } } private List<Map<String, Object>> readConfigList(final Path pagesConf) { try (final Reader reader = Files.newBufferedReader(pagesConf, Charset.forName("utf-8"))) { return getGson().fromJson(reader, List.class); } catch (IOException ioex) { logger.warn("", ioex); } return Collections.emptyList(); } private <T extends NodeInterface> void importMapData(final Class<T> type, final Map<String, Object> data) throws FrameworkException { final SecurityContext context = SecurityContext.getSuperUserInstance(); final App app = StructrApp.getInstance(); try (final Tx tx = app.tx()) { for (final T toDelete : app.nodeQuery(type).getAsList()) { app.delete(toDelete); } for (final Entry<String, Object> entry : data.entrySet()) { final String key = entry.getKey(); final Object val = entry.getValue(); if (val instanceof Map) { final Map<String, Object> values = (Map<String, Object>)val; final PropertyMap properties = PropertyMap.inputTypeToJavaType(context, type, values); properties.put(AbstractNode.name, key); app.create(type, properties); } } tx.success(); } } private <T extends NodeInterface> void importListData(final Class<T> type, final List<Map<String, Object>> data) throws FrameworkException { final SecurityContext context = SecurityContext.getSuperUserInstance(); final App app = StructrApp.getInstance(); try (final Tx tx = app.tx()) { for (final T toDelete : app.nodeQuery(type).getAsList()) { app.delete(toDelete); } for (final Map<String, Object> entry : data) { app.create(type, PropertyMap.inputTypeToJavaType(context, type, entry)); } tx.success(); } } // ----- public static methods ----- public static boolean okToExport(final AbstractFile file) { // export non-derived types only (ignore things that extend built-in file/folder etc.) if (!exportFileTypes.contains(file.getType())) { return false; } for (final AbstractRelationship rel : file.getRelationships()) { if (rel instanceof Security) { continue; } if (rel instanceof PrincipalOwnsNode) { continue; } if (rel instanceof FolderChildren) { continue; } if (rel instanceof FileChildren) { continue; } if (rel instanceof FileSiblings) { continue; } if (rel instanceof MinificationSource) { continue; } if (rel instanceof UserFavoriteFile) { continue; } if (rel instanceof UserWorkDir) { continue; } if (rel instanceof Folders) { continue; } if (rel instanceof org.structr.web.entity.relation.Files) { continue; } if (rel instanceof Images) { continue; } if (rel instanceof Thumbnails) { continue; } if (rel instanceof ResourceLink) { continue; } if (rel instanceof UserFavoriteFavoritable) { continue; } // if none of the above matched, the file should not be exported return false; } return true; } }