    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}