001// Licensed under the Apache License, Version 2.0 (the "License"); 002// you may not use this file except in compliance with the License. 003// You may obtain a copy of the License at 004// 005// http://www.apache.org/licenses/LICENSE-2.0 006// 007// Unless required by applicable law or agreed to in writing, software 008// distributed under the License is distributed on an "AS IS" BASIS, 009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 010// See the License for the specific language governing permissions and 011// limitations under the License. 012 013package org.apache.tapestry5.services.javascript; 014 015import org.apache.tapestry5.Asset; 016import org.apache.tapestry5.func.F; 017import org.apache.tapestry5.func.Flow; 018import org.apache.tapestry5.func.Mapper; 019import org.apache.tapestry5.func.Predicate; 020import org.apache.tapestry5.ioc.ServiceBinder; 021import org.apache.tapestry5.ioc.ServiceBindingOptions; 022import org.apache.tapestry5.ioc.annotations.UsesOrderedConfiguration; 023import org.apache.tapestry5.ioc.internal.util.InternalUtils; 024import org.apache.tapestry5.services.AssetSource; 025 026import java.util.List; 027 028/** 029 * An extensible implementation of {@link JavaScriptStack} that can be used as the implementation of a service. 030 * The contributions to the service are used to supply the libraries, stylesheets, and initialization for a 031 * JavaScriptStack, allowing the stack to be more dynamically configured. In practice, one will use 032 * {@link ServiceBinder#bind(Class, Class)} and {@link ServiceBindingOptions#withMarker(Class...)} to construct the 033 * service, then use the marker annotation to inject the service when contributing the service into to the 034 * {@link JavaScriptStackSource}. 035 * 036 * A limitation of this implementation is that the contributed assets are not localized at all. 037 * 038 * @see StackExtension 039 * @since 5.3 040 */ 041@UsesOrderedConfiguration(StackExtension.class) 042public class ExtensibleJavaScriptStack implements JavaScriptStack 043{ 044 private final AssetSource assetSource; 045 046 private final List<Asset> libraries; 047 048 private final List<StylesheetLink> stylesheets; 049 050 private final List<String> stacks; 051 052 private final List<String> modules; 053 054 private final String initialization; 055 056 private final JavaScriptAggregationStrategy strategy; 057 058 private final Predicate<StackExtension> by(final StackExtensionType type) 059 { 060 return new Predicate<StackExtension>() 061 { 062 public boolean accept(StackExtension element) 063 { 064 return element.type == type; 065 } 066 }; 067 } 068 069 private final Mapper<StackExtension, String> extractValue = new Mapper<StackExtension, String>() 070 { 071 public String map(StackExtension element) 072 { 073 return element.value; 074 } 075 076 ; 077 }; 078 079 private final Mapper<String, Asset> stringToAsset = new Mapper<String, Asset>() 080 { 081 public Asset map(String value) 082 { 083 return assetSource.getExpandedAsset(value); 084 } 085 086 ; 087 }; 088 089 private final Mapper<Asset, StylesheetLink> assetToStylesheetLink = new Mapper<Asset, StylesheetLink>() 090 { 091 public StylesheetLink map(Asset asset) 092 { 093 return new StylesheetLink(asset); 094 } 095 096 ; 097 }; 098 099 private final Mapper<String, JavaScriptAggregationStrategy> stringToStrategy = new Mapper<String, JavaScriptAggregationStrategy>() 100 { 101 @Override 102 public JavaScriptAggregationStrategy map(String name) 103 { 104 return JavaScriptAggregationStrategy.valueOf(name); 105 } 106 }; 107 108 public ExtensibleJavaScriptStack(AssetSource assetSource, List<StackExtension> configuration) 109 { 110 this.assetSource = assetSource; 111 112 Flow<StackExtension> extensions = F.flow(configuration); 113 114 libraries = extensions.filter(by(StackExtensionType.LIBRARY)).map(extractValue).map(stringToAsset).toList(); 115 116 stacks = extensions.filter(by(StackExtensionType.STACK)).map(extractValue).toList(); 117 118 modules = extensions.filter(by(StackExtensionType.MODULE)).map(extractValue).toList(); 119 120 stylesheets = extensions.filter(by(StackExtensionType.STYLESHEET)).map(extractValue).map(stringToAsset) 121 .map(assetToStylesheetLink).toList(); 122 123 List<String> initializations = extensions.filter(by(StackExtensionType.INITIALIZATION)).map(extractValue) 124 .toList(); 125 126 initialization = initializations.isEmpty() ? null : InternalUtils.join(initializations, "\n"); 127 128 strategy = toStrategy(extensions); 129 } 130 131 private JavaScriptAggregationStrategy toStrategy(Flow<StackExtension> extensions) 132 { 133 List<JavaScriptAggregationStrategy> values = extensions.filter(by(StackExtensionType.AGGREGATION_STRATEGY)).map(extractValue).map(stringToStrategy).toList(); 134 135 switch (values.size()) 136 { 137 case 0: 138 return JavaScriptAggregationStrategy.COMBINE_AND_MINIMIZE; 139 140 case 1: 141 142 return values.get(0); 143 144 default: 145 throw new IllegalStateException(String.format("Could not handle %d contribution(s) of JavaScriptAggregation Strategy. There should be at most one.", 146 values.size())); 147 } 148 } 149 150 public List<String> getStacks() 151 { 152 return stacks; 153 } 154 155 public List<Asset> getJavaScriptLibraries() 156 { 157 return libraries; 158 } 159 160 public List<StylesheetLink> getStylesheets() 161 { 162 return stylesheets; 163 } 164 165 public String getInitialization() 166 { 167 return initialization; 168 } 169 170 public List<String> getModules() 171 { 172 return modules; 173 } 174 175 @Override 176 public JavaScriptAggregationStrategy getJavaScriptAggregationStrategy() 177 { 178 return strategy; 179 } 180}