@SetupRender
  final void setup() {
    // Often, these controlName and clientId will end up as the same value. There are many
    // exceptions, including a form that renders inside a loop, or a form inside a component
    // that is used multiple times.

    if (formSupport == null)
      throw new RuntimeException(
          String.format(
              "Component %s must be enclosed by a Form component.", resources.getCompleteId()));

    assignedClientId = allocateClientId();

    String controlName = formSupport.allocateControlName(resources.getId());

    formSupport.storeAndExecute(this, new Setup(controlName));
    formSupport.store(this, PROCESS_SUBMISSION_ACTION);
  }
  public Asset getComponentAsset(
      final ComponentResources resources, final String path, final String libraryName) {
    assert resources != null;

    assert InternalUtils.isNonBlank(path);

    return tracker.invoke(
        String.format("Resolving '%s' for component %s", path, resources.getCompleteId()),
        new Invokable<Asset>() {
          public Asset invoke() {
            // First, expand symbols:

            String expanded = symbolSource.expandSymbols(path);

            int dotx = expanded.indexOf(':');

            // We special case the hell out of 'classpath:' so that we can provide warnings today
            // (5.4) and
            // blow up in a useful fashion tomorrow (5.5).

            if (expanded.startsWith("//")
                || (dotx > 0
                    && !expanded.substring(0, dotx).equalsIgnoreCase(AssetConstants.CLASSPATH))) {
              final String prefix =
                  dotx >= 0 ? expanded.substring(0, dotx) : AssetConstants.PROTOCOL_RELATIVE;
              if (EXTERNAL_URL_PREFIXES.contains(prefix)) {

                String url;
                if (prefix.equals(AssetConstants.PROTOCOL_RELATIVE)) {
                  url = (request != null && request.isSecure() ? "https:" : "http:") + expanded;
                  url = url.replace("//:", "//");
                } else {
                  url = expanded;
                }

                try {
                  UrlResource resource = new UrlResource(new URL(url));
                  return new UrlAsset(url, resource);
                } catch (MalformedURLException e) {
                  throw new RuntimeException(e);
                }
              } else {
                return getAssetInLocale(
                    resources.getBaseResource(), expanded, resources.getLocale());
              }
            }

            // No prefix, so implicitly classpath:, or explicitly classpath:

            String restOfPath = expanded.substring(dotx + 1);

            // This is tricky, because a relative path (including "../") is ok in 5.3, since its
            // just somewhere
            // else on the classpath (though you can "stray" out of the "safe" zone).  In 5.4, under
            // /META-INF/assets/
            // it's possible to "stray" out beyond the safe zone more easily, into parts of the
            // classpath that can't be
            // represented in the URL.

            // Ends with trailing slash:
            String metaRoot = "META-INF/assets/" + toPathPrefix(libraryName);

            String trimmedRestOfPath =
                restOfPath.startsWith("/") ? restOfPath.substring(1) : restOfPath;

            // TAP5-2044: Some components specify a full path, starting with META-INF/assets/, and
            // we should just trust them.
            // The warning logic below is for compnents that specify a relative path. Our bad
            // decisions come back to haunt us;
            // Resource paths should always had a leading slash to differentiate relative from
            // complete.
            String metaPath =
                trimmedRestOfPath.startsWith("META-INF/assets/")
                    ? trimmedRestOfPath
                    : metaRoot + trimmedRestOfPath;

            // Based on the path, metaResource is where it should exist in a 5.4 and beyond world
            // ... unless the expanded
            // path was a bit too full of ../ sequences, in which case the expanded path is not
            // valid and we adjust the
            // error we write.

            Resource metaResource = findLocalizedResource(null, metaPath, resources.getLocale());

            Asset result = getComponentAsset(resources, expanded, metaResource);

            if (result == null) {
              throw new RuntimeException(
                  String.format(
                      "Unable to locate asset '%s' for component %s. It should be located at %s.",
                      path, resources.getCompleteId(), metaPath));
            }

            // This is the best way to tell if the result is an asset for a Classpath resource.

            Resource resultResource = result.getResource();

            if (!resultResource.equals(metaResource)) {
              if (firstWarning.getAndSet(false)) {
                logger.error(
                    "Packaging of classpath assets has changed in release 5.4; "
                        + "Assets should no longer be on the main classpath, "
                        + "but should be moved to 'META-INF/assets/' or a sub-folder. Future releases of Tapestry may "
                        + "no longer support assets on the main classpath.");
              }

              if (metaResource.getFolder().startsWith(metaRoot)) {
                logger.warn(
                    String.format(
                        "Classpath asset '/%s' should be moved to folder '/%s/'.",
                        resultResource.getPath(), metaResource.getFolder()));
              } else {
                logger.warn(
                    String.format(
                        "Classpath asset '/%s' should be moved under folder '/%s', and the relative path adjusted.",
                        resultResource.getPath(), metaRoot));
              }
            }

            return result;
          }
        });
  }