com.google.speedtracer.client.SymbolServerController.java Source code

Java tutorial

Introduction

Here is the source code for com.google.speedtracer.client.SymbolServerController.java

Source

/*
 * Copyright 2010 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.speedtracer.client;

import com.google.gwt.coreext.client.IterableFastStringMap;
import com.google.gwt.coreext.client.JSON;
import com.google.gwt.coreext.client.JSON.JSONParseException;
import com.google.gwt.xhr.client.XMLHttpRequest;
import com.google.speedtracer.client.SourceViewer.SourcePresenter;
import com.google.speedtracer.client.SymbolServerManifest.ResourceSymbolInfo;
import com.google.speedtracer.client.model.JsSymbol;
import com.google.speedtracer.client.model.JsSymbolMap;
import com.google.speedtracer.client.util.Url;
import com.google.speedtracer.client.util.Xhr;
import com.google.speedtracer.client.util.Xhr.XhrCallback;

import java.util.ArrayList;
import java.util.List;

/**
 * Class resolving the symbol map file and original source files for
 * re-symbolization.
 */
public class SymbolServerController {
    /**
     * Listener interface for getting notified when a symbols or source are
     * available.
     */
    public interface Callback {
        /**
         * Called if the request for the symbols failed.
         */
        void onSymbolsFetchFailed(int errorReason);

        /**
         * Called when the symbols for a resource have been loaded.
         */
        void onSymbolsReady(JsSymbolMap symbols);
    }

    /**
     * Callback interface for renderers that wish to consume a resymbolized
     * symbol.
     */
    public interface Resymbolizeable {
        void reSymbolize(String sourceServer, SourceViewerServer sourceViewerServer, JsSymbol sourceSymbol,
                SourcePresenter sourcePresenter);
    }

    /**
     * A request that has been queued for servicing.
     */
    private class PendingRequest {
        final Callback callback;
        final String resourceUrl;

        PendingRequest(String resourceUrl, Callback callback) {
            this.resourceUrl = resourceUrl;
            this.callback = callback;
        }
    }

    public static final int ERROR_MANIFEST_NOT_LOADED = 0;

    public static final int ERROR_SYMBOL_FETCH_FAIL = 1;

    private static IterableFastStringMap<JsSymbolMap> resourceSymbols = new IterableFastStringMap<JsSymbolMap>();

    /**
     * Retrieve a Symbol Map for a particular resource URL.
     * 
     * @param resource The resource we want the symbol mapping for.
     * @return The symbol map.
     */
    public static JsSymbolMap get(String resource) {
        return resourceSymbols.get(resource);
    }

    /**
     * Inserts a symbol map into the set of available symbol maps, keyed by
     * resource URL.
     * 
     * @param resource The resource we want the symbol mapping for.
     */
    static void put(String resource, JsSymbolMap symbols) {
        resourceSymbols.put(resource, symbols);
    }

    private final Url mainResourceUrl;

    private boolean manifestLoaded = false;

    private final List<PendingRequest> pendingRequests;

    private final IterableFastStringMap<List<XhrCallback>> queuedSymbolMapRequests = new IterableFastStringMap<List<XhrCallback>>();

    private final Url symbolManifestUrl;

    private SymbolServerManifest symbolServerManifest;

    SymbolServerController(Url mainResourceUrl, Url symbolManifestUrl) {
        this.mainResourceUrl = mainResourceUrl;
        this.symbolManifestUrl = symbolManifestUrl;
        this.pendingRequests = new ArrayList<PendingRequest>();
        // Start xhr for fetching our associated symbol manifest.
        init();
    }

