001    // Copyright 2011 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    // http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.tapestry5.services.javascript;
016    
017    import java.util.Collections;
018    import java.util.List;
019    
020    import org.apache.tapestry5.Asset;
021    import org.apache.tapestry5.func.F;
022    import org.apache.tapestry5.func.Flow;
023    import org.apache.tapestry5.func.Mapper;
024    import org.apache.tapestry5.func.Predicate;
025    import org.apache.tapestry5.ioc.ServiceBinder;
026    import org.apache.tapestry5.ioc.ServiceBindingOptions;
027    import org.apache.tapestry5.ioc.annotations.UsesOrderedConfiguration;
028    import org.apache.tapestry5.ioc.internal.util.InternalUtils;
029    import org.apache.tapestry5.services.AssetSource;
030    
031    /**
032     * An extensible implementation of {@link JavaScriptStack} that can be used as the implementation of a service.
033     * The contributions to the service are used to supply the libraries, stylesheets, and initialization for a
034     * JavaScriptStack, allowing the stack to be more dynamically configured. In practice, one will use
035     * {@link ServiceBinder#bind(Class, Class)} and {@link ServiceBindingOptions#withMarker(Class...)} to construct the
036     * service, then use the marker annotation to inject the service when contributing the service into to the
037     * {@link JavaScriptStackSource}.
038     * <p>
039     * A limitation of this implementation is that the contributed assets are not localized at all.
040     * 
041     * @since 5.3
042     * @see StackExtension
043     */
044    @UsesOrderedConfiguration(StackExtension.class)
045    public class ExtensibleJavaScriptStack implements JavaScriptStack
046    {
047        private final AssetSource assetSource;
048    
049        private final List<Asset> libraries;
050    
051        private final List<StylesheetLink> stylesheets;
052    
053        private final String initialization;
054    
055        private final Predicate<StackExtension> by(final StackExtensionType type)
056        {
057            return new Predicate<StackExtension>()
058            {
059                public boolean accept(StackExtension element)
060                {
061                    return element.type == type;
062                }
063            };
064        }
065    
066        private final Mapper<StackExtension, String> extractValue = new Mapper<StackExtension, String>()
067        {
068            public String map(StackExtension element)
069            {
070                return element.value;
071            };
072        };
073    
074        private final Mapper<String, Asset> stringToAsset = new Mapper<String, Asset>()
075        {
076            public Asset map(String value)
077            {
078                return assetSource.getExpandedAsset(value);
079            };
080        };
081    
082        private final Mapper<Asset, StylesheetLink> assetToStylesheetLink = new Mapper<Asset, StylesheetLink>()
083        {
084            public StylesheetLink map(Asset asset)
085            {
086                return new StylesheetLink(asset);
087            };
088        };
089    
090        public ExtensibleJavaScriptStack(AssetSource assetSource, List<StackExtension> configuration)
091        {
092            this.assetSource = assetSource;
093    
094            Flow<StackExtension> extensions = F.flow(configuration);
095    
096            libraries = extensions.filter(by(StackExtensionType.LIBRARY)).map(extractValue).map(stringToAsset).toList();
097    
098            stylesheets = extensions.filter(by(StackExtensionType.STYLESHEET)).map(extractValue).map(stringToAsset)
099                    .map(assetToStylesheetLink).toList();
100    
101            List<String> initializations = extensions.filter(by(StackExtensionType.INITIALIZATION)).map(extractValue)
102                    .toList();
103    
104            initialization = initializations.isEmpty() ? null : InternalUtils.join(initializations, "\n");
105        }
106    
107        public List<String> getStacks()
108        {
109            return Collections.emptyList();
110        }
111    
112        public List<Asset> getJavaScriptLibraries()
113        {
114            return libraries;
115        }
116    
117        public List<StylesheetLink> getStylesheets()
118        {
119            return stylesheets;
120        }
121    
122        public String getInitialization()
123        {
124            return initialization;
125        }
126    
127    }