You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@tapestry.apache.org by Caiiiycuk <ca...@gmail.com> on 2011/10/04 16:45:56 UTC

Another approach to dynamic templating (T5.1.0.5)

Hi. Let me explain. Imagine that we have standard tapestry page:
Templates.java and of course we have template of this page Templates.tml and
it works perfect. But I need to change template of this page by user
request. More precisely I want to use one java class (Templates.java) with
many templates files (Template1.tml, Template2.tml, etc). And of course
EventLinks, ActionLinks, Zones, Componenst should work as usual.

Our team has spent much time to get this work by using tapestry standard
features. But no luck. I have working solution based on reflection wich
brakes oop ideology.
I introduce an interface:

public interface PageLoadService {
	Page loadPage(String defaultPageName, String templateName, Locale locale,
Object coreComponent);
} 

Where defaultPageName is default template (Templates.tml), templateName is
overriding template (Template1.tml, etc) and coreComponent is instance of
Page (Template.java instance). 

And in Template.onActivate() i use this interface like this:

	@Inject
	private PageResponseRenderer renderer;
	
	@Inject
	private PageLoadService pageLoadService;

	public void onActivate() {
		if (request.isXHR()) {
			//Ajax 
			return;
		}
		
		if (request.getPath().contains(":") || request.getPath().contains(".")) {
			//EVENT, ACTION, ZONE
			return;
		}
		
		if (template == null || template.equals(BASIC_TEMPLATE)) {
			//Default approach
			return;
		}

		Page page = pageLoadService.loadPage(
				BASIC_TEMPLATE, 
				template, 
				Locale.getDefault(),
				this);

		try {
			page.attached();
			renderer.renderPageResponse(page);
			page.detached();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

Let me explain. I'm trying to use default approach (without templating
Template.tml+Template.java) to handle ajax,zone,event|action links requests,
and PageResponseRenderer to render page in other requests. And it works
(tested T5.1.05 and T5.2.0, 5.2.6). But tapestry is huge project, and i'm
not sure if there are hidden problems, what do you think?

---
Realization of PageLoadService is really ugly:

package org.apache.tapestry5.internal.pageload;

import java.lang.reflect.Method;
import java.util.Locale;

import org.apache.tapestry5.internal.parser.ComponentTemplate;
import org.apache.tapestry5.internal.services.ComponentInstantiatorSource;
import org.apache.tapestry5.internal.services.Instantiator;
import org.apache.tapestry5.internal.services.PageLoader;
import org.apache.tapestry5.internal.services.PersistentFieldManager;
import org.apache.tapestry5.internal.services.TemplateParser;
import org.apache.tapestry5.internal.structure.ComponentPageElement;
import
org.apache.tapestry5.internal.structure.ComponentPageElementResources;
import
org.apache.tapestry5.internal.structure.ComponentPageElementResourcesSource;
import org.apache.tapestry5.internal.structure.Page;
import org.apache.tapestry5.internal.structure.PageImpl;
import org.apache.tapestry5.ioc.Resource;
import org.apache.tapestry5.model.ComponentModel;
import org.apache.tapestry5.services.ComponentClassResolver;

public class PageLoadServiceT5_1Impl implements PageLoadService {

        private final PageLoaderImpl internalPageLoader;

        private final ComponentInstantiatorSource
internalInstantiatorSource;
        private final TemplateParser internalTemplateParser;
        private final ComponentPageElementResourcesSource
internalResourcesSource;
        private final ComponentClassResolver internalComponentClassResolver;
        private final PersistentFieldManager internalPersistentFieldManager;
        private final Method internalProgramAssembler;
   
        public PageLoadServiceT5_1Impl(PageLoader tapestryPageLoader,
TemplateParser templateParser) {
                this.internalPageLoader =
                        ReflectionToolkit.getDelegate(tapestryPageLoader);
                this.internalInstantiatorSource =
                        ReflectionToolkit.getField(internalPageLoader,
"instantiatorSource");
                this.internalResourcesSource =
                        ReflectionToolkit.getField(internalPageLoader,
"resourcesSource");
                this.internalComponentClassResolver =
                        ReflectionToolkit.getField(internalPageLoader,
"componentClassResolver");
                this.internalPersistentFieldManager =
                        ReflectionToolkit.getField(internalPageLoader,
"persistentFieldManager");
                this.internalProgramAssembler =
                        ReflectionToolkit.getMethod(internalPageLoader,
"programAssembler");

                this.internalTemplateParser = templateParser;
        }

        public Page loadPage(String defaultPageName, String pageName, Locale
locale, Object coreComponent) {
        Page page = new PageImpl(defaultPageName, locale,
internalPersistentFieldManager);
        ComponentAssembler assembler = createAssembler(pageName,
coreComponent.getClass().getCanonicalName(), locale);
        ComponentPageElement rootElement =
assembler.assembleRootComponent(page);

       
        Object newCoreComponent = ReflectionToolkit.getField(rootElement,
"coreComponent");
        ReflectionToolkit.copyAnnotatedFields(coreComponent,
newCoreComponent, CopyField.class);
       
        page.setRootElement(rootElement);
        page.loaded();
        return page;
        }

    private ComponentAssembler createAssembler(String pageName, String
className, Locale locale)
    {
        Instantiator instantiator =
internalInstantiatorSource.getInstantiator(className);

        ComponentModel componentModel = instantiator.getModel();

        Resource templateResource =
componentModel.getBaseResource().forFile(pageName + ".tml");
                ComponentTemplate template =
internalTemplateParser.parseTemplate(templateResource);

        ComponentPageElementResources resources =
internalResourcesSource.get(locale);

        ComponentAssembler assembler = new ComponentAssemblerImpl(
        internalPageLoader,
        internalInstantiatorSource,
        internalComponentClassResolver,
        instantiator,
        resources,
        locale);

        try {
                        internalProgramAssembler.invoke(internalPageLoader,
assembler, template);
                } catch (Exception e) {
                        throw new RuntimeException(e);
                }
       
        return assembler;
    }
   
}

---
Also this approach always creates new instance of Template.java and we
loosing setted context. But it is not a big problem - just copy fields from
one class to another.

--
View this message in context: http://tapestry.1045711.n5.nabble.com/Another-approach-to-dynamic-templating-T5-1-0-5-tp4868869p4868869.html
Sent from the Tapestry - User mailing list archive at Nabble.com.

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org


Re: Another approach to dynamic templating (T5.1.0.5)

Posted by Caiiiycuk <ca...@gmail.com>.
Our project is used Tapestry 5.1.0.5, and we unable to upgrade it to lastest
version (5.3) and even 5.2, cause we have lot of legacy code. But it is not
important, just look to PageLoaderImpl (from 5.3 sources):

    public ComponentAssembler getAssembler(String className,
ComponentResourceSelector selector)
    {
        Key key = new Key(className, selector);

        ComponentAssembler result = cache.get(key);

        if (result == null)
        {
            // There's a window here where two threads may create the same
assembler simultaneously;
            // the extra assembler will be discarded.

            result = createAssembler(className, selector);

            cache.put(key, result);
        }

        return result;
    }

    private ComponentAssembler createAssembler(final String className, final
ComponentResourceSelector selector)
    {
        return tracker.invoke("Creating ComponentAssembler for " +
className, new Invokable<ComponentAssembler>()
        {
            public ComponentAssembler invoke()
            {
                Instantiator instantiator =
instantiatorSource.getInstantiator(className);

                ComponentModel componentModel = instantiator.getModel();

                ComponentTemplate template =
templateSource.getTemplate(componentModel, selector);

                ComponentPageElementResources resources =
resourcesSource.get(selector);

                ComponentAssembler assembler = new
ComponentAssemblerImpl(PageLoaderImpl.this, instantiatorSource,
                        componentClassResolver, instantiator, resources,
tracker, request, symbolSource);

                // "Program" the assembler by adding actions to it. The
actions interact with a
                // PageAssembly object (a fresh one for each new page being
created).

                programAssembler(assembler, template);

                return assembler;
            }
        });
    }

