/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.xwiki.rendering.internal.macro.script;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;

import org.xwiki.component.annotation.Component;
import org.xwiki.observation.event.CancelableEvent;
import org.xwiki.rendering.block.Block;
import org.xwiki.rendering.block.MacroMarkerBlock;
import org.xwiki.rendering.block.match.ClassBlockMatcher;
import org.xwiki.rendering.macro.Macro;
import org.xwiki.rendering.macro.MacroId;
import org.xwiki.rendering.macro.MacroLookupException;
import org.xwiki.rendering.macro.MacroManager;
import org.xwiki.rendering.macro.script.ScriptMacro;
import org.xwiki.rendering.macro.script.ScriptMacroParameters;
import org.xwiki.rendering.transformation.MacroTransformationContext;

/**
 * Listens to {@link org.xwiki.script.event.ScriptEvaluatingEvent} and cancels the evaluation if the script
 * is nested.
 * 
 * @version $Id$
 * @since 2.5M1
 */
@Component
@Named("nestedscriptmacrovalidator")
@Singleton
public class NestedScriptMacroValidatorListener extends AbstractScriptCheckerListener
{
    /**
     * Used to find the type of a Macro defined by a Macro Marker block; we're interested to prevent nested scripts only
     * in Script macros.
     */
    @Inject
    private MacroManager macroManager;

    @Override
    public String getName()
    {
        return "nestedscriptmacrovalidator";
    }

    @Override
    protected void check(CancelableEvent event, MacroTransformationContext context, ScriptMacroParameters parameters)
    {
        // Traverse the XDOM tree up to the root
        if (context.getCurrentMacroBlock() != null) {
            MacroMarkerBlock parent = context.getCurrentMacroBlock().getFirstBlock(
                new ClassBlockMatcher(MacroMarkerBlock.class), Block.Axes.ANCESTOR);
            while (parent != null) {
                String parentId = parent.getId();
                try {
                    Macro< ? > macro = this.macroManager.getMacro(new MacroId(parentId));
                    if (macro instanceof ScriptMacro) {
                        event.cancel("Nested scripts are not allowed");
                    } else if (macro instanceof NestedScriptMacroEnabled) {
                        // This macro has the right to produce script macro whatever the parent.
                        return;
                    } else if ("include".equals(parentId)) {
                        // Included documents intercept the chain of nested script macros with XWiki syntax
                        // TODO: find cleaner way. I don't think we can make include macro depends on script macro to
                        // use NestedScriptMacroEnabled, we should maybe find something more generic
                        return;
                    }
                } catch (MacroLookupException exception) {
                    // Shouldn't happen, the parent macro was already successfully executed earlier
                }
                parent = parent.getFirstBlock(new ClassBlockMatcher(MacroMarkerBlock.class), Block.Axes.ANCESTOR);
            }
        }
    }
}

@@ -1255,31 +1255,186 @@
 ock.
-match.ClassBlockMatcher
+MetaDataBlock;%0Aimport org.xwiki.rendering.block.match.ClassBlockMatcher;%0Aimport org.xwiki.rendering.block.match.MetadataBlockMatcher;%0Aimport org.xwiki.rendering.listener.MetaData
 ;%0Aim
@@ -3091,24 +3091,60 @@
 iptMacro) %7B%0A
+                        // Find the%0A
             
@@ -3168,16 +3168,30 @@
 .cancel(
+String.format(
 %22Nested 
@@ -3213,17 +3213,379 @@
  allowed
-%22
+. Current Script Macro %5B%25s%5D %22%0A                            + %22(source %5B%25s%5D) is executed inside Script Macro %5B%25s%5D (source %5B%25s%5D)%22,%0A                            context.getCurrentMacroBlock().getId(),%0A                            extractSourceContentReference(context.getCurrentMacroBlock()), parentId,%0A                            extractSourceContentReference(parent))
 );%0A     
@@ -4524,32 +4524,32 @@
 ;%0A            %7D%0A
-
         %7D%0A    %7D%0A
@@ -4547,11 +4547,597 @@
  %7D%0A    %7D
+%0A%0A    /**%0A     * @param source the blocks from where to try to extract the source content%0A     * @return the source content reference or null if none is found%0A     */%0A    private String extractSourceContentReference(Block source)%0A    %7B%0A        String contentSource = null;%0A        MetaDataBlock metaDataBlock =%0A            source.getFirstBlock(new MetadataBlockMatcher(MetaData.SOURCE), Block.Axes.ANCESTOR);%0A        if (metaDataBlock != null) %7B%0A            contentSource = (String) metaDataBlock.getMetaData().getMetaData(MetaData.SOURCE);%0A        %7D%0A        return contentSource;%0A    %7D
 %0A%7D%0A

Improve exception message to make it easier to debug problems. As an example here's the type of message we get now after this commit: "Nested scripts are not allowed. Current Script Macro [velocity] (source [xwiki:FAQCode.FAQMacro]) is executed inside Script Macro [velocity] (source [xwiki:FAQCode.FAQHomeSheet])".