    /**
     * Attempts to resymbolize a given symbol name within a given resource.
     * 
     * @param resourceUrl the url of the resource that contains the symbol we want
     *          to resymblize
     * @param symbolName the symbol we want to resymbolize
     * @param renderer the recipient of the resymbolization
     * @param sourcePresenter the entity responsible for displaying the source of
     *          the resymbolized symbol
     */
    public void attemptResymbolization(final String resourceUrl, final String symbolName,
            final Resymbolizeable renderer, final SourcePresenter sourcePresenter) {
        requestSymbolsFor(resourceUrl, new Callback() {
            public void onSymbolsFetchFailed(int errorReason) {
                // TODO (jaimeyap): Do something here... or not.
            }

            public void onSymbolsReady(final JsSymbolMap symbols) {
                // Extract the source symbol.
                final JsSymbol sourceSymbol = symbols.lookup(symbolName);

                if (sourceSymbol == null) {
                    return;
                }
                // Enhance the rendered frame with the resymbolization.
                renderer.reSymbolize(symbols.getSourceServer(), symbols.getSourceViewerServer(), sourceSymbol,
                        sourcePresenter);
            }
        });
    }

    /**
     * Cancels all pending requests.
     */
    public void cancelPendingRequests() {
        pendingRequests.clear();
    }

    /**
     * Looks up the {@link JsSymbolMap} associated with a particular resource url
     * and calls the specified callback when it has been fetched.
     * 
     * If the symbol manifest is not loaded, this method will queue the request to
     * be serviced as soon as the manifest loads.
     * 
     * This call may or may not call back synchronously. Do not bank on
     * asynchronous behavior.
     * 
     * @param resourceUrl the resource that we intend to get the symbols for.
     * @param callback the {@link Callback} that gets invoked when the symbols are
     *          loaded.
     */
    public void requestSymbolsFor(final String resourceUrl, final Callback callback) {
        PendingRequest request = new PendingRequest(resourceUrl, callback);
        if (!manifestLoaded) {
            // Queue a pending request.
            pendingRequests.add(request);
            return;
        }

        serviceRequest(request);
    }

    private void init() {
        Xhr.get(symbolManifestUrl.getUrl(), new XhrCallback() {

            public void onFail(XMLHttpRequest xhr) {
                // Let pending requests know that the manifest failed to load.
                for (PendingRequest request : pendingRequests) {
                    request.callback.onSymbolsFetchFailed(ERROR_MANIFEST_NOT_LOADED);
                }
                cancelPendingRequests();
                SymbolServerService.unregisterSymbolServerController(mainResourceUrl);
                if (ClientConfig.isDebugMode()) {
                    Logging.getLogger().logText("Fetching manifest " + symbolManifestUrl.getUrl() + " failed.");
                }
            }

            public void onSuccess(XMLHttpRequest xhr) {
                // TODO (jaimeyap): This needs to be validated... and we should handle
                // any parsing errors as well.
                try {
                    SymbolServerController.this.symbolServerManifest = JSON.parse(xhr.getResponseText()).cast();
                } catch (JSONParseException jpe) {
                    if (ClientConfig.isDebugMode()) {
                        Logging.getLogger().logText("Unable to parse manifest for " + symbolManifestUrl.getUrl());
                    }
                    onFail(xhr); // Treat an invalid manifest like a network failure.
                    return;
                }

                SymbolServerController.this.manifestLoaded = true;
                // Now service all the pending requests.
                while (!pendingRequests.isEmpty()) {
                    serviceRequest(pendingRequests.remove(0));
                }
                if (ClientConfig.isDebugMode()) {
                    Logging.getLogger().logText("Manifest " + symbolManifestUrl.getUrl() + " loaded.");
                }
            }
        });
    }

    private ResourceSymbolInfo lookupEntryInManifest(String resourceUrl) {
        ResourceSymbolInfo resourceSymbolInfo = null;

        // If the resourceUrl begins with a '/' then we assume it is relative to the
        // origin.
        String relativeUrl = "";
        if (resourceUrl.charAt(0) == '/') {
            relativeUrl = Url.convertToRelativeUrl(mainResourceUrl.getOrigin(), resourceUrl);
            resourceSymbolInfo = symbolServerManifest.getResourceSymbolInfo(relativeUrl);

        } else {
            // First try looking for the resource using the full URL.
            resourceSymbolInfo = symbolServerManifest.getResourceSymbolInfo(resourceUrl);
            // If the lookup was null, then attempt a relative url lookup.
            if (resourceSymbolInfo == null) {
                relativeUrl = Url.convertToRelativeUrl(mainResourceUrl.getResourceBase(), resourceUrl);
                resourceSymbolInfo = symbolServerManifest.getResourceSymbolInfo(relativeUrl);
            }
        }
        if (ClientConfig.isDebugMode() && resourceSymbolInfo == null) {
            Logging.getLogger().logText("Failed to find url " + resourceUrl + " relative to " + relativeUrl
                    + " base: " + mainResourceUrl.getResourceBase());
        }
        return resourceSymbolInfo;
    }

