cache service: split up into cache controller and service

This commit is contained in:
Adam
2018-02-02 22:28:31 -05:00
parent b25c237e94
commit 8c678f69eb
4 changed files with 284 additions and 206 deletions

View File

@@ -0,0 +1,252 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.http.service.cache;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import net.runelite.cache.ConfigType;
import net.runelite.cache.IndexType;
import net.runelite.cache.definitions.ItemDefinition;
import net.runelite.cache.definitions.NpcDefinition;
import net.runelite.cache.definitions.ObjectDefinition;
import net.runelite.cache.definitions.loaders.ItemLoader;
import net.runelite.cache.definitions.loaders.NpcLoader;
import net.runelite.cache.definitions.loaders.ObjectLoader;
import net.runelite.cache.fs.ArchiveFiles;
import net.runelite.cache.fs.FSFile;
import net.runelite.http.api.cache.Cache;
import net.runelite.http.api.cache.CacheArchive;
import net.runelite.http.api.cache.CacheIndex;
import net.runelite.http.service.util.exception.NotFoundException;
import net.runelite.http.service.cache.beans.ArchiveEntry;
import net.runelite.http.service.cache.beans.CacheEntry;
import net.runelite.http.service.cache.beans.IndexEntry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/cache")
@Slf4j
public class CacheController
{
@Autowired
private CacheService cacheService;
@RequestMapping("/")
public List<Cache> listCaches()
{
return cacheService.listCaches().stream()
.map(entry -> new Cache(entry.getId(), entry.getRevision(), entry.getDate()))
.collect(Collectors.toList());
}
@RequestMapping("{cacheId}")
public List<CacheIndex> listIndexes(@PathVariable int cacheId)
{
CacheEntry cache = cacheService.findCache(cacheId);
if (cache == null)
{
throw new NotFoundException();
}
List<IndexEntry> indexes = cacheService.findIndexesForCache(cache);
return indexes.stream()
.map(entry -> new CacheIndex(entry.getIndexId(), entry.getRevision()))
.collect(Collectors.toList());
}
@RequestMapping("{cacheId}/{indexId}")
public List<CacheArchive> listArchives(@PathVariable int cacheId,
@PathVariable int indexId)
{
CacheEntry cache = cacheService.findCache(cacheId);
if (cache == null)
{
throw new NotFoundException();
}
IndexEntry indexEntry = cacheService.findIndexForCache(cache, indexId);
if (indexEntry == null)
{
throw new NotFoundException();
}
List<ArchiveEntry> archives = cacheService.findArchivesForIndex(indexEntry);
return archives.stream()
.map(archive -> new CacheArchive(archive.getArchiveId(), archive.getNameHash(), archive.getRevision()))
.collect(Collectors.toList());
}
@RequestMapping("{cacheId}/{indexId}/{archiveId}")
public CacheArchive getCacheArchive(@PathVariable int cacheId,
@PathVariable int indexId,
@PathVariable int archiveId)
{
CacheEntry cache = cacheService.findCache(cacheId);
if (cache == null)
{
throw new NotFoundException();
}
IndexEntry indexEntry = cacheService.findIndexForCache(cache, indexId);
if (indexEntry == null)
{
throw new NotFoundException();
}
ArchiveEntry archiveEntry = cacheService.findArchiveForIndex(indexEntry, archiveId);
if (archiveEntry == null)
{
throw new NotFoundException();
}
return new CacheArchive(archiveEntry.getArchiveId(),
archiveEntry.getNameHash(), archiveEntry.getRevision());
}
@RequestMapping("{cacheId}/{indexId}/{archiveId}/data")
public byte[] getArchiveData(
@PathVariable int cacheId,
@PathVariable int indexId,
@PathVariable int archiveId
)
{
CacheEntry cache = cacheService.findCache(cacheId);
if (cache == null)
{
throw new NotFoundException();
}
IndexEntry indexEntry = cacheService.findIndexForCache(cache, indexId);
if (indexEntry == null)
{
throw new NotFoundException();
}
ArchiveEntry archiveEntry = cacheService.findArchiveForIndex(indexEntry, archiveId);
if (archiveEntry == null)
{
throw new NotFoundException();
}
return cacheService.getArchive(archiveEntry);
}
private ArchiveEntry findConfig(ConfigType config)
{
CacheEntry cache = cacheService.findMostRecent();
if (cache == null)
{
throw new NotFoundException();
}
IndexEntry indexEntry = cacheService.findIndexForCache(cache, IndexType.CONFIGS.getNumber());
if (indexEntry == null)
{
throw new NotFoundException();
}
ArchiveEntry archiveEntry = cacheService.findArchiveForIndex(indexEntry, config.getId());
if (archiveEntry == null)
{
throw new NotFoundException();
}
return archiveEntry;
}
@RequestMapping("item/{itemId}")
public ItemDefinition getItem(@PathVariable int itemId) throws IOException
{
ArchiveEntry archiveEntry = findConfig(ConfigType.ITEM);
ArchiveFiles archiveFiles = cacheService.getArchiveFiles(archiveEntry);
if (archiveFiles == null)
{
throw new NotFoundException();
}
FSFile file = archiveFiles.findFile(itemId);
if (file == null)
{
throw new NotFoundException();
}
ItemDefinition itemdef = new ItemLoader().load(itemId, file.getContents());
return itemdef;
}
@RequestMapping("object/{objectId}")
public ObjectDefinition getObject(
@PathVariable int objectId
) throws IOException
{
ArchiveEntry archiveEntry = findConfig(ConfigType.OBJECT);
ArchiveFiles archiveFiles = cacheService.getArchiveFiles(archiveEntry);
if (archiveFiles == null)
{
throw new NotFoundException();
}
FSFile file = archiveFiles.findFile(objectId);
if (file == null)
{
throw new NotFoundException();
}
ObjectDefinition objectdef = new ObjectLoader().load(objectId, file.getContents());
return objectdef;
}
@RequestMapping("npc/{npcId}")
public NpcDefinition getNpc(
@PathVariable int npcId
) throws IOException
{
ArchiveEntry archiveEntry = findConfig(ConfigType.NPC);
ArchiveFiles archiveFiles = cacheService.getArchiveFiles(archiveEntry);
if (archiveFiles == null)
{
throw new NotFoundException();
}
FSFile file = archiveFiles.findFile(npcId);
if (file == null)
{
throw new NotFoundException();
}
NpcDefinition npcdef = new NpcLoader().load(npcId, file.getContents());
return npcdef;
}
}

