All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.microsphere.net.ExtendableProtocolURLStreamHandler Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 io.microsphere.net;

import io.microsphere.lang.Prioritized;

import java.io.IOException;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;

import static io.microsphere.collection.SetUtils.of;
import static io.microsphere.constants.SymbolConstants.COLON_CHAR;
import static io.microsphere.constants.SymbolConstants.DOT_CHAR;
import static io.microsphere.constants.SymbolConstants.QUERY_STRING;
import static io.microsphere.net.URLUtils.DEFAULT_HANDLER_PACKAGE_PREFIX;
import static io.microsphere.net.URLUtils.HANDLER_CONVENTION_CLASS_NAME;
import static io.microsphere.net.URLUtils.HANDLER_PACKAGES_PROPERTY_NAME;
import static io.microsphere.net.URLUtils.HANDLER_PACKAGES_SEPARATOR_CHAR;
import static io.microsphere.net.URLUtils.SUB_PROTOCOL_MATRIX_NAME;
import static io.microsphere.net.URLUtils.buildMatrixString;
import static io.microsphere.net.URLUtils.registerURLStreamHandler;
import static io.microsphere.util.StringUtils.isBlank;
import static io.microsphere.util.StringUtils.split;
import static java.net.Proxy.NO_PROXY;
import static java.util.Collections.sort;

