001package co.codewizards.cloudstore.rest.client.transport; 002 003import static co.codewizards.cloudstore.core.objectfactory.ObjectFactoryUtil.*; 004import static co.codewizards.cloudstore.core.oio.OioFileFactory.*; 005import static java.util.Objects.*; 006 007import java.io.IOException; 008import java.net.MalformedURLException; 009import java.net.URL; 010import java.security.GeneralSecurityException; 011import java.util.Arrays; 012import java.util.Collections; 013import java.util.Date; 014import java.util.HashMap; 015import java.util.List; 016import java.util.Map; 017import java.util.UUID; 018 019import javax.ws.rs.client.ClientBuilder; 020 021import org.slf4j.Logger; 022import org.slf4j.LoggerFactory; 023 024import co.codewizards.cloudstore.core.auth.AuthConstants; 025import co.codewizards.cloudstore.core.auth.AuthToken; 026import co.codewizards.cloudstore.core.auth.AuthTokenIO; 027import co.codewizards.cloudstore.core.auth.AuthTokenVerifier; 028import co.codewizards.cloudstore.core.auth.EncryptedSignedAuthToken; 029import co.codewizards.cloudstore.core.auth.SignedAuthToken; 030import co.codewizards.cloudstore.core.auth.SignedAuthTokenDecrypter; 031import co.codewizards.cloudstore.core.auth.SignedAuthTokenIO; 032import co.codewizards.cloudstore.core.concurrent.DeferredCompletionException; 033import co.codewizards.cloudstore.core.config.ConfigImpl; 034import co.codewizards.cloudstore.core.dto.ChangeSetDto; 035import co.codewizards.cloudstore.core.dto.ConfigPropSetDto; 036import co.codewizards.cloudstore.core.dto.DateTime; 037import co.codewizards.cloudstore.core.dto.RepoFileDto; 038import co.codewizards.cloudstore.core.dto.RepositoryDto; 039import co.codewizards.cloudstore.core.dto.VersionInfoDto; 040import co.codewizards.cloudstore.core.dto.jaxb.ChangeSetDtoIo; 041import co.codewizards.cloudstore.core.io.TimeoutException; 042import co.codewizards.cloudstore.core.oio.File; 043import co.codewizards.cloudstore.core.oio.FileFilter; 044import co.codewizards.cloudstore.core.repo.local.ContextWithLocalRepoManager; 045import co.codewizards.cloudstore.core.repo.local.LocalRepoManager; 046import co.codewizards.cloudstore.core.repo.local.LocalRepoManagerFactory; 047import co.codewizards.cloudstore.core.repo.local.LocalRepoRegistryImpl; 048import co.codewizards.cloudstore.core.repo.transport.AbstractRepoTransport; 049import co.codewizards.cloudstore.rest.client.ClientBuilderDefaultValuesDecorator; 050import co.codewizards.cloudstore.rest.client.CloudStoreRestClient; 051import co.codewizards.cloudstore.rest.client.CredentialsProvider; 052import co.codewizards.cloudstore.rest.client.request.BeginPutFile; 053import co.codewizards.cloudstore.rest.client.request.Copy; 054import co.codewizards.cloudstore.rest.client.request.Delete; 055import co.codewizards.cloudstore.rest.client.request.EndPutFile; 056import co.codewizards.cloudstore.rest.client.request.EndSyncFromRepository; 057import co.codewizards.cloudstore.rest.client.request.EndSyncToRepository; 058import co.codewizards.cloudstore.rest.client.request.GetChangeSetDto; 059import co.codewizards.cloudstore.rest.client.request.GetClientRepositoryDto; 060import co.codewizards.cloudstore.rest.client.request.GetEncryptedSignedAuthToken; 061import co.codewizards.cloudstore.rest.client.request.GetFileData; 062import co.codewizards.cloudstore.rest.client.request.GetRepoFileDto; 063import co.codewizards.cloudstore.rest.client.request.GetRepositoryDto; 064import co.codewizards.cloudstore.rest.client.request.GetVersionInfoDto; 065import co.codewizards.cloudstore.rest.client.request.MakeDirectory; 066import co.codewizards.cloudstore.rest.client.request.MakeSymlink; 067import co.codewizards.cloudstore.rest.client.request.Move; 068import co.codewizards.cloudstore.rest.client.request.PutFileData; 069import co.codewizards.cloudstore.rest.client.request.PutParentConfigPropSetDto; 070import co.codewizards.cloudstore.rest.client.request.RequestRepoConnection; 071import co.codewizards.cloudstore.rest.client.ssl.DynamicX509TrustManagerCallback; 072import co.codewizards.cloudstore.rest.client.ssl.SSLContextBuilder; 073 074public class RestRepoTransport extends AbstractRepoTransport implements CredentialsProvider, ContextWithLocalRepoManager { 075 private static final Logger logger = LoggerFactory.getLogger(RestRepoTransport.class); 076 077 public static final String CONFIG_KEY_GET_CHANGE_SET_DTO_TIMEOUT = "getChangeSetDtoTimeout"; 078 public static final long CONFIG_DEFAULT_GET_CHANGE_SET_DTO_TIMEOUT = 60L * 60L * 1000L; 079 080 public static final String CONFIG_KEY_GET_REPO_FILE_DTO_WITH_FILE_CHUNK_DTOS_TIMEOUT = "getRepoFileDtoWithFileChunkDtosTimeout"; 081 public static final long CONFIG_DEFAULT_GET_REPO_FILE_DTO_WITH_FILE_CHUNK_DTOS_TIMEOUT = 60L * 60L * 1000L; 082 083 private final long changeSetTimeout = ConfigImpl.getInstance().getPropertyAsPositiveOrZeroLong( 084 CONFIG_KEY_GET_CHANGE_SET_DTO_TIMEOUT, CONFIG_DEFAULT_GET_CHANGE_SET_DTO_TIMEOUT); 085 086 private final long fileChunkSetTimeout = ConfigImpl.getInstance().getPropertyAsPositiveOrZeroLong( 087 CONFIG_KEY_GET_REPO_FILE_DTO_WITH_FILE_CHUNK_DTOS_TIMEOUT, CONFIG_DEFAULT_GET_REPO_FILE_DTO_WITH_FILE_CHUNK_DTOS_TIMEOUT); 088 089// public static final String CHANGE_SET_DTO_CACHE_FILE_NAME_TEMPLATE = "ChangeSetDto.${serverRepositoryId}.${lastRevisionSynced}.xml.gz"; 090 public static final String CHANGE_SET_DTO_CACHE_FILE_NAME_PREFIX = "ChangeSetDto."; 091 public static final String CHANGE_SET_DTO_CACHE_FILE_NAME_SUFFIX = ".xml.gz"; 092 public static final String TMP_FILE_NAME_SUFFIX = ".tmp"; 093 094 private UUID repositoryId; // server-repository 095 private byte[] publicKey; 096 private String repositoryName; // server-repository 097 private CloudStoreRestClient client; 098 private final Map<UUID, AuthToken> clientRepositoryId2AuthToken = new HashMap<UUID, AuthToken>(1); // should never be more ;-) 099 private LocalRepoManager localRepoManager; 100 private File localRepoTmpDir; 101 102 protected DynamicX509TrustManagerCallback getDynamicX509TrustManagerCallback() { 103 final RestRepoTransportFactory repoTransportFactory = (RestRepoTransportFactory) getRepoTransportFactory(); 104 final Class<? extends DynamicX509TrustManagerCallback> klass = repoTransportFactory.getDynamicX509TrustManagerCallbackClass(); 105 if (klass == null) 106 throw new IllegalStateException("dynamicX509TrustManagerCallbackClass is not set!"); 107 108 try { 109 final DynamicX509TrustManagerCallback instance = klass.newInstance(); 110 return instance; 111 } catch (final Exception e) { 112 throw new RuntimeException(String.format("Could not instantiate class %s: %s", klass.getName(), e.toString()), e); 113 } 114 } 115 116 public RestRepoTransport() { } 117 118 @Override 119 public UUID getRepositoryId() { 120 if (repositoryId == null) { 121 final RepositoryDto repositoryDto = getRepositoryDto(); 122 repositoryId = repositoryDto.getRepositoryId(); 123 publicKey = repositoryDto.getPublicKey(); 124 } 125 return repositoryId; 126 } 127 128 @Override 129 public byte[] getPublicKey() { 130 getRepositoryId(); // ensure, the public key is loaded 131 return requireNonNull(publicKey, "publicKey"); 132 } 133 134 @Override 135 public RepositoryDto getRepositoryDto() { 136 return getClient().execute(new GetRepositoryDto(getRepositoryName())); 137 } 138 139 @Override 140 public RepositoryDto getClientRepositoryDto() { 141 getClientRepositoryIdOrFail(); 142 return getClient().execute(new GetClientRepositoryDto(getRepositoryName())); 143 } 144 145 @Override 146 public void requestRepoConnection(final byte[] publicKey) { 147 final RepositoryDto repositoryDto = new RepositoryDto(); 148 repositoryDto.setRepositoryId(getClientRepositoryIdOrFail()); 149 repositoryDto.setPublicKey(publicKey); 150 getClient().execute(new RequestRepoConnection(getRepositoryName(), getPathPrefix(), repositoryDto)); 151 } 152 153 @Override 154 public void close() { 155 client = null; 156 super.close(); 157 } 158 159 @Override 160 public ChangeSetDto getChangeSetDto(final boolean localSync, final Long lastSyncToRemoteRepoLocalRepositoryRevisionSynced) { 161 File changeSetDtoCacheFile = null; 162 ChangeSetDto result = null; 163 164 try { 165 changeSetDtoCacheFile = getChangeSetDtoCacheFile(lastSyncToRemoteRepoLocalRepositoryRevisionSynced); 166 if (changeSetDtoCacheFile.isFile() && changeSetDtoCacheFile.length() > 0) { 167 ChangeSetDtoIo changeSetDtoIo = createObject(ChangeSetDtoIo.class); 168 result = changeSetDtoIo.deserializeWithGz(changeSetDtoCacheFile); 169 logger.info("getChangeSetDto: Read ChangeSetDto-cache-file: {}", changeSetDtoCacheFile.getAbsolutePath()); 170 return result; 171 } else { 172 logger.info("getChangeSetDto: ChangeSetDto-cache-file NOT found: {}", changeSetDtoCacheFile.getAbsolutePath()); 173 } 174 } catch (Exception x) { 175 result = null; 176 logger.error("getChangeSetDto: Reading ChangeSetDto-cache-file failed: " + x, x); 177 } 178 179 final long beginTimestamp = System.currentTimeMillis(); 180 while (true) { 181 try { 182 result = getClient().execute(new GetChangeSetDto(getRepositoryId().toString(), localSync, lastSyncToRemoteRepoLocalRepositoryRevisionSynced)); 183 } catch (final DeferredCompletionException x) { 184 if (System.currentTimeMillis() > beginTimestamp + changeSetTimeout) 185 throw new TimeoutException(String.format("Could not get change-set within %s milliseconds!", changeSetTimeout), x); 186 187 logger.info("getChangeSetDto: Got DeferredCompletionException; will retry."); 188 } 189 190 if (result != null) { 191 if (changeSetDtoCacheFile != null) { 192 File tmpFile = changeSetDtoCacheFile.getParentFile().createFile(changeSetDtoCacheFile.getName() + TMP_FILE_NAME_SUFFIX); 193 ChangeSetDtoIo changeSetDtoIo = createObject(ChangeSetDtoIo.class); 194 changeSetDtoIo.serializeWithGz(result, tmpFile); 195 if (! tmpFile.renameTo(changeSetDtoCacheFile)) { 196 logger.error("getChangeSetDto: Could not rename temporary file to active ChangeSetDto-cache-file: {}", changeSetDtoCacheFile.getAbsolutePath()); 197 } else { 198 logger.info("getChangeSetDto: Wrote ChangeSetDto-cache-file: {}", changeSetDtoCacheFile.getAbsolutePath()); 199 } 200 } 201 return result; 202 } 203 } 204 } 205 206 @Override 207 public void prepareForChangeSetDto(ChangeSetDto changeSetDto) { 208 // nothing to do here. 209 } 210 211 @Override 212 public void makeDirectory(String path, final Date lastModified) { 213 path = prefixPath(path); 214 getClient().execute(new MakeDirectory(getRepositoryId().toString(), path, lastModified)); 215 } 216 217 @Override 218 public void makeSymlink(String path, final String target, final Date lastModified) { 219 path = prefixPath(path); 220 getClient().execute(new MakeSymlink(getRepositoryId().toString(), path, target, lastModified)); 221 } 222 223 @Override 224 public void copy(String fromPath, String toPath) { 225 fromPath = prefixPath(fromPath); 226 toPath = prefixPath(toPath); 227 getClient().execute(new Copy(getRepositoryId().toString(), fromPath, toPath)); 228 } 229 230 @Override 231 public void move(String fromPath, String toPath) { 232 fromPath = prefixPath(fromPath); 233 toPath = prefixPath(toPath); 234 getClient().execute(new Move(getRepositoryId().toString(), fromPath, toPath)); 235 } 236 237 @Override 238 public void delete(String path) { 239 path = prefixPath(path); 240 getClient().execute(new Delete(getRepositoryId().toString(), path)); 241 } 242 243 @Override 244 public RepoFileDto getRepoFileDto(String path) { 245 path = prefixPath(path); 246 final long beginTimestamp = System.currentTimeMillis(); 247 while (true) { 248 try { 249 return getClient().execute(new GetRepoFileDto(getRepositoryId().toString(), path)); 250 } catch (final DeferredCompletionException x) { 251 if (System.currentTimeMillis() > beginTimestamp + fileChunkSetTimeout) 252 throw new TimeoutException(String.format("Could not get file-chunk-set within %s milliseconds!", fileChunkSetTimeout), x); 253 254 logger.info("getFileChunkSet: Got DeferredCompletionException; will retry."); 255 } 256 } 257 } 258 259 @Override 260 public byte[] getFileData(String path, final long offset, final int length) { 261 path = prefixPath(path); 262 return getClient().execute(new GetFileData(getRepositoryId().toString(), path, offset, length)); 263 } 264 265 @Override 266 public void beginPutFile(String path) { 267 path = prefixPath(path); 268 getClient().execute(new BeginPutFile(getRepositoryId().toString(), path)); 269 } 270 271 @Override 272 public void putFileData(String path, final long offset, final byte[] fileData) { 273 path = prefixPath(path); 274 getClient().execute(new PutFileData(getRepositoryId().toString(), path, offset, fileData)); 275 } 276 277 @Override 278 public void endPutFile(String path, final Date lastModified, final long length, final String sha1) { 279 path = prefixPath(path); 280 getClient().execute(new EndPutFile(getRepositoryId().toString(), path, new DateTime(lastModified), length, sha1)); 281 } 282 283 @Override 284 public void endSyncFromRepository() { 285 for (final File file : getChangeSetDtoCacheFiles(true)) { 286 file.delete(); 287 } 288 File tmpDir = this.localRepoTmpDir; 289 if (tmpDir != null) { 290 tmpDir.delete(); // deletes only, if empty. 291 this.localRepoTmpDir = null; // null this in order to ensure that it is re-created by getLocalRepoTmpDir(). 292 } 293 getClient().execute(new EndSyncFromRepository(getRepositoryId().toString())); 294 } 295 296 @Override 297 public void endSyncToRepository(final long fromLocalRevision) { 298 getClient().execute(new EndSyncToRepository(getRepositoryId().toString(), fromLocalRevision)); 299 } 300 301 @Override 302 public void putParentConfigPropSetDto(ConfigPropSetDto parentConfigPropSetDto) { 303 getClient().execute(new PutParentConfigPropSetDto(getRepositoryId().toString(), parentConfigPropSetDto)); 304 } 305 306 @Override 307 public String getUserName() { 308 final UUID clientRepositoryId = getClientRepositoryIdOrFail(); 309 return AuthConstants.USER_NAME_REPOSITORY_ID_PREFIX + clientRepositoryId; 310 } 311 312 @Override 313 public String getPassword() { 314 final AuthToken authToken = getAuthToken(); 315 return authToken.getPassword(); 316 } 317 318 private AuthToken getAuthToken() { 319 final UUID clientRepositoryId = getClientRepositoryIdOrFail(); 320 AuthToken authToken = clientRepositoryId2AuthToken.get(clientRepositoryId); 321 if (authToken != null && isAfterRenewalDate(authToken)) { 322 logger.debug("getAuthToken: old AuthToken passed renewal-date: clientRepositoryId={} serverRepositoryId={} renewalDateTime={} expiryDateTime={}", 323 clientRepositoryId, getRepositoryId(), authToken.getRenewalDateTime(), authToken.getExpiryDateTime()); 324 325 authToken = null; 326 } 327 328 if (authToken == null) { 329 logger.debug("getAuthToken: getting new AuthToken: clientRepositoryId={} serverRepositoryId={}", 330 clientRepositoryId, getRepositoryId()); 331 332 final File localRoot = LocalRepoRegistryImpl.getInstance().getLocalRoot(clientRepositoryId); 333 final LocalRepoManager localRepoManager = LocalRepoManagerFactory.Helper.getInstance().createLocalRepoManagerForExistingRepository(localRoot); 334 try { 335 final EncryptedSignedAuthToken encryptedSignedAuthToken = getClient().execute(new GetEncryptedSignedAuthToken(getRepositoryName(), localRepoManager.getRepositoryId())); 336 337 final byte[] signedAuthTokenData = new SignedAuthTokenDecrypter(localRepoManager.getPrivateKey()).decrypt(encryptedSignedAuthToken); 338 339 final SignedAuthToken signedAuthToken = new SignedAuthTokenIO().deserialise(signedAuthTokenData); 340 341 final AuthTokenVerifier verifier = new AuthTokenVerifier(localRepoManager.getRemoteRepositoryPublicKeyOrFail(getRepositoryId())); 342 verifier.verify(signedAuthToken); 343 344 authToken = new AuthTokenIO().deserialise(signedAuthToken.getAuthTokenData()); 345 final Date expiryDate = requireNonNull(authToken.getExpiryDateTime(), "authToken.expiryDateTime").toDate(); 346 final Date renewalDate = requireNonNull(authToken.getRenewalDateTime(), "authToken.renewalDateTime").toDate(); 347 if (!renewalDate.before(expiryDate)) 348 throw new IllegalArgumentException( 349 String.format("Invalid AuthToken: renewalDateTime >= expiryDateTime :: renewalDateTime=%s expiryDateTime=%s", 350 authToken.getRenewalDateTime(), authToken.getExpiryDateTime())); 351 352 clientRepositoryId2AuthToken.put(clientRepositoryId, authToken); 353 } finally { 354 localRepoManager.close(); 355 } 356 357 logger.info("getAuthToken: got new AuthToken: clientRepositoryId={} serverRepositoryId={} renewalDateTime={} expiryDateTime={}", 358 clientRepositoryId, getRepositoryId(), authToken.getRenewalDateTime(), authToken.getExpiryDateTime()); 359 } 360 else 361 logger.trace("getAuthToken: old AuthToken still valid: clientRepositoryId={} serverRepositoryId={} renewalDateTime={} expiryDateTime={}", 362 clientRepositoryId, getRepositoryId(), authToken.getRenewalDateTime(), authToken.getExpiryDateTime()); 363 364 return authToken; 365 } 366 367 private boolean isAfterRenewalDate(final AuthToken authToken) { 368 requireNonNull(authToken, "authToken"); 369 return System.currentTimeMillis() > authToken.getRenewalDateTime().getMillis(); 370 } 371 372 protected CloudStoreRestClient getClient() { 373 if (client == null) { 374 ClientBuilder clientBuilder = createClientBuilder(); 375 final CloudStoreRestClient c = new CloudStoreRestClient(getRemoteRoot(), clientBuilder); 376 c.setCredentialsProvider(this); 377 client = c; 378 } 379 return client; 380 } 381 382 @Override 383 protected URL determineRemoteRootWithoutPathPrefix() { 384 final String repositoryName = getRepositoryName(); 385 final String baseURL = getClient().getBaseUrl(); 386 if (!baseURL.endsWith("/")) 387 throw new IllegalStateException(String.format("baseURL does not end with a '/'! baseURL='%s'", baseURL)); 388 389 try { 390 return new URL(baseURL + repositoryName); 391 } catch (final MalformedURLException e) { 392 throw new RuntimeException(e); 393 } 394 } 395 396 public String getRepositoryName() { 397 if (repositoryName == null) { 398 final String pathAfterBaseURL = getPathAfterBaseURL(); 399 final int indexOfFirstSlash = pathAfterBaseURL.indexOf('/'); 400 if (indexOfFirstSlash < 0) { 401 repositoryName = pathAfterBaseURL; 402 } 403 else { 404 repositoryName = pathAfterBaseURL.substring(0, indexOfFirstSlash); 405 } 406 if (repositoryName.isEmpty()) 407 throw new IllegalStateException("repositoryName is empty!"); 408 } 409 return repositoryName; 410 } 411 412 private String pathAfterBaseURL; 413 414 protected String getPathAfterBaseURL() { 415 String pathAfterBaseURL = this.pathAfterBaseURL; 416 if (pathAfterBaseURL == null) { 417 final URL remoteRoot = getRemoteRoot(); 418 if (remoteRoot == null) 419 throw new IllegalStateException("remoteRoot not yet assigned!"); 420 421 final String baseURL = getClient().getBaseUrl(); 422 if (!baseURL.endsWith("/")) 423 throw new IllegalStateException(String.format("baseURL does not end with a '/'! remoteRoot='%s' baseURL='%s'", remoteRoot, baseURL)); 424 425 final String remoteRootString = remoteRoot.toExternalForm(); 426 if (!remoteRootString.startsWith(baseURL)) 427 throw new IllegalStateException(String.format("remoteRoot does not start with baseURL! remoteRoot='%s' baseURL='%s'", remoteRoot, baseURL)); 428 429 this.pathAfterBaseURL = pathAfterBaseURL = remoteRootString.substring(baseURL.length()); 430 } 431 return pathAfterBaseURL; 432 } 433 434 private ClientBuilder createClientBuilder(){ 435 final ClientBuilder builder = new ClientBuilderDefaultValuesDecorator(); 436 try { 437 builder.sslContext(SSLContextBuilder.create() 438 .remoteURL(getRemoteRoot()) 439 .callback(getDynamicX509TrustManagerCallback()).build()); 440 } catch (final GeneralSecurityException e) { 441 throw new RuntimeException(e); 442 } 443 return builder; 444 } 445 446 @Override 447 public VersionInfoDto getVersionInfoDto() { 448 final VersionInfoDto versionInfoDto = getClient().execute(new GetVersionInfoDto()); 449 return versionInfoDto; 450 } 451 452 protected File getChangeSetDtoCacheFile(final Long lastSyncToRemoteRepoLocalRepositoryRevisionSynced) { 453 String fileName = CHANGE_SET_DTO_CACHE_FILE_NAME_PREFIX 454 + getRepositoryId() + "." 455 + lastSyncToRemoteRepoLocalRepositoryRevisionSynced 456 + CHANGE_SET_DTO_CACHE_FILE_NAME_SUFFIX; 457 return getLocalRepoTmpDir().createFile(fileName); 458 } 459 460 protected List<File> getChangeSetDtoCacheFiles(final boolean includeTmpFiles) { 461 File[] fileArray = getLocalRepoTmpDir().listFiles(new FileFilter() { 462 @Override 463 public boolean accept(File file) { 464 if (! file.getName().startsWith(CHANGE_SET_DTO_CACHE_FILE_NAME_PREFIX)) 465 return false; 466 467 if (file.getName().endsWith(CHANGE_SET_DTO_CACHE_FILE_NAME_SUFFIX)) 468 return true; 469 470 if (includeTmpFiles && file.getName().endsWith(CHANGE_SET_DTO_CACHE_FILE_NAME_SUFFIX + TMP_FILE_NAME_SUFFIX)) 471 return true; 472 else 473 return false; 474 } 475 }); 476 return fileArray == null ? Collections.<File>emptyList() : Arrays.asList(fileArray); 477 } 478 479 protected File getLocalRepoTmpDir() { 480 if (localRepoTmpDir == null) { 481 try { 482 final File metaDir = getLocalRepoMetaDir(); 483 if (! metaDir.isDirectory()) { 484 if (metaDir.isFile()) 485 throw new IOException(String.format("Path '%s' already exists as ordinary file! It should be a directory!", metaDir.getAbsolutePath())); 486 else 487 throw new IOException(String.format("Directory '%s' does not exist!", metaDir.getAbsolutePath())); 488 } 489 490 final File tmpDir = metaDir.createFile(LocalRepoManager.REPO_TEMP_DIR_NAME); 491 if (! tmpDir.isDirectory()) { 492 tmpDir.mkdir(); 493 494 if (! tmpDir.isDirectory()) { 495 if (tmpDir.isFile()) 496 throw new IOException(String.format("Cannot create directory '%s', because this path already exists as an ordinary file!", tmpDir.getAbsolutePath())); 497 else 498 throw new IOException(String.format("Creating directory '%s' failed for an unknown reason (permissions? disk full?)!", tmpDir.getAbsolutePath())); 499 } 500 } 501 this.localRepoTmpDir = tmpDir; 502 } catch (RuntimeException x) { 503 throw x; 504 } catch (Exception x) { 505 throw new RuntimeException(x); 506 } 507 } 508 return localRepoTmpDir; 509 } 510 511 protected File getLocalRepoMetaDir() { 512 final File localRoot = LocalRepoRegistryImpl.getInstance().getLocalRootOrFail(getClientRepositoryIdOrFail()); 513 return createFile(localRoot, LocalRepoManager.META_DIR_NAME); 514 } 515 516 @Override 517 public LocalRepoManager getLocalRepoManager() { 518 if (localRepoManager == null) { 519 logger.debug("getLocalRepoManager: Creating a new LocalRepoManager."); 520 final File localRoot = LocalRepoRegistryImpl.getInstance().getLocalRoot(getClientRepositoryIdOrFail()); 521 localRepoManager = LocalRepoManagerFactory.Helper.getInstance().createLocalRepoManagerForExistingRepository(localRoot); 522 } 523 return localRepoManager; 524 } 525}