View File

@@ -35,7 +35,7 @@ import org.sql2o.Connection;
import org.sql2o.Query; import org.sql2o.Query;
import org.sql2o.ResultSetIterable; import org.sql2o.ResultSetIterable;
public class CacheDAO class CacheDAO
{ {
// cache prepared statements for high volume queries // cache prepared statements for high volume queries
private Query associateArchive; private Query associateArchive;

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2017, Adam <Adam@sigterm.info> * Copyright (c) 2017-2018, Adam <Adam@sigterm.info>
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@@ -42,46 +42,29 @@ import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j;
import net.runelite.cache.ConfigType;
import net.runelite.cache.IndexType; import net.runelite.cache.IndexType;
import net.runelite.cache.definitions.ItemDefinition;
import net.runelite.cache.definitions.NpcDefinition;
import net.runelite.cache.definitions.ObjectDefinition;
import net.runelite.cache.definitions.loaders.ItemLoader;
import net.runelite.cache.definitions.loaders.NpcLoader;
import net.runelite.cache.definitions.loaders.ObjectLoader;
import net.runelite.cache.fs.ArchiveFiles; import net.runelite.cache.fs.ArchiveFiles;
import net.runelite.cache.fs.Container; import net.runelite.cache.fs.Container;
import net.runelite.cache.fs.FSFile; import net.runelite.cache.fs.FSFile;
import net.runelite.http.api.cache.Cache;
import net.runelite.http.api.cache.CacheArchive;
import net.runelite.http.api.cache.CacheIndex;
import net.runelite.http.service.util.exception.NotFoundException;
import net.runelite.http.service.cache.beans.ArchiveEntry; import net.runelite.http.service.cache.beans.ArchiveEntry;
import net.runelite.http.service.cache.beans.CacheEntry; import net.runelite.http.service.cache.beans.CacheEntry;
import net.runelite.http.service.cache.beans.FileEntry; import net.runelite.http.service.cache.beans.FileEntry;
import net.runelite.http.service.cache.beans.IndexEntry; import net.runelite.http.service.cache.beans.IndexEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.sql2o.Connection; import org.sql2o.Connection;
import org.sql2o.ResultSetIterable; import org.sql2o.ResultSetIterable;
import org.sql2o.Sql2o; import org.sql2o.Sql2o;
import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserException;
@RestController @Service
@RequestMapping("/cache") @Slf4j
public class CacheService public class CacheService
{ {
private static final Logger logger = LoggerFactory.getLogger(CacheService.class);
@Autowired @Autowired
@Qualifier("Runelite Cache SQL2O") @Qualifier("Runelite Cache SQL2O")
private Sql2o sql2o; private Sql2o sql2o;
@@ -130,12 +113,12 @@ public class CacheService
| IOException | InvalidKeyException | NoResponseException | XmlPullParserException | IOException | InvalidKeyException | NoResponseException | XmlPullParserException
| ErrorResponseException | InternalException | InvalidArgumentException ex) | ErrorResponseException | InternalException | InvalidArgumentException ex)
{ {
logger.warn(null, ex); log.warn(null, ex);
return null; return null;
} }
} }
private ArchiveFiles getArchiveFiles(ArchiveEntry archiveEntry) throws IOException public ArchiveFiles getArchiveFiles(ArchiveEntry archiveEntry) throws IOException
{ {
CacheDAO cacheDao = new CacheDAO(); CacheDAO cacheDao = new CacheDAO();
@@ -169,228 +152,78 @@ public class CacheService
} }
} }
@RequestMapping("/") public List<CacheEntry> listCaches()
public List<Cache> listCaches()
{ {
List<CacheEntry> caches;
try (Connection con = sql2o.open()) try (Connection con = sql2o.open())
{ {
CacheDAO cacheDao = new CacheDAO(); CacheDAO cacheDao = new CacheDAO();
caches = cacheDao.listCaches(con); return cacheDao.listCaches(con);
} }
return caches.stream()
.map(entry -> new Cache(entry.getId(), entry.getRevision(), entry.getDate()))
.collect(Collectors.toList());
} }
@RequestMapping("{cacheId}") public CacheEntry findCache(int cacheId)
public List<CacheIndex> listIndexes(@PathVariable int cacheId)
{ {
List<IndexEntry> indexes;
try (Connection con = sql2o.open()) try (Connection con = sql2o.open())
{ {
CacheDAO cacheDao = new CacheDAO(); CacheDAO cacheDao = new CacheDAO();
CacheEntry cacheEntry = cacheDao.findCache(con, cacheId); return cacheDao.findCache(con, cacheId);
if (cacheEntry == null)
{
throw new NotFoundException();
}
indexes = cacheDao.findIndexesForCache(con, cacheEntry);
} }
return indexes.stream()
.map(entry -> new CacheIndex(entry.getIndexId(), entry.getRevision()))
.collect(Collectors.toList());
} }
@RequestMapping("{cacheId}/{indexId}") public CacheEntry findMostRecent()
public List<CacheArchive> listArchives(@PathVariable int cacheId,
@PathVariable int indexId)
{ {
List<ArchiveEntry> archives = new ArrayList<>();
try (Connection con = sql2o.open()) try (Connection con = sql2o.open())
{ {
CacheDAO cacheDao = new CacheDAO(); CacheDAO cacheDao = new CacheDAO();
CacheEntry cacheEntry = cacheDao.findCache(con, cacheId); return cacheDao.findMostRecent(con);
if (cacheEntry == null)
{
throw new NotFoundException();
}
IndexEntry indexEntry = cacheDao.findIndexForCache(con, cacheEntry, indexId);
if (indexEntry == null)
{
throw new NotFoundException();
}
try (ResultSetIterable<ArchiveEntry> archiveEntries = cacheDao.findArchivesForIndex(con, indexEntry))
{
Iterables.addAll(archives, archiveEntries);
}
} }
return archives.stream()
.map(archive -> new CacheArchive(archive.getArchiveId(), archive.getNameHash(), archive.getRevision()))
.collect(Collectors.toList());
} }
@RequestMapping("{cacheId}/{indexId}/{archiveId}") public List<IndexEntry> findIndexesForCache(CacheEntry cacheEntry)
public CacheArchive getCacheArchive(@PathVariable int cacheId,
@PathVariable int indexId,
@PathVariable int archiveId)
{ {
ArchiveEntry archiveEntry;
try (Connection con = sql2o.open()) try (Connection con = sql2o.open())
{ {
CacheDAO cacheDao = new CacheDAO(); CacheDAO cacheDao = new CacheDAO();
CacheEntry cacheEntry = cacheDao.findCache(con, cacheId); return cacheDao.findIndexesForCache(con, cacheEntry);
if (cacheEntry == null)
{
throw new NotFoundException();
}
IndexEntry indexEntry = cacheDao.findIndexForCache(con, cacheEntry, indexId);
if (indexEntry == null)
{
throw new NotFoundException();
}
archiveEntry = cacheDao.findArchiveForIndex(con, indexEntry, archiveId);
} }
return new CacheArchive(archiveEntry.getArchiveId(),
archiveEntry.getNameHash(), archiveEntry.getRevision());
} }
@RequestMapping("{cacheId}/{indexId}/{archiveId}/data") public IndexEntry findIndexForCache(CacheEntry cahceEntry, int indexId)
public byte[] getArchiveData(
@PathVariable int cacheId,
@PathVariable int indexId,
@PathVariable int archiveId
)
{ {
ArchiveEntry archiveEntry;
try (Connection con = sql2o.open()) try (Connection con = sql2o.open())
{ {
CacheDAO cacheDao = new CacheDAO(); CacheDAO cacheDao = new CacheDAO();
CacheEntry cacheEntry = cacheDao.findCache(con, cacheId); return cacheDao.findIndexForCache(con, cahceEntry, indexId);
if (cacheEntry == null)
{
throw new NotFoundException();
}
IndexEntry indexEntry = cacheDao.findIndexForCache(con, cacheEntry, indexId);
if (indexEntry == null)
{
throw new NotFoundException();
}
archiveEntry = cacheDao.findArchiveForIndex(con, indexEntry, archiveId);
} }
return getArchive(archiveEntry);
} }
@RequestMapping("item/{itemId}") public List<ArchiveEntry> findArchivesForIndex(IndexEntry indexEntry)
public ItemDefinition getItem(@PathVariable int itemId) throws IOException
{ {
ArchiveEntry archiveEntry;
try (Connection con = sql2o.open()) try (Connection con = sql2o.open())
{ {
CacheDAO cacheDao = new CacheDAO(); CacheDAO cacheDao = new CacheDAO();
ResultSetIterable<ArchiveEntry> archiveEntries = cacheDao.findArchivesForIndex(con, indexEntry);
CacheEntry cache = cacheDao.findMostRecent(con); List<ArchiveEntry> archives = new ArrayList<>();
archiveEntry = cacheDao.findArchiveById(con, cache, IndexType.CONFIGS, ConfigType.ITEM.getId()); Iterables.addAll(archives, archiveEntries);
if (archiveEntry == null) return archives;
{
throw new NotFoundException();
}
} }
ArchiveFiles archiveFiles = getArchiveFiles(archiveEntry);
if (archiveFiles == null)
{
throw new NotFoundException();
}
FSFile file = archiveFiles.findFile(itemId);
if (file == null)
{
throw new NotFoundException();
}
ItemDefinition itemdef = new ItemLoader().load(itemId, file.getContents());
return itemdef;
} }
@RequestMapping("object/{objectId}") public ArchiveEntry findArchiveForIndex(IndexEntry indexEntry, int archiveId)
public ObjectDefinition getObject(
@PathVariable int objectId
) throws IOException
{ {
ArchiveEntry archiveEntry;
try (Connection con = sql2o.open()) try (Connection con = sql2o.open())
{ {
CacheDAO cacheDao = new CacheDAO(); CacheDAO cacheDao = new CacheDAO();
return cacheDao.findArchiveForIndex(con, indexEntry, archiveId);
CacheEntry cache = cacheDao.findMostRecent(con);
archiveEntry = cacheDao.findArchiveById(con, cache, IndexType.CONFIGS, ConfigType.OBJECT.getId());
if (archiveEntry == null)
{
throw new NotFoundException();
}
} }
ArchiveFiles archiveFiles = getArchiveFiles(archiveEntry);
if (archiveFiles == null)
{
throw new NotFoundException();
}
FSFile file = archiveFiles.findFile(objectId);
if (file == null)
{
throw new NotFoundException();
}
ObjectDefinition objectdef = new ObjectLoader().load(objectId, file.getContents());
return objectdef;
} }
@RequestMapping("npc/{npcId}") public ArchiveEntry findArchiveForTypeAndName(CacheEntry cache, IndexType index, int nameHash)
public NpcDefinition getNpc(
@PathVariable int npcId
) throws IOException
{ {
ArchiveEntry archiveEntry;
try (Connection con = sql2o.open()) try (Connection con = sql2o.open())
{ {
CacheDAO cacheDao = new CacheDAO(); CacheDAO cacheDao = new CacheDAO();
return cacheDao.findArchiveByName(con, cache, index, nameHash);
CacheEntry cache = cacheDao.findMostRecent(con);
archiveEntry = cacheDao.findArchiveById(con, cache, IndexType.CONFIGS, ConfigType.NPC.getId());
if (archiveEntry == null)
{
throw new NotFoundException();
}
} }
ArchiveFiles archiveFiles = getArchiveFiles(archiveEntry);
if (archiveFiles == null)
{
throw new NotFoundException();
}
FSFile file = archiveFiles.findFile(npcId);
if (file == null)
{
throw new NotFoundException();
}
NpcDefinition npcdef = new NpcLoader().load(npcId, file.getContents());
return npcdef;
} }
} }

