    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}