/**
 * Extendable Protocol {@link URLStreamHandler} class supports the sub-protocols,
 * like "{protocol}:{sub-protocols[0]}: ... :{sub-protocols[n]}://...",
 * 
    *
  • {protocol} : The protocol of {@link URLStreamHandler URLStreamHandler} is recognized by {@link URL} (required)
  • *
  • {sub-protocols} : the list of sub-protocols that is {@link #resolveSubProtocols(URL) resolved} from {@link URL} (optional)
  • *
*

* The method {@link #initSubProtocolURLConnectionFactories(List)} that is overridden allows the sub-protocols to be extended, * the prerequisite is the method {@link #init() being invoked later. *

* If no {@link SubProtocolURLConnectionFactory} initialized or {@link URLConnection} open, * the {@link #openFallbackConnection(URL, Proxy) fallback strategy} will be applied. *

* If there is no requirement to support the sub-protocol, the subclass only needs to override {@link #openConnection(URL, Proxy)} method. *

* If an instance is instantiated by the default constructor, the implementation class must the obey conventions as follow: *

    *
  • The class must be the top level
  • *
  • The simple class name must be "Handler"
  • *
  • The class must not be present in the "default" or builtin package({@linkURLUtils #DEFAULT_HANDLER_PACKAGE_PREFIX "sun.net.www.protocol"})
  • *
*

* A new instance also can specify some protocol via {@link #ExtendableProtocolURLStreamHandler(String) the constructor with the protocol argument}. *

* Node: these methods are overridden making final: *

    *
  • {@link #openConnection(URL)}
  • *
  • {@link #parseURL(URL, String, int, int)}
  • *
  • {@link #equals(URL, URL)}
  • *
  • {@link #hostsEqual(URL, URL)}
  • *
  • {@link #hashCode(URL)}
  • *
  • {@link #toExternalForm(URL)}
  • *
* * @author Mercy * @see SubProtocolURLConnectionFactory * @see URLStreamHandler * @since 1.0.0 */ public abstract class ExtendableProtocolURLStreamHandler extends URLStreamHandler { private final String protocol; private final List factories = new ArrayList<>(); /** * The default constructor must obey the following conventions: *
    *
  • The class must be the top level
  • *
  • The simple class name must be "Handler"
  • *
  • The class must not be present in the "default" or builtin package({@link URLUtils#DEFAULT_HANDLER_PACKAGE_PREFIX "sun.net.www.protocol"})
  • *
*/ public ExtendableProtocolURLStreamHandler() { Class currentClass = getClass(); assertConventions(currentClass); String packageName = appendHandlerPackage(currentClass); this.protocol = resolveConventionProtocol(packageName); } public ExtendableProtocolURLStreamHandler(String protocol) { this.protocol = protocol; } public static Set getHandlePackages() { String value = getHandlePackagesPropertyValue(); String[] packages = split(value, HANDLER_PACKAGES_SEPARATOR_CHAR); return of(packages); } /** * Get the {@link System} property value of the packages of {@link URLStreamHandler URLStreamHandlers}. * * @return null if absent */ public static String getHandlePackagesPropertyValue() { return System.getProperty(HANDLER_PACKAGES_PROPERTY_NAME); } public void init() { initSubProtocolURLConnectionFactories(); // register self registerURLStreamHandler(this); } private void initSubProtocolURLConnectionFactories() { List factories = this.factories; initSubProtocolURLConnectionFactories(factories); sort(factories, Prioritized.COMPARATOR); } /** * Initialize {@link SubProtocolURLConnectionFactory SubProtocolURLConnectionFactories} * * @param factories the collection of {@link SubProtocolURLConnectionFactory SubProtocolURLConnectionFactories} */ protected void initSubProtocolURLConnectionFactories(List factories) { } @Override protected final URLConnection openConnection(URL u) throws IOException { return openConnection(u, NO_PROXY); } @Override protected URLConnection openConnection(URL u, Proxy p) throws IOException { List subProtocols = resolveSubProtocols(u); URLConnection urlConnection = null; int size = factories.size(); for (int i = 0; i < size; i++) { SubProtocolURLConnectionFactory factory = factories.get(i); if (factory.supports(u, subProtocols)) { urlConnection = factory.create(u, subProtocols, p); if (urlConnection != null) { break; } } } return urlConnection == null ? openFallbackConnection(u, p) : urlConnection; } /** * The subclass can override this method to open the fallback {@link URLConnection} if * any {@link SubProtocolURLConnectionFactory} does not create the {@link URLConnection}. * * @param url the URL that this connects to * @param proxy {@link Proxy} the proxy through which the connection will be made. If direct connection is desired, Proxy.NO_PROXY should be specified. * @return null as default * @throws IOException */ protected URLConnection openFallbackConnection(URL url, Proxy proxy) throws IOException { return null; } @Override protected final boolean equals(URL u1, URL u2) { return Objects.equals(toExternalForm(u1), toExternalForm(u2)); } @Override protected final int hashCode(URL u) { return toExternalForm(u).hashCode(); } @Override protected final boolean hostsEqual(URL u1, URL u2) { return Objects.equals(u1.getHost(), u2.getHost()); } /** * Reuses the algorithm of {@link URLStreamHandler#toExternalForm(URL)} using the {@link StringBuilder} to * the {@link StringBuilder}. * * @param u the URL. * @return a string representation of the URL argument. */ @Override protected final String toExternalForm(URL u) { return URLUtils.toExternalForm(u); } @Override protected final void parseURL(URL u, String spec, int start, int limit) { int end = spec.indexOf("://", start); if (end > start) { // The sub-protocol was found String actualSpec = reformSpec(u, spec, start, end, limit); super.parseURL(u, actualSpec, start, actualSpec.length()); } else { super.parseURL(u, spec, start, limit); } } /** * Reform the string of specified {@link URL} if its' scheme presents the sub-protocol, e,g. * A string representing the URL is "jdbc:mysql://localhost:3307/mydb?charset=UTF-8#top", its' *
    *
  • scheme : "jdbc:mysql"
  • *
  • host : "localhost"
  • *
  • port : 3307
  • *
  • path : "/mydb"
  • *
  • query : "charset=UTF-8"
  • *
  • ref : "top"
  • *
*

* This scheme contains two parts, the former is "jdbc" as the protocol, the later is "mysql" called sub-protocol * which is convenient to extend the fine-grain {@link URLStreamHandler}. * In this case, the reformed string of specified {@link URL} will be "jdbc://localhost:3307/mydb;_sp=mysql?charset=UTF-8#top". * * @param url the {@code URL} to receive the result of parsing * the spec. * @param spec the {@code String} representing the URL that * must be parsed. * @param start the character index at which to begin parsing. This is * just past the '{@code :}' (if there is one) that * specifies the determination of the protocol name. * @param end the index of the string "://" present in the URL from the * start index, its' value is greater or equal 0. * @param limit the character position to stop parsing at. This is the * end of the string or the position of the * "{@code #}" character, if present. All information * after the sharp sign indicates an anchor. * @return reformed the string of specified {@link URL} if the suffix o */ protected String reformSpec(URL url, String spec, int start, int end, int limit) { String protocol = url.getProtocol(); String subProtocol = spec.substring(start, end); String[] subProtocols = split(subProtocol, COLON_CHAR); String matrix = buildMatrixString(SUB_PROTOCOL_MATRIX_NAME, subProtocols); String suffix = spec.substring(end, limit); int capacity = protocol.length() + matrix.length() + suffix.length(); StringBuilder newSpecBuilder = new StringBuilder(capacity); newSpecBuilder.append(protocol).append(suffix); int insertIndex = newSpecBuilder.indexOf(QUERY_STRING, end); if (insertIndex > end) { newSpecBuilder.insert(insertIndex, matrix); } else { newSpecBuilder.append(matrix); } return newSpecBuilder.toString(); } /** * Get the sub-protocols from the specified {@link URL} * * @param url {@link URL} * @return non-null */ protected List resolveSubProtocols(URL url) { return URLUtils.resolveSubProtocols(url); } protected String resolveAuthority(URL url) { return URLUtils.resolveAuthority(url); } protected String resolvePath(URL url) { return URLUtils.resolvePath(url.getPath()); } public final String getProtocol() { return protocol; } @Override public String toString() { String sb = getClass().getName() + "{defaultPort=" + getDefaultPort() + ",protocol=" + getProtocol() + '}'; return sb; } private static String resolveConventionProtocol(String packageName) { int lastIndex = packageName.lastIndexOf(DOT_CHAR); return packageName.substring(lastIndex + 1); } private static void assertConventions(Class type) { assertClassTopLevel(type); assertClassName(type); assertPackage(type); } private static void assertClassTopLevel(Class type) { if (type.isLocalClass() || type.isAnonymousClass() || type.isMemberClass()) { throw new IllegalStateException("The implementation " + type + " must be the top level"); } } private static void assertClassName(Class type) { String simpleClassName = type.getSimpleName(); String className = HANDLER_CONVENTION_CLASS_NAME; if (!Objects.equals(className, simpleClassName)) { throw new IllegalStateException("The implementation class must name '" + className + "', actual : '" + simpleClassName + "'"); } } private static void assertPackage(Class type) { String className = type.getName(); if (className.indexOf(DOT_CHAR) < 0) { throw new IllegalStateException("The Handler class must not be present at the top package!"); } String packagePrefix = DEFAULT_HANDLER_PACKAGE_PREFIX; if (className.startsWith(packagePrefix)) { throw new IllegalStateException("The Handler class must not be present in the builtin package : '" + packagePrefix + "'"); } } private static String appendHandlerPackage(Class type) { String packageName = type.getPackage().getName(); appendHandlePackage(packageName); return packageName; } static void appendHandlePackage(String packageName) { String handlePackage = packageName.substring(0, packageName.lastIndexOf('.')); Set packages = getHandlePackages(); if (packages.contains(handlePackage)) { return; } String currentHandlerPackages = getHandlePackagesPropertyValue(); String handlePackages = null; if (isBlank(currentHandlerPackages)) { handlePackages = handlePackage; } else { handlePackages = currentHandlerPackages + HANDLER_PACKAGES_SEPARATOR_CHAR + handlePackage; } System.setProperty(HANDLER_PACKAGES_PROPERTY_NAME, handlePackages); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy