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                changeTracker.forceInvalidationEvent();
058            }
059        });
060    }
061
062    @Override
063    public String toString()
064    {
065        return String.format("MessageCatalogResource[%s]", locale);
066    }
067
068    /**
069     * StreamableResourceSourceImpl needs a file, and a suffix, to determine the content type.
070     */
071    @Override
072    public String getFile()
073    {
074        return String.format("virtual-%s.js", locale);
075    }
076
077    /**
078     * To work as a streamable resource, must return a value (or null) from toURL.
079     */
080    @Override
081    public URL toURL()
082    {
083        return null;
084    }
085
086    public InputStream openStream() throws IOException
087    {
088        return toInputStream(getBytes());
089    }
090
091    private byte[] getBytes() throws IOException
092    {
093        if (bytes == null)
094        {
095            bytes = assembleCatalog().getBytes(UTF8);
096        }
097
098        return bytes;
099    }
100
101    private String assembleCatalog()
102    {
103        Messages messages = messagesSource.getApplicationCatalog(locale);
104
105        JSONObject catalog = new JSONObject();
106
107        for (String key : messages.getKeys())
108        {
109            if (key.startsWith("private-"))
110            {
111                continue;
112            }
113
114            String value = messages.get(key);
115
116            if (value.contains("%"))
117            {
118                continue;
119            }
120
121            catalog.put(key, value);
122        }
123
124        StringBuilder builder = new StringBuilder(2000);
125
126        builder.append("define(").append(catalog.toString(compactJSON)).append(");");
127
128        return builder.toString();
129    }
130}