001// Copyright 2012 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 015package org.apache.tapestry5.internal.util; 016 017import org.apache.tapestry5.commons.Messages; 018import org.apache.tapestry5.internal.services.assets.ResourceChangeTracker; 019import org.apache.tapestry5.json.JSONObject; 020import org.apache.tapestry5.services.messages.ComponentMessagesSource; 021 022import java.io.IOException; 023import java.io.InputStream; 024import java.net.URL; 025import java.util.Locale; 026 027public class MessageCatalogResource extends VirtualResource 028{ 029 private final Locale locale; 030 031 private final ComponentMessagesSource messagesSource; 032 033 private final boolean compactJSON; 034 035 private volatile byte[] bytes; 036 037 public MessageCatalogResource(Locale locale, ComponentMessagesSource messagesSource, final ResourceChangeTracker changeTracker, boolean compactJSON) 038 { 039 this.locale = locale; 040 this.messagesSource = messagesSource; 041 this.compactJSON = compactJSON; 042 043 messagesSource.getInvalidationEventHub().addInvalidationCallback(new Runnable() 044 { 045 public void run() 046 { 047 bytes = null; 048 049 // What's all this then? This MessageCatalogResource is converted, as if it was a real file, into 050 // a StreamableResource by the StreamableResourceService service; as a virtual resource, we don't have 051 // any date-time-modified information for the application message catalog (as if that was possible, 052 // given that its composed of multiple files). The ComponentMessagesSource can tell us when 053 // *some* properties file has changed (not necessarily one used in the application message catalog, 054 // but that's the breaks). When that occurs, we tell the ResourceChangeTracker to fire its invalidation 055 // event. That flushes out all the assets it has cached, including StreamableResources for JavaScript files, 056 // including the one created here to represent the application message catalog. 057 058 // TAP-2742: now we're doing smarter page cache invalidation, 059 // so we don't need to clear everything anymore. 060 061 // changeTracker.forceInvalidationEvent(); 062 } 063 }); 064 } 065 066 @Override 067 public String toString() 068 { 069 return String.format("MessageCatalogResource[%s]", locale); 070 } 071 072 /** 073 * StreamableResourceSourceImpl needs a file, and a suffix, to determine the content type. 074 */ 075 @Override 076 public String getFile() 077 { 078 return String.format("virtual-%s.js", locale); 079 } 080 081 /** 082 * To work as a streamable resource, must return a value (or null) from toURL. 083 */ 084 @Override 085 public URL toURL() 086 { 087 return null; 088 } 089 090 public InputStream openStream() throws IOException 091 { 092 return toInputStream(getBytes()); 093 } 094 095 private byte[] getBytes() throws IOException 096 { 097 if (bytes == null) 098 { 099 bytes = assembleCatalog().getBytes(UTF8); 100 } 101 102 return bytes; 103 } 104 105 private String assembleCatalog() 106 { 107 Messages messages = messagesSource.getApplicationCatalog(locale); 108 109 JSONObject catalog = new JSONObject(); 110 111 for (String key : messages.getKeys()) 112 { 113 if (key.startsWith("private-")) 114 { 115 continue; 116 } 117 118 String value = messages.get(key); 119 120 if (value.contains("%")) 121 { 122 continue; 123 } 124 125 catalog.put(key, value); 126 } 127 128 StringBuilder builder = new StringBuilder(2000); 129 130 builder.append("define(").append(catalog.toString(compactJSON)).append(");"); 131 132 return builder.toString(); 133 } 134}