    private void serviceRequest(PendingRequest request) {
        assert manifestLoaded : "Manifest should be loaded";

        final ResourceSymbolInfo resourceSymbolInfo = lookupEntryInManifest(request.resourceUrl);
        final Callback callback = request.callback;
        if (resourceSymbolInfo == null) {
            if (ClientConfig.isDebugMode()) {
                Logging.getLogger().logText("Resymbolization failed: No symbol info for " + request.resourceUrl);
            }
            callback.onSymbolsFetchFailed(ERROR_SYMBOL_FETCH_FAIL);
            return;
        }

        final String symbolMapUrl = resourceSymbolInfo.getSymbolMapUrl();
        JsSymbolMap symbolMap = get(symbolMapUrl);
        // We only want to request and parse for symbolMaps we havn't already
        // parsed.
        if (symbolMap == null) {
            // Create the XhrCallback to service this request.
            XhrCallback xhrCallback = new XhrCallback() {

                public void onFail(XMLHttpRequest xhr) {
                    callback.onSymbolsFetchFailed(ERROR_SYMBOL_FETCH_FAIL);
                    dequeuePendingXhrs(symbolMapUrl, xhr, false);
                    if (ClientConfig.isDebugMode()) {
                        Logging.getLogger().logText("Fetching symbol map: " + symbolMapUrl + " failed.");
                    }
                }

                public void onSuccess(XMLHttpRequest xhr) {
                    // Double check that another XHR didnt pull it down and parse it.
                    JsSymbolMap fetchedSymbolMap = get(symbolMapUrl);
                    if (fetchedSymbolMap == null) {
                        fetchedSymbolMap = JsSymbolMap.parse(resourceSymbolInfo.getSourceServer(),
                                resourceSymbolInfo.getSourceViewerServer(), resourceSymbolInfo.getType(),
                                xhr.getResponseText());
                        put(symbolMapUrl, fetchedSymbolMap);
                    }
                    callback.onSymbolsReady(fetchedSymbolMap);
                    dequeuePendingXhrs(symbolMapUrl, xhr, true);
                    if (ClientConfig.isDebugMode()) {
                        Logging.getLogger().logText("Fetched symbol map: " + symbolMapUrl);
                    }
                }

                private void dequeuePendingXhrs(String symbolMapUrl, XMLHttpRequest xhr, boolean success) {
                    List<XhrCallback> callbacks = queuedSymbolMapRequests.get(symbolMapUrl);
                    if (callbacks != null) {
                        while (!callbacks.isEmpty()) {
                            XhrCallback callback = callbacks.remove(0);
                            if (success) {
                                callback.onSuccess(xhr);
                            } else {
                                callback.onFail(xhr);
                            }
                        }
                    }
                }
            };

            // Check to see if we have a request in flight.
            List<XhrCallback> requestCallbacks = queuedSymbolMapRequests.get(symbolMapUrl);
            if (requestCallbacks == null) {
                // Make an entry indicating a request is in flight.
                queuedSymbolMapRequests.put(symbolMapUrl, new ArrayList<XhrCallback>());
                if (ClientConfig.isDebugMode()) {
                    Logging.getLogger().logText(
                            "Fetching symbol map URL: " + symbolManifestUrl.getResourceBase() + symbolMapUrl);
                }
                Xhr.get(symbolManifestUrl.getResourceBase() + symbolMapUrl, xhrCallback);
            } else {
                // There are pending XHRs out. Which means that we should just queue
                // this request.
                requestCallbacks.add(xhrCallback);
            }
        } else {
            // We have already fetched this and parsed it before. Send it to the
            // callback.
            callback.onSymbolsReady(symbolMap);
        }
    }
}