001package co.codewizards.cloudstore.core.repo.transport; 002 003import static co.codewizards.cloudstore.core.util.AssertUtil.*; 004 005import java.net.URL; 006import java.util.UUID; 007 008import org.slf4j.Logger; 009import org.slf4j.LoggerFactory; 010 011import co.codewizards.cloudstore.core.util.UrlDecoder; 012import co.codewizards.cloudstore.core.util.UrlUtil; 013 014public abstract class AbstractRepoTransport implements RepoTransport { 015 private static final Logger logger = LoggerFactory.getLogger(AbstractRepoTransport.class); 016 017 private static final String SLASH = "/"; 018 019 private RepoTransportFactory repoTransportFactory; 020 private URL remoteRoot; 021 private URL remoteRootWithoutPathPrefix; 022 private String pathPrefix; 023 private UUID clientRepositoryId; 024 025 // Don't know, if fillInStackTrace() is necessary, but better do it. 026 // I did a small test: 1 million invocations of new Exception() vs. new Exception() with fillInStackTrace(): 3 s vs 2.2 s 027 private volatile Throwable repoTransportCreatedStackTraceException = new Exception("repoTransportCreatedStackTraceException").fillInStackTrace(); 028 029 @Override 030 public RepoTransportFactory getRepoTransportFactory() { 031 return repoTransportFactory; 032 } 033 034 @Override 035 public void setRepoTransportFactory(final RepoTransportFactory repoTransportFactory) { 036 this.repoTransportFactory = assertNotNull(repoTransportFactory, "repoTransportFactory"); 037 } 038 039 @Override 040 public URL getRemoteRoot() { 041 return remoteRoot; 042 } 043 @Override 044 public void setRemoteRoot(URL remoteRoot) { 045 remoteRoot = UrlUtil.canonicalizeURL(remoteRoot); 046 final URL rr = this.remoteRoot; 047 if (rr != null && !rr.equals(remoteRoot)) 048 throw new IllegalStateException("Cannot re-assign remoteRoot!"); 049 050 this.remoteRoot = remoteRoot; 051 } 052 053 public UUID getClientRepositoryIdOrFail() { 054 final UUID clientRepositoryId = getClientRepositoryId(); 055 if (clientRepositoryId == null) 056 throw new IllegalStateException("clientRepositoryId == null :: You must invoke setClientRepositoryId(...) before!"); 057 058 return clientRepositoryId; 059 } 060 061 @Override 062 public UUID getClientRepositoryId() { 063 return clientRepositoryId; 064 } 065 @Override 066 public void setClientRepositoryId(final UUID clientRepositoryId) { 067 this.clientRepositoryId = clientRepositoryId; 068 } 069 070 @Override 071 public URL getRemoteRootWithoutPathPrefix() { 072 if (remoteRootWithoutPathPrefix == null) { 073 remoteRootWithoutPathPrefix = UrlUtil.canonicalizeURL(determineRemoteRootWithoutPathPrefix()); 074 } 075 return remoteRootWithoutPathPrefix; 076 } 077 078 protected abstract URL determineRemoteRootWithoutPathPrefix(); 079 080 @Override 081 public String getPathPrefix() { 082 String pathPrefix = this.pathPrefix; 083 if (pathPrefix == null) 084 this.pathPrefix = pathPrefix = determinePathPrefix(); 085 086 return pathPrefix; 087 } 088 089 protected String determinePathPrefix() { 090 final URL rr = getRemoteRoot(); 091 if (rr == null) 092 throw new IllegalStateException("remoteRoot not yet assigned!"); 093 094 final String remoteRoot = rr.toExternalForm(); 095 final String remoteRootWithoutPathPrefix = getRemoteRootWithoutPathPrefix().toExternalForm(); 096 if (!remoteRoot.startsWith(remoteRootWithoutPathPrefix)) 097 throw new IllegalStateException(String.format( 098 "remoteRoot='%s' does not start with remoteRootWithoutPathPrefix='%s'", 099 remoteRoot, remoteRootWithoutPathPrefix)); 100 101 String urlEncodedPathPrefix; 102 if (remoteRoot.equals(remoteRootWithoutPathPrefix)) 103 urlEncodedPathPrefix = ""; 104 else { 105 urlEncodedPathPrefix = remoteRoot.substring(remoteRootWithoutPathPrefix.length()); 106 if (!urlEncodedPathPrefix.startsWith(SLASH)) 107 urlEncodedPathPrefix = SLASH + urlEncodedPathPrefix; 108 109 if (urlEncodedPathPrefix.endsWith(SLASH)) 110 throw new IllegalStateException("pathPrefix should not end with '" + SLASH + "', but it does!"); 111 } 112 113 final String pathPrefix = UrlDecoder.decode(urlEncodedPathPrefix); 114 return pathPrefix; 115 } 116 117 @Override 118 public String prefixPath(final String path) { 119 assertNotNull(path, "path"); 120 if ("".equals(path) || SLASH.equals(path)) 121 return getPathPrefix(); 122 if (path.startsWith(SLASH)) 123 return getPathPrefix() + path; 124 else 125 return getPathPrefix() + SLASH + path; 126 } 127 128 @Override 129 public String unprefixPath(String path) { 130 assertNotNull(path, "path"); 131 final String pathPrefix = getPathPrefix(); 132 if (pathPrefix.isEmpty()) 133 return path; 134 135 if (!path.startsWith(SLASH)) 136 path = SLASH + path; 137 138 if (!path.startsWith(pathPrefix)) 139 throw new IllegalArgumentException(String.format("path='%s' does not start with pathPrefix='%s'!", path, pathPrefix)); 140 141 final String result = path.substring(pathPrefix.length()); 142 if (!result.isEmpty() && !result.startsWith(SLASH)) 143 throw new IllegalStateException(String.format("pathAfterPathPrefix='%s' is neither empty nor does it start with a '/'! path='%s' pathPrefix='%s'", result, path, pathPrefix)); 144 145 return result; 146 } 147 148 protected boolean isPathUnderPathPrefix(final String path) { 149 assertNotNull(path, "path"); 150 final String pathPrefix = getPathPrefix(); 151 if (pathPrefix.isEmpty()) 152 return true; 153 154 return path.startsWith(pathPrefix); 155 } 156 157 @Override 158 protected void finalize() throws Throwable { 159 if (repoTransportCreatedStackTraceException != null) { 160 logger.warn("finalize: Detected forgotten close() invocation!", repoTransportCreatedStackTraceException); 161 } 162 super.finalize(); 163 } 164 165 @Override 166 public void close() { 167 repoTransportCreatedStackTraceException = null; 168 } 169 170}