View File

@@ -32,7 +32,6 @@ import net.runelite.cache.fs.Container;
import net.runelite.cache.util.Djb2; import net.runelite.cache.util.Djb2;
import net.runelite.http.api.xtea.XteaKey; import net.runelite.http.api.xtea.XteaKey;
import net.runelite.http.api.xtea.XteaRequest; import net.runelite.http.api.xtea.XteaRequest;
import net.runelite.http.service.cache.CacheDAO;
import net.runelite.http.service.cache.CacheService; import net.runelite.http.service.cache.CacheService;
import net.runelite.http.service.cache.beans.ArchiveEntry; import net.runelite.http.service.cache.beans.ArchiveEntry;
import net.runelite.http.service.cache.beans.CacheEntry; import net.runelite.http.service.cache.beans.CacheEntry;
@@ -71,18 +70,15 @@ public class XteaService
+ ") ENGINE=InnoDB"; + ") ENGINE=InnoDB";
private final Sql2o sql2o; private final Sql2o sql2o;
private final Sql2o cacheSql2o;
private final CacheService cacheService; private final CacheService cacheService;
@Autowired @Autowired
public XteaService( public XteaService(
@Qualifier("Runelite SQL2O") Sql2o sql2o, @Qualifier("Runelite SQL2O") Sql2o sql2o,
@Qualifier("Runelite Cache SQL2O") Sql2o cacheSql2o,
CacheService cacheService CacheService cacheService
) )
{ {
this.sql2o = sql2o; this.sql2o = sql2o;
this.cacheSql2o = cacheSql2o;
this.cacheService = cacheService; this.cacheService = cacheService;
try (Connection con = sql2o.beginTransaction()) try (Connection con = sql2o.beginTransaction())
@@ -105,11 +101,9 @@ public class XteaService
@RequestMapping(method = POST) @RequestMapping(method = POST)
public void submit(@RequestBody XteaRequest xteaRequest) public void submit(@RequestBody XteaRequest xteaRequest)
{ {
try (Connection con = sql2o.beginTransaction(); try (Connection con = sql2o.beginTransaction())
Connection cacheCon = cacheSql2o.open())
{ {
CacheDAO cacheDao = new CacheDAO(); CacheEntry cache = cacheService.findMostRecent();
CacheEntry cache = cacheDao.findMostRecent(cacheCon);
if (cache == null) if (cache == null)
{ {
@@ -141,7 +135,7 @@ public class XteaService
continue; continue;
} }
if (!checkKeys(cacheCon, cache, region, keys)) if (!checkKeys(cache, region, keys))
{ {
continue; continue;
} }
@@ -198,7 +192,7 @@ public class XteaService
return entryToKey(entry); return entryToKey(entry);
} }
private boolean checkKeys(Connection con, CacheEntry cache, int regionId, int[] keys) private boolean checkKeys(CacheEntry cache, int regionId, int[] keys)
{ {
int x = regionId >>> 8; int x = regionId >>> 8;
int y = regionId & 0xFF; int y = regionId & 0xFF;
@@ -211,8 +205,7 @@ public class XteaService
.toString(); .toString();
int archiveNameHash = Djb2.hash(archiveName); int archiveNameHash = Djb2.hash(archiveName);
CacheDAO cacheDao = new CacheDAO(); ArchiveEntry archiveEntry = cacheService.findArchiveForTypeAndName(cache, IndexType.MAPS, archiveNameHash);
ArchiveEntry archiveEntry = cacheDao.findArchiveByName(con, cache, IndexType.MAPS, archiveNameHash);
if (archiveEntry == null) if (archiveEntry == null)
{ {
throw new InternalServerErrorException("Unable to find archive for region"); throw new InternalServerErrorException("Unable to find archive for region");