/* * Copyright (c) 2002-2012 Alibaba Group Holding Limited. * All rights reserved. * * 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.alibaba.citrus.service.uribroker.uri; import static com.alibaba.citrus.util.ArrayUtil.*; import static com.alibaba.citrus.util.Assert.*; import static com.alibaba.citrus.util.BasicConstant.*; import static com.alibaba.citrus.util.CollectionUtil.*; import static com.alibaba.citrus.util.ObjectUtil.*; import static com.alibaba.citrus.util.StringUtil.*; import java.lang.reflect.Array; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.http.HttpServletRequest; import com.alibaba.citrus.service.requestcontext.util.QueryStringParser; import com.alibaba.citrus.service.uribroker.interceptor.URIBrokerInterceptor; import com.alibaba.citrus.service.uribroker.interceptor.URIBrokerPathInterceptor; import com.alibaba.citrus.util.StringUtil; /** * 代表一个基本的URI风格。 * <p> * 一个URI包括如下几个部分: * </p> * <p/> * <pre> * URI = SERVER_INFO + PATH + "?" + QUERY_DATA + "#" + REFERENCE * SERVER_INFO = scheme://loginUser:loginPassword@serverName:serverPort * PATH = /path/path * QUERY_DATA = queryKey1=value1&queryKey2=value2 * REFERENCE = reference * </pre> * <p> * 例如: * </p> * <p/> * <pre> * http://user:pass@myserver.com:8080/view?id=1#top * </pre> * <p> * 注意,<code>URIBroker</code>没有提供修改path的方法。如果要添加、删除、修改path,请直接使用子类 * <code>GenericURIBroker</code>。 * </p> * * @author Michael Zhou */ public abstract class URIBroker extends URIBrokerFeatures { /** 代表URI的类型。 */ public static enum URIType { /** 自动选择URI的类型。 */ auto, /** 完整URI,包括serverInfo, path。 */ full, /** 绝对URI,不包括serverInfo,但包括完整的path。 */ absolute, /** 相对URI,不包括serverInfo,其path为相对于当前请求的URI的相对路径。 */ relative } public static final String SERVER_SCHEME_HTTP = "http"; public static final String SERVER_SCHEME_HTTPS = "https"; public static final Integer SERVER_PORT_HTTP = 80; public static final Integer SERVER_PORT_HTTPS = 443; protected static final int PATH_INDEX = 0; private static final Pattern pathPattern = Pattern.compile("(^/*|/+)(^[^/]+|[^/]*)"); private final int[] pathSegmentIndexes = new int[getPathSegmentCount()]; private URIType type; private URI baseURI; private String serverScheme; private String serverName; private int serverPort = -1; private String loginUser; private String loginPassword; private String reference; private final List<String> path = createLinkedList(); private final Map<String, Object> query = createLinkedHashMap(); /** 取得URI类型。 */ public URIType getURIType() { return type; } /** 设置URI类型。 */ public URIBroker setURIType(URIType type) { this.type = type; return this; } /** 设置成自动URI类型。 */ public URIBroker autoURI() { return setURIType(URIType.auto); } /** 设置成完整URI类型。 */ public URIBroker fullURI() { return setURIType(URIType.full); } /** 设置成绝对URI类型。 */ public URIBroker absoluteURI() { return setURIType(URIType.absolute); } /** 设置成相对URI类型。 */ public URIBroker relativeURI() { return setURIType(URIType.relative); } /** 取得baseURI,当<code>URIType==absolute/relative</code>时,用来生成以此为基准的URI。 */ public String getBaseURI() { return baseURI == null ? null : baseURI.toString(); } /** 设置baseURI,当<code>URIType==absolute/relative</code>时,用来生成以此为基准的URI。 */ public URIBroker setBaseURI(String baseURI) { baseURI = trimToNull(baseURI); this.baseURI = baseURI == null ? null : URI.create(baseURI).normalize(); return this; } /** 取得不包含query和reference的serverURI。 */ public String getServerURI() { processInterceptors(); StringBuilder buf = new StringBuilder(); render(buf, true); return buf.toString(); } /** 设置现成的uri。 */ public final URIBroker setServerURI(String uriString) { URL uri; try { uri = new URL(assertNotNull(trimToNull(uriString), "serverURI")); } catch (MalformedURLException e) { throw new IllegalArgumentException(e.getMessage()); } String serverScheme = uri.getProtocol(); String[] userInfo = StringUtil.split(uri.getUserInfo(), ":"); String serverName = uri.getHost(); int serverPort = uri.getPort(); if (serverScheme != null) { setServerScheme(serverScheme); } if (!isEmptyArray(userInfo)) { if (userInfo.length > 0) { setLoginUser(userInfo[0]); } if (userInfo.length > 1) { setLoginPassword(userInfo[1]); } } if (serverName != null) { setServerName(serverName); } if (serverPort > 0) { setServerPort(serverPort); } setServerURI(uri); new URIBrokerQueryStringParser().parse(uri.getQuery()); setReference(uri.getRef()); return this; } protected void setServerURI(URL uri) { } /** 取得URI scheme。 */ public String getServerScheme() { return serverScheme; } /** 设置URI scheme,只能是http和https。 */ public URIBroker setServerScheme(String serverScheme) { this.serverScheme = trim(serverScheme); renderer.clearServerBuffer(); return this; } /** 取得服务器名。 */ public String getServerName() { return serverName; } /** 设置服务器名。 */ public URIBroker setServerName(String serverName) { this.serverName = trim(serverName); renderer.clearServerBuffer(); return this; } /** 取得服务器端口。 */ public int getServerPort() { return serverPort; } /** 设置服务器端口。注意默认的80端口和443端口分别在http和https时不做输出。 */ public URIBroker setServerPort(int serverPort) { this.serverPort = serverPort <= 0 ? -1 : serverPort; renderer.clearServerBuffer(); return this; } /** 取得登录用户。 */ public String getLoginUser() { return loginUser; } /** 设置登录用户。 */ public URIBroker setLoginUser(String loginUser) { this.loginUser = trim(loginUser); renderer.clearServerBuffer(); return this; } /** 取得登录密码。 */ public String getLoginPassword() { return loginPassword; } /** 设置登录密码。 */ public URIBroker setLoginPassword(String loginPassword) { this.loginPassword = trim(loginPassword); renderer.clearServerBuffer(); return this; } /** 取得reference引用。 */ public String getReference() { return reference; } /** 设置reference引用。 */ public URIBroker setReference(String reference) { this.reference = trim(reference); return this; } /** 取得URL的path部分。 */ public String getPath() { return getAllPathSegmentsAsString(PATH_INDEX); } /** 取得URI path列表。 */ public List<String> getPathElements() { return getAllPathSegments(PATH_INDEX); } /** 取得当前URI path分成几段。 */ protected abstract int getPathSegmentCount(); protected final List<String> getAllPathSegments(int segment) { return subPath(segment, true); } protected final List<String> getPathSegment(int segment) { return subPath(segment, false); } private List<String> subPath(int segment, boolean multi) { assertSegment(segment); int size = path.size(); int endIndex = multi ? size : pathSegmentIndexes[segment]; int beginIndex = segment > 0 ? pathSegmentIndexes[segment - 1] : 0; if (beginIndex == 0 && endIndex == size) { return path; } else { return path.subList(beginIndex, endIndex); } } protected final String getAllPathSegmentsAsString(int segment) { return getSubPathAsString(segment, true); } protected final String getPathSegmentAsString(int segment) { return getSubPathAsString(segment, false); } private String getSubPathAsString(int segment, boolean multi) { assertSegment(segment); StringBuilder buf = new StringBuilder(); int endIndex = multi ? path.size() : pathSegmentIndexes[segment]; int beginIndex = segment > 0 ? pathSegmentIndexes[segment - 1] : 0; for (int i = beginIndex; i < endIndex; i++) { buf.append("/").append(path.get(i)); } return buf.toString(); } /** * 合并多个list,并加入到某个segment中。 * <ul> * <li>以复制的方式合并,支持如下操作:path = parent.path + path。</li> * <li>合并时不再解析每一项path,假设每一项path在合并前已经是合法的格式,即不包含"/"。</li> * </ul> */ @SuppressWarnings("unchecked") protected final void setPathSegment(int segment, List<?>... lists) { List<String> mergedList = createLinkedList(); if (lists != null) { for (List<?> list : lists) { mergedList.addAll((List<String>) list); } } clearPathSegment(segment); if (!mergedList.isEmpty()) { for (String element : mergedList) { addPathSegment(segment, element, false); } } } protected final void setPathSegment(int segment, String path) { clearPathSegment(segment); addPathSegment(segment, path); } protected final void addPathSegment(int segment, String path) { addPathSegment(segment, path, true); } private void addPathSegment(int segment, String path, boolean split) { assertSegment(segment); if (path != null) { int index = pathSegmentIndexes[segment]; boolean isAppend = index == pathSegmentIndexes[pathSegmentIndexes.length - 1]; if (split) { for (String element : StringUtil.split(path, " /\\")) { index = addPathElement(element, index, isAppend); } } else { index = addPathElement(path, index, isAppend); } int newAdded = index - pathSegmentIndexes[segment]; // 如果什么也没有被加入,则加入空字符串 if (newAdded > 0) { for (int i = segment; i < pathSegmentIndexes.length; i++) { pathSegmentIndexes[i] += newAdded; } } } } private int addPathElement(String element, int elementIndex, boolean isAppend) { if (!isEmpty(element)) { path.add(elementIndex, element); if (isAppend) { renderer.updatePathBuffer(element); } else { renderer.clearPathBuffer(); } return elementIndex + 1; } return elementIndex; } protected final void clearPathSegment(int segment) { assertSegment(segment); int index = pathSegmentIndexes[segment]; int previousIndex = segment > 0 ? pathSegmentIndexes[segment - 1] : 0; int removedCount = index - previousIndex; boolean isTruncate = index == pathSegmentIndexes[pathSegmentIndexes.length - 1]; if (removedCount > 0) { ListIterator<String> i = path.listIterator(index); for (int j = 0; j < removedCount; j++) { i.previous(); i.remove(); } for (int j = segment; j < pathSegmentIndexes.length; j++) { pathSegmentIndexes[j] -= removedCount; } if (isTruncate) { renderer.truncatePathBuffer(removedCount); } else { renderer.clearPathBuffer(); } } } private void assertSegment(int segment) { if (segment < 0 || segment >= pathSegmentIndexes.length) { throw new IllegalArgumentException("segment index " + segment + " out of bound [0, " + pathSegmentIndexes.length + ")"); } } /** 取得URI query表。 */ public Map<String, Object/* String or String[] */> getQuery() { return query; } /** 设置一组query。 */ public void setQuery(Map<String, Object> query) { getQuery().clear(); if (query != null && !query.isEmpty()) { for (Map.Entry<String, Object> entry : query.entrySet()) { setQueryData(entry.getKey(), entry.getValue()); } } renderer.clearQueryBuffer(); } /** 删除所有的query。 */ public URIBroker clearQuery() { if (query != null) { query.clear(); } renderer.clearQueryBuffer(); return this; } /** 取得指定id对应的query值。 */ public String getQueryData(String id) { Object value = getQuery().get(id); if (value instanceof String) { return (String) value; } else if (value instanceof String[]) { String[] values = (String[]) value; if (values.length > 0) { return values[0]; } } return null; } /** 设置query。 */ public URIBroker setQueryData(String id, Object values) { id = assertNotNull(trimToNull(id), "empty query id"); Map<String, Object> query = getQuery(); if (values == null) { query.put(id, EMPTY_STRING); } else if (values instanceof String[]) { String[] strArray = (String[]) values; for (int i = 0; i < strArray.length; i++) { if (strArray[i] == null) { strArray[i] = EMPTY_STRING; } } query.put(id, strArray); } else if (values.getClass().isArray()) { String[] strArray = new String[Array.getLength(values)]; for (int i = 0; i < strArray.length; i++) { Object value = Array.get(values, i); strArray[i] = value == null ? EMPTY_STRING : String.valueOf(value); } query.put(id, strArray); } else { query.put(id, String.valueOf(values)); } renderer.clearQueryBuffer(); return this; } /** 添加query。 */ public URIBroker addQueryData(String id, Object value) { id = assertNotNull(trimToNull(id), "empty query id"); String strValue; if (value == null) { strValue = EMPTY_STRING; } else { assertTrue(!value.getClass().isArray(), "use setQueryData(array) instead"); strValue = String.valueOf(value); } Object values = getQuery().get(id); if (values == null) { values = strValue; } else if (values instanceof String) { values = new String[] { (String) values, strValue }; } else if (values instanceof String[]) { int length = ((String[]) values).length; String[] tmp = new String[length + 1]; System.arraycopy(values, 0, tmp, 0, length); tmp[length] = strValue; values = tmp; } else { unreachableCode(); } getQuery().put(id, values); renderer.updateQueryBuffer(id, strValue); return this; } /** 删除指定的query。 */ public URIBroker removeQueryData(String id) { if (query != null) { query.remove(trimToNull(id)); } renderer.clearQueryBuffer(); return this; } /** 返回不带参数的uri路径,并将参数部分添加到query中、设置ref(如果有的话)。 */ protected final String setUriAndGetPath(String uri) { if (uri != null) { int i = uri.indexOf("?"); int j = uri.indexOf("#", i + 1); if (j >= 0) { setReference(uri.substring(j + 1)); uri = uri.substring(0, j); } if (i >= 0) { String query = uri.substring(i + 1); uri = uri.substring(0, i); new URIBrokerQueryStringParser().parse(query); } } return uri; } /** * 复制parent broker中的值作为默认值,但不覆盖当前broker中已有的值。 * <p> * 子类应该覆盖此方法,以实现特定的init功能。 * </p> */ @Override protected void initDefaults(URIBroker parent) { if (type == null) { type = parent.getURIType(); } if (baseURI == null) { baseURI = parent.baseURI; } if (serverScheme == null) { serverScheme = parent.getServerScheme(); } if (serverName == null) { serverName = parent.getServerName(); } if (serverPort <= 0) { serverPort = parent.getServerPort(); } if (loginUser == null) { loginUser = parent.getLoginUser(); } if (loginPassword == null) { loginPassword = parent.getLoginPassword(); } if (reference == null) { reference = parent.getReference(); } if (!parent.getQuery().isEmpty()) { // 合并query,包括合并query的值 Map<String, Object> queryCopy = createLinkedHashMap(); queryCopy.putAll(getQuery()); clearQuery(); getQuery().putAll(parent.getQuery()); for (Map.Entry<String, Object> entry : queryCopy.entrySet()) { String id = entry.getKey(); Object values = entry.getValue(); if (values instanceof String) { addQueryData(id, values); } else if (values instanceof String[]) { for (String value : (String[]) values) { addQueryData(id, value); } } } } } /** * 复制parent的状态。 * <p> * 子类应该覆盖此方法,以实现特定的reset功能。 * </p> */ @Override protected void copyFrom(URIBroker parent) { baseURI = parent.baseURI; setURIType(parent.getURIType()); setServerScheme(parent.getServerScheme()); setServerName(parent.getServerName()); setServerPort(parent.getServerPort()); setLoginUser(parent.getLoginUser()); setLoginPassword(parent.getLoginPassword()); setReference(parent.getReference()); clearQuery(); setQuery(parent.getQuery()); } /** 将request中的运行时信息填充到uri broker中。 */ @Override protected void populateWithRequest(HttpServletRequest request) { // scheme、serverName和serverPort必须一起设。 if (serverScheme == null && serverName == null && serverPort <= 0) { setServerScheme(request.getScheme()); setServerName(request.getServerName()); setServerPort(request.getServerPort()); } } /** * 按顺序执行所有path interceptors。 * <p> * 和<code>URIBrokerInterceptor</code>不同, * <code>URIBrokerPathInterceptor</code>在每次render时均会被调用。 * </p> */ protected String processPathInterceptors(String path) { if (hasInterceptors()) { for (Map.Entry<URIBrokerInterceptor, Integer> entry : getInterceptorStates().entrySet()) { if (entry.getKey() instanceof URIBrokerPathInterceptor) { URIBrokerPathInterceptor interceptor = (URIBrokerPathInterceptor) entry.getKey(); Integer value = entry.getValue(); if (value != null && value.intValue() == 1) { path = interceptor.perform(this, path); } } } } return path; } @Override protected final void render(StringBuilder buf) { render(buf, false); } private void render(StringBuilder buf, boolean renderServerURIOnly) { URIType type = renderServerURIOnly || getURIType() == null ? URIType.full : getURIType(); BaseURI baseURI = null; boolean renderServer = true; switch (type) { case auto: case absolute: case relative: baseURI = createBaseURI(); if (baseURI != null) { // 当且仅当baseURI中的server信息和broker中的一致,才不渲染server String scheme = getEffectiveServerScheme(getServerScheme()); int port = getEffectiveServerPort(scheme, getServerPort()); if (isEquals(scheme, baseURI.serverScheme) && isEquals(getServerName(), baseURI.serverName) && port == baseURI.serverPort) { renderServer = false; } } break; case full: } // server info if (renderServer) { if (renderer.isServerRendered()) { buf.append(renderer.serverBuffer); } else { renderServer(buf); } } // path info String path = EMPTY_STRING; if (!URIBroker.this.path.isEmpty()) { StringBuilder pathBuf = new StringBuilder(); if (renderer.isPathRendered()) { pathBuf.append(renderer.pathBuffer); } else { renderPath(pathBuf); } path = pathBuf.toString(); } path = trimToEmpty(processPathInterceptors(path)); // 将path转换成相对路径(前提:server未渲染) if (!renderServer) { switch (type) { case relative: path = getRelativePath(baseURI.path, path); break; case auto: String relativePath = getRelativePath(baseURI.path, path); if (!relativePath.startsWith("../")) { path = relativePath; } break; case full: case absolute: } } if (renderServer && !path.startsWith("/")) { buf.append("/"); } buf.append(path); if (!renderServerURIOnly) { // query info if (renderer.isQueryRendered()) { buf.append(renderer.queryBuffer); } else { renderQuery(buf); } // #reference String reference = getReference(); if (!isEmpty(reference)) { buf.append("#"); buf.append(reference); } } } /** 从request或指定URI中创建并规格化baseURI。 */ private BaseURI createBaseURI() { BaseURI result = null; if (baseURI != null) { result = new BaseURI(); result.serverScheme = getEffectiveServerScheme(baseURI.getScheme()); result.serverName = trimToNull(baseURI.getHost()); result.serverPort = getEffectiveServerPort(result.serverScheme, baseURI.getPort()); result.path = trimToEmpty(baseURI.getPath()); } else { HttpServletRequest request = getRealRequest(); if (request != null) { result = new BaseURI(); result.serverScheme = getEffectiveServerScheme(request.getScheme()); result.serverName = trimToNull(request.getServerName()); result.serverPort = getEffectiveServerPort(result.serverScheme, request.getServerPort()); result.path = trimToEmpty(request.getRequestURI()); } } return result; } /** 将path转换成相对于base的相对路径。 */ private String getRelativePath(String base, String path) { // 去除base中最后一段路径,例如: // /sub/dir/index.html -> /sub/dir // /sub/dir/ -> /sub/dir int index = base.lastIndexOf("/"); if (index >= 0) { base = base.substring(0, index); } // 特殊情况:base为空,直接返回path,但除去开头的/ if (isEmpty(base)) { if (path.startsWith("/")) { return path.replaceFirst("^/+", EMPTY_STRING); } else { return path; } } Matcher baseMatcher = pathPattern.matcher(base); Matcher pathMatcher = pathPattern.matcher(path); boolean baseFound; boolean pathFound; // 去除base和path中相同的部分,例如: // /this/is/base/dir -> /base/dir // /this/is/my/path -> /my/path do { baseFound = baseMatcher.find(); pathFound = pathMatcher.find(); } while (baseFound && pathFound && isEquals(baseMatcher.group(2), pathMatcher.group(2))); StringBuilder relativePath = new StringBuilder(); // 将base中剩余的部分,分别转换成../ for (; baseFound; baseFound = baseMatcher.find()) { relativePath.append("../"); } // 将path中剩余的部分,追加到末尾 while (pathFound) { relativePath.append(pathMatcher.group(2)); pathFound = pathMatcher.find(); if (pathFound) { relativePath.append("/"); } } return relativePath.toString(); } /** 渲染URI服务器信息。 */ @Override protected final void renderServer(StringBuilder buf) { // scheme:// String serverScheme = getEffectiveServerScheme(getServerScheme()); buf.append(serverScheme); buf.append("://"); // user:password@ String loginUser = getLoginUser(); String loginPassword = getLoginPassword(); if (!isEmpty(loginUser)) { buf.append(loginUser); if (!isEmpty(loginPassword)) { buf.append(":").append(loginPassword); } buf.append("@"); } // hostname:port String serverName = getServerName(); if (!isEmpty(serverName)) { buf.append(serverName); if (!isDefaultPort(serverScheme)) { buf.append(":"); buf.append(getServerPort()); } } } private String getEffectiveServerScheme(String serverScheme) { return defaultIfEmpty(serverScheme, SERVER_SCHEME_HTTP); } private int getEffectiveServerPort(String serverScheme, int serverPort) { if (serverPort <= 0) { if (SERVER_SCHEME_HTTP.equals(serverScheme)) { return SERVER_PORT_HTTP; } else if (SERVER_SCHEME_HTTPS.equals(serverScheme)) { return SERVER_PORT_HTTPS; } } return serverPort; } private boolean isDefaultPort(String serverScheme) { int serverPort = getServerPort(); boolean isDefaultPort = serverPort <= 0; if (!isDefaultPort) { // http和80 isDefaultPort |= SERVER_SCHEME_HTTP.equals(serverScheme) && serverPort == SERVER_PORT_HTTP; // https和443 isDefaultPort |= SERVER_SCHEME_HTTPS.equals(serverScheme) && serverPort == SERVER_PORT_HTTPS; } return isDefaultPort; } /** 渲染URI path。 */ @Override protected final void renderPath(StringBuilder buf) { for (String path : URIBroker.this.path) { buf.append("/").append(escapeURL(path)); } } /** 渲染URI query string。 */ @Override protected final void renderQuery(StringBuilder buf) { if (!getQuery().isEmpty()) { buf.append("?"); for (Iterator<Map.Entry<String, Object>> i = getQuery().entrySet().iterator(); i.hasNext(); ) { Map.Entry<String, Object> entry = i.next(); String id = escapeURL(entry.getKey()); Object value = entry.getValue(); if (value instanceof String[]) { int length = ((String[]) value).length; for (int j = 0; j < length; j++) { buf.append(id).append("=").append(escapeURL(((String[]) value)[j])); if (j + 1 < length) { buf.append("&"); } } } else { buf.append(id).append("=").append(escapeURL(String.valueOf(value))); } if (i.hasNext()) { buf.append("&"); } } } } private class BaseURI { String serverScheme; String serverName; int serverPort; String path; } private final class URIBrokerQueryStringParser extends QueryStringParser { public URIBrokerQueryStringParser() { super(getCharset()); } @Override protected void add(String key, String value) { addQueryData(key, value); } } }