/** * 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.deploy; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.structr.common.Permission; import org.structr.common.error.FrameworkException; import org.structr.core.app.StructrApp; import org.structr.core.entity.AbstractNode; import org.structr.core.entity.Principal; import org.structr.core.property.PropertyMap; import org.structr.web.entity.AbstractFile; import org.structr.web.entity.LinkSource; import org.structr.web.entity.Linkable; 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.importer.CommentHandler; /** * */ public class DeploymentCommentHandler implements CommentHandler { private static final Set<Character> separators = new LinkedHashSet<>(Arrays.asList(new Character[] { ',', ';', '(', ')', ' ', '\t', '\n', '\r' } )); private static final Logger logger = LoggerFactory.getLogger(DeploymentCommentHandler.class.getName()); private static final Map<String, Handler> handlers = new LinkedHashMap<>(); static { handlers.put("public-only", (Page page, DOMNode node, final String parameters) -> { final PropertyMap changedProperties = new PropertyMap(); changedProperties.put(AbstractNode.visibleToPublicUsers, true); changedProperties.put(AbstractNode.visibleToAuthenticatedUsers, false); node.setProperties(node.getSecurityContext(), changedProperties); }); handlers.put("public", (Page page, DOMNode node, final String parameters) -> { final PropertyMap changedProperties = new PropertyMap(); changedProperties.put(AbstractNode.visibleToPublicUsers, true); changedProperties.put(AbstractNode.visibleToAuthenticatedUsers, true); node.setProperties(node.getSecurityContext(), changedProperties); }); handlers.put("protected", (Page page, DOMNode node, final String parameters) -> { final PropertyMap changedProperties = new PropertyMap(); changedProperties.put(AbstractNode.visibleToPublicUsers, false); changedProperties.put(AbstractNode.visibleToAuthenticatedUsers, true); node.setProperties(node.getSecurityContext(), changedProperties); }); handlers.put("private", (Page page, DOMNode node, final String parameters) -> { final PropertyMap changedProperties = new PropertyMap(); changedProperties.put(AbstractNode.visibleToPublicUsers, false); changedProperties.put(AbstractNode.visibleToAuthenticatedUsers, false); node.setProperties(node.getSecurityContext(), changedProperties); }); handlers.put("link", (Page page, DOMNode node, final String parameters) -> { if (node instanceof LinkSource) { final Linkable file = StructrApp.getInstance().nodeQuery(Linkable.class).and(AbstractFile.path, parameters).getFirst(); if (file != null) { final LinkSource linkSource = (LinkSource)node; linkSource.setProperties(linkSource.getSecurityContext(), new PropertyMap(LinkSource.linkable, file)); } } }); handlers.put("content", (Page page, DOMNode node, final String parameters) -> { node.setProperties(node.getSecurityContext(), new PropertyMap(Content.contentType, parameters)); }); handlers.put("show", (Page page, DOMNode node, final String parameters) -> { node.setProperties(node.getSecurityContext(), new PropertyMap(DOMNode.showConditions, parameters)); }); handlers.put("hide", (Page page, DOMNode node, final String parameters) -> { node.setProperties(node.getSecurityContext(), new PropertyMap(DOMNode.hideConditions, parameters)); }); handlers.put("owner", (Page page, DOMNode node, final String parameters) -> { final Principal owner = StructrApp.getInstance().nodeQuery(Principal.class).andName(parameters).getFirst(); if (owner != null) { // && !owner.equals(page.getOwnerNode())) { node.setProperty(AbstractNode.owner, owner); } else { logger.warn("Unknown owner {}, ignoring.", parameters); } }); handlers.put("grant", (Page page, DOMNode node, final String parameters) -> { final String[] parts = parameters.split("[,]+"); if (parts.length == 2) { final Principal grantee = StructrApp.getInstance().nodeQuery(Principal.class).andName(parts[0]).getFirst(); if (grantee != null) { for (final char c : parts[1].toCharArray()) { switch (c) { case 'a': node.grant(Permission.accessControl, grantee); break; case 'r': node.grant(Permission.read, grantee); break; case 'w': node.grant(Permission.write, grantee); break; case 'd': node.grant(Permission.delete, grantee); break; default: logger.warn("Invalid @grant permission {}, must be one of [a, r, w, d].", c); } } } else { logger.warn("Unknown grantee {}, ignoring.", parts[0]); } } else { logger.warn("Invalid @grant instruction {}, must be like @structr:grant(userName,rw).", parameters); } }); } private char[] source = null; private int currentPosition = 0; private int sourceLength = 0; @Override public boolean containsInstructions(final String comment) { try { return parseInstructions(null, null, comment, false); } catch (FrameworkException fex) { logger.warn("Unexpected exception, no changes should be made in this method: {}", fex.getMessage()); } return false; } @Override public boolean handleComment(final Page page, final DOMNode node, final String comment, final boolean apply) throws FrameworkException { return parseInstructions(page, node, comment.trim(), apply); } // ----- private methods ----- private boolean parseInstructions(final Page page, final DOMNode node, final String src, final boolean apply) throws FrameworkException { this.source = src.toCharArray(); this.sourceLength = src.length(); this.currentPosition = 0; boolean hit = false; while (currentPosition < sourceLength) { if (findSequence("@structr:")) { // comment contains instruction, can be removed upon import hit = true; // currentPosition is now at the first letter of the actual instruction final String token = getNextToken(); final Handler handler = handlers.get(token); String parameters = null; if (handler != null) { // additional characters.. if (hasMore()) { final char c = source[currentPosition++]; switch (c) { case '(': parameters = getUntilClosingParenthesis(); break; case ',': // next instruction, ignore break; } } // only apply if instructed to (can be used to check // if the comment source actually contains instructions) if (apply) { handler.apply(page, node, parameters); } } else { logger.warn("Unknown token {}, expected one of {}.", new Object[] { token, handlers.keySet() }); break; } } } return hit; } private boolean findSequence(final String sequence) { final StringBuilder buf = new StringBuilder(); final char[] seq = sequence.toCharArray(); final int len = seq.length; boolean sequenceStarted = false; for (int i=0; i<len; i++) { if (hasMore()) { final char c = Character.toLowerCase(source[currentPosition++]); final char s = Character.toLowerCase(seq[i]); // skip whitespace before sequence if (!sequenceStarted && Character.isWhitespace(c) || separators.contains(c)) { // reset sequence to start i = -1; continue; } if (c != s) { return false; } sequenceStarted = true; buf.append(c); } } return buf.toString().equals(sequence); } private String getNextToken() { final StringBuilder buf = new StringBuilder(); while (hasMore()) { final char c = source[currentPosition]; if (Character.isWhitespace(c) || separators.contains(c)) { break; } buf.append(c); // advance position only if the character was actually used currentPosition++; } return buf.toString(); } private String getUntilClosingParenthesis() { final StringBuilder buf = new StringBuilder(); int count = 1; while (hasMore()) { final char c = source[currentPosition++]; if (c == '(') { count++; } if (c == ')' && --count == 0) { break; } buf.append(c); } return buf.toString(); } private boolean hasMore() { return currentPosition < sourceLength; } private static interface Handler { void apply(final Page page, final DOMNode node, final String parameters) throws FrameworkException; } }