| /*=============================================================================# |
| # Copyright (c) 2010, 2020 Stephan Wahlbrink and others. |
| # |
| # This program and the accompanying materials are made available under the |
| # terms of the Eclipse Public License 2.0 which is available at |
| # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 |
| # which is available at https://www.apache.org/licenses/LICENSE-2.0. |
| # |
| # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 |
| # |
| # Contributors: |
| # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation |
| #=============================================================================*/ |
| |
| package org.eclipse.statet.rhelp.core.http; |
| |
| import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullElse; |
| |
| import static org.eclipse.statet.internal.rhelp.core.RHelpWebapp.CAT_DOC; |
| import static org.eclipse.statet.internal.rhelp.core.RHelpWebapp.CAT_LIBRARY; |
| import static org.eclipse.statet.internal.rhelp.core.RHelpWebapp.LIBRARY_HELP; |
| import static org.eclipse.statet.internal.rhelp.core.RHelpWebapp.LIBRARY_HTML; |
| |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| |
| import org.eclipse.statet.jcommons.lang.NonNullByDefault; |
| import org.eclipse.statet.jcommons.lang.Nullable; |
| |
| import org.eclipse.statet.internal.rhelp.core.RHelpWebapp; |
| import org.eclipse.statet.internal.rhelp.core.RHelpWebapp.RequestInfo; |
| import org.eclipse.statet.internal.rhelp.core.server.ServerClientSupport; |
| import org.eclipse.statet.rhelp.core.REnvHelp; |
| import org.eclipse.statet.rhelp.core.REnvHelpConfiguration; |
| import org.eclipse.statet.rhelp.core.RHelpManager; |
| import org.eclipse.statet.rhelp.core.RHelpPage; |
| import org.eclipse.statet.rhelp.core.RHelpTopicLookup; |
| import org.eclipse.statet.rhelp.core.RPkgHelp; |
| import org.eclipse.statet.rj.renv.core.REnv; |
| import org.eclipse.statet.rj.renv.core.REnvConfiguration; |
| |
| |
| @NonNullByDefault |
| public abstract class RHelpHttpService { |
| |
| |
| public static final String HTTP_SCHEME= "http"; //$NON-NLS-1$ |
| |
| public static final String PORTABLE_URI_SCHEME= "erhelp"; //$NON-NLS-1$ |
| |
| |
| /** |
| * Searches topic in library |
| */ |
| private static final String RHELP_TOPIC_PATH= "/topic"; //$NON-NLS-1$ |
| |
| /** |
| * Shows page (package, package/name or package/topic) |
| */ |
| private static final String RHELP_PAGE_PATH= "/page"; //$NON-NLS-1$ |
| |
| private static final String ABOUT_BLANK_URI_STRING= "about:blank"; //$NON-NLS-1$ |
| private static final URI ABOUT_BLANK_URI= URI.create(ABOUT_BLANK_URI_STRING); |
| |
| |
| private final String contextPath= RHelpWebapp.CONTEXT_PATH; |
| |
| private final RHelpManager rHelpManager; |
| |
| public static final String BROWSE_TARGET= "browse"; //$NON-NLS-1$ |
| |
| |
| public RHelpHttpService(final RHelpManager rHelpManager) { |
| this.rHelpManager= rHelpManager; |
| } |
| |
| |
| public abstract boolean ensureIsRunning(); |
| |
| protected abstract String getHost(); |
| protected abstract int getPort(); |
| |
| protected void checkRunning() { |
| if (!ensureIsRunning()) { |
| throw new UnsupportedOperationException("Help is not available."); |
| } |
| } |
| |
| |
| public boolean isDynamicUrl(final URI url) { |
| checkRunning(); |
| final String path; |
| return (HTTP_SCHEME.equals(url.getScheme()) |
| && getHost().equals(url.getHost()) && getPort() == url.getPort() |
| && (path= url.getPath()) != null && path.startsWith(this.contextPath) ); |
| } |
| |
| private String getDynamicPath(final URI url) { |
| return url.getPath().substring(this.contextPath.length()); |
| } |
| |
| protected URI createHttpUrl(final StringBuilder encPath, |
| final @Nullable String encQuery, final @Nullable String encFragment) |
| throws URISyntaxException { |
| final String host= getHost(); |
| final StringBuilder sb= new StringBuilder(host.length() + encPath.length() + 16); |
| sb.append(HTTP_SCHEME); |
| sb.append("://"); //$NON-NLS-1$ |
| sb.append(getHost()); |
| if (getPort() != -1) { |
| sb.append(':'); |
| sb.append(getPort()); |
| } |
| sb.append(encPath); |
| if (encQuery != null) { |
| sb.append('?'); |
| sb.append(encQuery); |
| } |
| if (encFragment != null) { |
| sb.append('#'); |
| sb.append(encFragment); |
| } |
| return new URI(sb.toString()); |
| } |
| |
| protected URI createHttpUrl(final StringBuilder encPath) { |
| try { |
| return createHttpUrl(encPath, null, null); |
| } |
| catch (final URISyntaxException e) { |
| throw new IllegalStateException(e); |
| } |
| } |
| |
| |
| public URI getPageHttpUrl(final RHelpPage page, final String target) { |
| final RPkgHelp pkgHelp= page.getPackage(); |
| return getPageHttpUrl(pkgHelp.getName(), page.getName(), pkgHelp.getREnv(), target); |
| } |
| |
| public URI getPageHttpUrl(final String pkgName, final @Nullable String pageName, |
| final REnv rEnv, final String target) { |
| checkRunning(); |
| final StringBuilder p= new StringBuilder(64); |
| p.append(this.contextPath); |
| p.append('/'); |
| p.append(target); |
| p.append('/'); |
| p.append(rEnv.getId()); |
| p.append('/'); |
| p.append(CAT_LIBRARY); |
| p.append('/'); |
| p.append(pkgName); |
| p.append('/'); |
| if (pageName != null) { |
| p.append(LIBRARY_HTML); |
| p.append('/'); |
| p.append(pageName); |
| p.append(".html"); //$NON-NLS-1$ |
| } |
| return createHttpUrl(p); |
| } |
| |
| public URI getTopicHttpUrl(final String topic, final @Nullable String pkgName, |
| final REnv rEnv, final String target) { |
| checkRunning(); |
| final StringBuilder p= new StringBuilder(64); |
| p.append(this.contextPath); |
| p.append('/'); |
| p.append(target); |
| p.append('/'); |
| p.append(rEnv.getId()); |
| p.append('/'); |
| p.append(CAT_LIBRARY); |
| p.append('/'); |
| p.append((pkgName != null) ? pkgName : "-"); |
| p.append('/'); |
| p.append(LIBRARY_HELP); |
| p.append('/'); |
| p.append(topic); |
| return createHttpUrl(p); |
| } |
| |
| public URI getREnvHttpUrl(final REnv rEnv, final String target) { |
| checkRunning(); |
| final StringBuilder p= new StringBuilder(64); |
| p.append(this.contextPath); |
| p.append('/'); |
| p.append(target); |
| p.append('/'); |
| p.append(rEnv.getId()); |
| p.append('/'); |
| return createHttpUrl(p); |
| } |
| |
| public URI getPackageHttpUrl(final RPkgHelp pkgHelp, final String target) { |
| return getPageHttpUrl(pkgHelp.getName(), null, pkgHelp.getREnv(), target); |
| } |
| |
| public @Nullable URI toHttpUrl(final String url, |
| final @Nullable REnv rEnv, final @Nullable String target) { |
| checkRunning(); |
| if (url.startsWith("rhelp:///")) { //$NON-NLS-1$ |
| if (rEnv == null || target == null) { |
| return null; |
| } |
| |
| final String path= url.substring(8); |
| final int idx1= (path.length() > 0) ? path.indexOf('/', 1) : -1; |
| if (idx1 > 0) { |
| final String command= path.substring(0, idx1); |
| if (command.equals(RHELP_PAGE_PATH)) { |
| final int idx2= path.indexOf('/', idx1 + 1); |
| if (idx2 > idx1 + 1 && idx2 < path.length() - 1) { |
| return getPageHttpUrl(path.substring(idx1 + 1, idx2), |
| path.substring(idx2 + 1), rEnv, target ); |
| } |
| else { |
| return getPageHttpUrl(path.substring(idx1 + 1, (idx2 > 0) ? idx2 : path.length()), |
| null, rEnv, target ); |
| } |
| } |
| else if (command.equals(RHELP_TOPIC_PATH)) { |
| return getTopicHttpUrl(path.substring(idx1 + 1), null, rEnv, target); |
| } |
| } |
| else if (path.length() == 1) { // start |
| return getREnvHttpUrl(rEnv, target); |
| } |
| return null; |
| } |
| if (url.startsWith("http://")) { //$NON-NLS-1$ |
| try { |
| final URI uri= new URI(url); |
| if (isDynamicUrl(uri)) { |
| if (rEnv == null && target == null) { |
| return null; |
| } |
| final String path= getDynamicPath(uri); |
| final int targetEnd= path.indexOf('/', 1); |
| if (targetEnd > 1) { |
| final StringBuilder p= new StringBuilder(path.length() + 16); |
| p.append(this.contextPath); |
| p.append('/'); |
| p.append((target != null) ? target : path.substring(1, targetEnd)); |
| final String info= path.substring(targetEnd + 1); |
| if (rEnv != null) { |
| final int idx3= info.indexOf('/'); |
| if (idx3 < 0) { |
| return null; |
| } |
| p.append('/'); |
| p.append(rEnv.getId()); |
| p.append(info.substring(idx3)); |
| } |
| else { |
| p.append('/'); |
| p.append(info); |
| } |
| return createHttpUrl(p, uri.getRawQuery(), uri.getRawFragment()); |
| } |
| return null; |
| } |
| return uri; |
| } |
| catch (final Exception e) {} |
| } |
| return null; |
| } |
| |
| |
| public @Nullable URI toHttpUrl(final Object object, final String target) { |
| if (object == this) { |
| return ABOUT_BLANK_URI; |
| } |
| if (object instanceof REnv) { |
| return getREnvHttpUrl((REnv) object, target); |
| } |
| if (object instanceof REnvHelpConfiguration) { |
| return getREnvHttpUrl(((REnvHelpConfiguration) object).getREnv(), target); |
| } |
| if (object instanceof RPkgHelp) { |
| return getPackageHttpUrl((RPkgHelp) object, target); |
| } |
| if (object instanceof RHelpPage) { |
| return getPageHttpUrl((RHelpPage) object, target); |
| } |
| if (object instanceof RHelpTopicLookup) { |
| final RHelpTopicLookup lookup= (RHelpTopicLookup) object; |
| return getTopicHttpUrl(lookup.getTopic(), null, lookup.getREnv(), target); |
| } |
| if (object instanceof String) { |
| final String s= (String) object; |
| if (s.startsWith("http://")) { //$NON-NLS-1$ |
| try { |
| return new URI(s); |
| } |
| catch (final URISyntaxException e) {} |
| } |
| } |
| return null; |
| } |
| |
| public @Nullable Object getContentOfUrl(final String url) { |
| try { |
| return getContentOfUrl(new URI(url)); |
| } |
| catch (final URISyntaxException e) { |
| return null; |
| } |
| } |
| |
| public @Nullable Object getContentOfUrl(final URI url) { |
| if (isDynamicUrl(url)) { |
| final String path= getDynamicPath(url); |
| final int targetEnd= path.indexOf('/', 1); |
| if (targetEnd > 1) { |
| final RequestInfo info= RHelpWebapp.extractRequestInfo(path.substring(targetEnd)); |
| if (info != null) { |
| final REnv rEnv= this.rHelpManager.getREnv(info.rEnvId); |
| if (rEnv != null && info.cat == CAT_LIBRARY) { |
| final REnvHelp help= this.rHelpManager.getHelp(rEnv); |
| if (help != null) { |
| try { |
| final RPkgHelp pkgHelp= help.getPkgHelp(info.pkgName); |
| if (pkgHelp != null && info.cmd == RHelpWebapp.PKGCMD_HTML_PAGE) { |
| final RHelpPage page= pkgHelp.getPage(info.detail); |
| if (page != null) { |
| return page; |
| } |
| } |
| return pkgHelp; |
| } |
| finally { |
| help.unlock(); |
| } |
| } |
| return null; |
| } |
| if (rEnv != null && info.cat == CAT_DOC) { |
| return new Object[] { rEnv, null }; |
| } |
| return rEnv; |
| } |
| } |
| } |
| return null; |
| } |
| |
| |
| public boolean isPortableUrl(final URI url) { |
| return PORTABLE_URI_SCHEME.equals(url.getScheme()); |
| } |
| |
| public URI toHttpUrl(final URI url) throws URISyntaxException { |
| if (isPortableUrl(url)) { |
| final String path= nonNullElse(url.getPath(), "/"); //$NON-NLS-1$ |
| final StringBuilder p= new StringBuilder(this.contextPath.length() + path.length() + 2); |
| p.append(this.contextPath); |
| if (path.isEmpty() || path.charAt(0) != '/') { |
| p.append('/'); |
| } |
| p.append(path); |
| return createHttpUrl(p); |
| } |
| return url; |
| } |
| |
| protected URI createPortableUrl(final String path) throws URISyntaxException { |
| return new URI(PORTABLE_URI_SCHEME, null, null, -1, path, null, null); |
| } |
| |
| public @Nullable URI toPortableUrl(final URI url) throws URISyntaxException { |
| if (isDynamicUrl(url)) { |
| final String path= getDynamicPath(url); |
| return createPortableUrl(path); |
| } |
| else if (isPortableUrl(url)) { |
| return url; |
| } |
| return null; |
| } |
| |
| |
| public @Nullable URI toServerUrl(final URI url) { |
| if (isDynamicUrl(url)) { |
| final String path= getDynamicPath(url); |
| final int targetEnd= path.indexOf('/', 1); |
| if (targetEnd > 1 |
| && path.substring(1, targetEnd).equals(BROWSE_TARGET)) { |
| final int envIdEnd= path.indexOf('/', targetEnd + 1); |
| if (envIdEnd > targetEnd + 2) { |
| final REnv rEnv= this.rHelpManager.getREnv(path.substring(targetEnd + 1, envIdEnd)); |
| if (rEnv != null) { |
| final REnvHelpConfiguration rEnvConfig= rEnv.get(REnvHelpConfiguration.class); |
| if (rEnvConfig != null |
| && rEnvConfig.getStateSharedType() == REnvConfiguration.SHARED_SERVER) { |
| try { |
| final ServerClientSupport serverSupport= ServerClientSupport.getInstance(); |
| return serverSupport.toServerBrowseUrl(rEnvConfig, |
| path.substring(envIdEnd) ); |
| } |
| catch (final Exception e) {} |
| } |
| } |
| } |
| } |
| } |
| else { |
| final String path= url.getPath(); |
| if (path != null && path.startsWith(RHelpWebapp.CONTEXT_PATH)) { |
| return url; |
| } |
| } |
| return null; |
| } |
| |
| } |