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}