Look for cache, for example we have cache-key {ClassName(Template.java),
ComponentResourceSelector(MyImagineSelector.java)}. On app start this cache
is empty and we go to createAssembler method, where real template is created 

ComponentTemplate template = templateSource.getTemplate(componentModel,
selector); //Templates.tml

And after that assembler putted into cache. So we have cached template :(
And how i can change template now? On next pageload with same logical name
and resource selector i hit the cache. I should create new resourceSelector
for each request, and this resourceSelector need access to page logic. I
really think that not easy to use. However i think this approach is much
better than mine, but only since 5.3. 

In 5.2 & 5.1 we have more aggresive cache (by logical name). I try to avoid
this cache by assembling PageImpl manually and render it manually. If you
look to my service you can see that it use methods from PageLoaderImpl to
assembly page (by reflection).

Also you can try to use Dynamic component
http://tapestry.apache.org/5.3/apidocs/org/apache/tapestry5/corelib/components/Dynamic.html.
But it is not worked for me (it throws index out of bounds exception). And
it is in Tapestry 5.3.

--
View this message in context: http://tapestry.1045711.n5.nabble.com/Another-approach-to-dynamic-templating-T5-1-0-5-tp4868869p4869388.html
Sent from the Tapestry - User mailing list archive at Nabble.com.

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org


Re: Another approach to dynamic templating (T5.1.0.5)

Posted by Chris Poulsen <ma...@nesluop.dk>.
Interesting! How does your solution differ from the template skinning
feature of T5.3? (
http://blog.tapestry5.de/index.php/2011/06/24/template-skinning/ )
- Or is it just the same functionality for pre 5.3 ?

-- 
Chris


On Tue, Oct 4, 2011 at 4:45 PM, Caiiiycuk <ca...@gmail.com> wrote:

> Hi. Let me explain. Imagine that we have standard tapestry page:
> Templates.java and of course we have template of this page Templates.tml
> and
> it works perfect. But I need to change template of this page by user
> request. More precisely I want to use one java class (Templates.java) with
> many templates files (Template1.tml, Template2.tml, etc). And of course
> EventLinks, ActionLinks, Zones, Componenst should work as usual.
>
> Our team has spent much time to get this work by using tapestry standard
> features. But no luck. I have working solution based on reflection wich
> brakes oop ideology.
> I introduce an interface:
>
> public interface PageLoadService {
>        Page loadPage(String defaultPageName, String templateName, Locale
> locale,
> Object coreComponent);
> }
>
> Where defaultPageName is default template (Templates.tml), templateName is
> overriding template (Template1.tml, etc) and coreComponent is instance of
> Page (Template.java instance).
>
> And in Template.onActivate() i use this interface like this:
>
>        @Inject
>        private PageResponseRenderer renderer;
>
>        @Inject
>        private PageLoadService pageLoadService;
>
>        public void onActivate() {
>                if (request.isXHR()) {
>                        //Ajax
>                        return;
>                }
>
>                if (request.getPath().contains(":") ||
> request.getPath().contains(".")) {
>                        //EVENT, ACTION, ZONE
>                        return;
>                }
>
>                if (template == null || template.equals(BASIC_TEMPLATE)) {
>                        //Default approach
>                        return;
>                }
>
>                Page page = pageLoadService.loadPage(
>                                BASIC_TEMPLATE,
>                                template,
>                                Locale.getDefault(),
>                                this);
>
>                try {
>                        page.attached();
>                        renderer.renderPageResponse(page);
>                        page.detached();
>                } catch (IOException e) {
>                        e.printStackTrace();
>                }
>        }
>
> Let me explain. I'm trying to use default approach (without templating
> Template.tml+Template.java) to handle ajax,zone,event|action links
> requests,
> and PageResponseRenderer to render page in other requests. And it works
> (tested T5.1.05 and T5.2.0, 5.2.6). But tapestry is huge project, and i'm
> not sure if there are hidden problems, what do you think?
>
> ---
> Realization of PageLoadService is really ugly:
>
> package org.apache.tapestry5.internal.pageload;
>
> import java.lang.reflect.Method;
> import java.util.Locale;
>
> import org.apache.tapestry5.internal.parser.ComponentTemplate;
> import org.apache.tapestry5.internal.services.ComponentInstantiatorSource;
> import org.apache.tapestry5.internal.services.Instantiator;
> import org.apache.tapestry5.internal.services.PageLoader;
> import org.apache.tapestry5.internal.services.PersistentFieldManager;
> import org.apache.tapestry5.internal.services.TemplateParser;
> import org.apache.tapestry5.internal.structure.ComponentPageElement;
> import
> org.apache.tapestry5.internal.structure.ComponentPageElementResources;
> import
>
> org.apache.tapestry5.internal.structure.ComponentPageElementResourcesSource;
> import org.apache.tapestry5.internal.structure.Page;
> import org.apache.tapestry5.internal.structure.PageImpl;
> import org.apache.tapestry5.ioc.Resource;
> import org.apache.tapestry5.model.ComponentModel;
> import org.apache.tapestry5.services.ComponentClassResolver;
>
> public class PageLoadServiceT5_1Impl implements PageLoadService {
>
>        private final PageLoaderImpl internalPageLoader;
>
>        private final ComponentInstantiatorSource
> internalInstantiatorSource;
>        private final TemplateParser internalTemplateParser;
>        private final ComponentPageElementResourcesSource
> internalResourcesSource;
>        private final ComponentClassResolver internalComponentClassResolver;
>        private final PersistentFieldManager internalPersistentFieldManager;
>        private final Method internalProgramAssembler;
>
>        public PageLoadServiceT5_1Impl(PageLoader tapestryPageLoader,
> TemplateParser templateParser) {
>                this.internalPageLoader =
>                        ReflectionToolkit.getDelegate(tapestryPageLoader);
>                this.internalInstantiatorSource =
>                        ReflectionToolkit.getField(internalPageLoader,
> "instantiatorSource");
>                this.internalResourcesSource =
>                        ReflectionToolkit.getField(internalPageLoader,
> "resourcesSource");
>                this.internalComponentClassResolver =
>                        ReflectionToolkit.getField(internalPageLoader,
> "componentClassResolver");
>                this.internalPersistentFieldManager =
>                        ReflectionToolkit.getField(internalPageLoader,
> "persistentFieldManager");
>                this.internalProgramAssembler =
>                        ReflectionToolkit.getMethod(internalPageLoader,
> "programAssembler");
>
>                this.internalTemplateParser = templateParser;
>        }
>
>        public Page loadPage(String defaultPageName, String pageName, Locale
> locale, Object coreComponent) {
>        Page page = new PageImpl(defaultPageName, locale,
> internalPersistentFieldManager);
>        ComponentAssembler assembler = createAssembler(pageName,
> coreComponent.getClass().getCanonicalName(), locale);
>        ComponentPageElement rootElement =
> assembler.assembleRootComponent(page);
>
>
>        Object newCoreComponent = ReflectionToolkit.getField(rootElement,
> "coreComponent");
>        ReflectionToolkit.copyAnnotatedFields(coreComponent,
> newCoreComponent, CopyField.class);
>
>        page.setRootElement(rootElement);
>        page.loaded();
>        return page;
>        }
>
>    private ComponentAssembler createAssembler(String pageName, String
> className, Locale locale)
>    {
>        Instantiator instantiator =
> internalInstantiatorSource.getInstantiator(className);
>
>        ComponentModel componentModel = instantiator.getModel();
>
>        Resource templateResource =
> componentModel.getBaseResource().forFile(pageName + ".tml");
>                ComponentTemplate template =
> internalTemplateParser.parseTemplate(templateResource);
>
>        ComponentPageElementResources resources =
> internalResourcesSource.get(locale);
>
>        ComponentAssembler assembler = new ComponentAssemblerImpl(
>        internalPageLoader,
>        internalInstantiatorSource,
>        internalComponentClassResolver,
>        instantiator,
>        resources,
>        locale);
>
>        try {
>                        internalProgramAssembler.invoke(internalPageLoader,
> assembler, template);
>                } catch (Exception e) {
>                        throw new RuntimeException(e);
>                }
>
>        return assembler;
>    }
>
> }
>
> ---
> Also this approach always creates new instance of Template.java and we
> loosing setted context. But it is not a big problem - just copy fields from
> one class to another.
>
> --
> View this message in context:
> http://tapestry.1045711.n5.nabble.com/Another-approach-to-dynamic-templating-T5-1-0-5-tp4868869p4868869.html
> Sent from the Tapestry - User mailing list archive at Nabble.com.
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
> For additional commands, e-mail: users-help@tapestry.apache.org
>
>