feat: トランザクションエラーがある程度発生しないように

This commit is contained in:
usbharu 2023-08-11 15:47:25 +09:00
parent ee8bbf5091
commit 37db201f87
13 changed files with 119 additions and 64 deletions

View File

@ -20,6 +20,7 @@ import dev.usbharu.hideout.service.activitypub.ActivityPubUserService
import dev.usbharu.hideout.service.api.IPostApiService import dev.usbharu.hideout.service.api.IPostApiService
import dev.usbharu.hideout.service.api.IUserApiService import dev.usbharu.hideout.service.api.IUserApiService
import dev.usbharu.hideout.service.api.UserAuthApiService import dev.usbharu.hideout.service.api.UserAuthApiService
import dev.usbharu.hideout.service.api.WebFingerApiService
import dev.usbharu.hideout.service.auth.HttpSignatureVerifyService import dev.usbharu.hideout.service.auth.HttpSignatureVerifyService
import dev.usbharu.hideout.service.core.* import dev.usbharu.hideout.service.core.*
import dev.usbharu.hideout.service.job.JobQueueParentService import dev.usbharu.hideout.service.job.JobQueueParentService
@ -75,7 +76,7 @@ fun Application.parent() {
level = LogLevel.INFO level = LogLevel.INFO
} }
install(httpSignaturePlugin) { install(httpSignaturePlugin) {
keyMap = KtorKeyMap(get()) keyMap = KtorKeyMap(get(), get())
} }
expectSuccess = true expectSuccess = true
} }
@ -116,6 +117,7 @@ fun Application.parent() {
userQueryService = inject<UserQueryService>().value, userQueryService = inject<UserQueryService>().value,
followerQueryService = inject<FollowerQueryService>().value, followerQueryService = inject<FollowerQueryService>().value,
userAuthApiService = inject<UserAuthApiService>().value, userAuthApiService = inject<UserAuthApiService>().value,
webFingerApiService = inject<WebFingerApiService>().value,
transaction = inject<Transaction>().value transaction = inject<Transaction>().value
) )
} }

View File

@ -3,6 +3,7 @@ package dev.usbharu.hideout.plugins
import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.config.Config
import dev.usbharu.hideout.domain.model.ap.JsonLd import dev.usbharu.hideout.domain.model.ap.JsonLd
import dev.usbharu.hideout.query.UserQueryService import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.core.Transaction
import dev.usbharu.hideout.service.user.UserAuthService import dev.usbharu.hideout.service.user.UserAuthService
import dev.usbharu.hideout.util.HttpUtil.Activity import dev.usbharu.hideout.util.HttpUtil.Activity
import io.ktor.client.* import io.ktor.client.*
@ -164,19 +165,21 @@ val httpSignaturePlugin = createClientPlugin("HttpSign", ::HttpSignaturePluginCo
} }
} }
class KtorKeyMap(private val userQueryService: UserQueryService) : KeyMap { class KtorKeyMap(private val userQueryService: UserQueryService, private val transaction: Transaction) : KeyMap {
override fun getPublicKey(keyId: String?): PublicKey = runBlocking { override fun getPublicKey(keyId: String?): PublicKey = runBlocking {
val username = (keyId ?: throw IllegalArgumentException("keyId is null")).substringBeforeLast("#pubkey") val username = (keyId ?: throw IllegalArgumentException("keyId is null")).substringBeforeLast("#pubkey")
.substringAfterLast("/") .substringAfterLast("/")
val publicBytes = Base64.getDecoder().decode( val publicBytes = Base64.getDecoder().decode(
userQueryService.findByNameAndDomain( transaction.transaction {
username, userQueryService.findByNameAndDomain(
Config.configData.domain username,
).run { Config.configData.domain
publicKey ).run {
.replace("-----BEGIN PUBLIC KEY-----", "") publicKey
.replace("-----END PUBLIC KEY-----", "") .replace("-----BEGIN PUBLIC KEY-----", "")
.replace("\n", "") .replace("-----END PUBLIC KEY-----", "")
.replace("\n", "")
}
} }
) )
val x509EncodedKeySpec = X509EncodedKeySpec(publicBytes) val x509EncodedKeySpec = X509EncodedKeySpec(publicBytes)
@ -187,13 +190,15 @@ class KtorKeyMap(private val userQueryService: UserQueryService) : KeyMap {
val username = (keyId ?: throw IllegalArgumentException("keyId is null")).substringBeforeLast("#pubkey") val username = (keyId ?: throw IllegalArgumentException("keyId is null")).substringBeforeLast("#pubkey")
.substringAfterLast("/") .substringAfterLast("/")
val publicBytes = Base64.getDecoder().decode( val publicBytes = Base64.getDecoder().decode(
userQueryService.findByNameAndDomain( transaction.transaction {
username, userQueryService.findByNameAndDomain(
Config.configData.domain username,
).privateKey?.run { Config.configData.domain
replace("-----BEGIN PRIVATE KEY-----", "") ).privateKey?.run {
.replace("-----END PRIVATE KEY-----", "") replace("-----BEGIN PRIVATE KEY-----", "")
.replace("\n", "") .replace("-----END PRIVATE KEY-----", "")
.replace("\n", "")
}
} }
) )
val x509EncodedKeySpec = PKCS8EncodedKeySpec(publicBytes) val x509EncodedKeySpec = PKCS8EncodedKeySpec(publicBytes)

View File

@ -14,6 +14,7 @@ import dev.usbharu.hideout.service.activitypub.ActivityPubUserService
import dev.usbharu.hideout.service.api.IPostApiService import dev.usbharu.hideout.service.api.IPostApiService
import dev.usbharu.hideout.service.api.IUserApiService import dev.usbharu.hideout.service.api.IUserApiService
import dev.usbharu.hideout.service.api.UserAuthApiService import dev.usbharu.hideout.service.api.UserAuthApiService
import dev.usbharu.hideout.service.api.WebFingerApiService
import dev.usbharu.hideout.service.auth.HttpSignatureVerifyService import dev.usbharu.hideout.service.auth.HttpSignatureVerifyService
import dev.usbharu.hideout.service.core.Transaction import dev.usbharu.hideout.service.core.Transaction
import dev.usbharu.hideout.service.user.IUserService import dev.usbharu.hideout.service.user.IUserService
@ -32,6 +33,7 @@ fun Application.configureRouting(
userQueryService: UserQueryService, userQueryService: UserQueryService,
followerQueryService: FollowerQueryService, followerQueryService: FollowerQueryService,
userAuthApiService: UserAuthApiService, userAuthApiService: UserAuthApiService,
webFingerApiService: WebFingerApiService,
transaction: Transaction transaction: Transaction
) { ) {
install(AutoHeadResponse) install(AutoHeadResponse)
@ -39,7 +41,7 @@ fun Application.configureRouting(
inbox(httpSignatureVerifyService, activityPubService) inbox(httpSignatureVerifyService, activityPubService)
outbox() outbox()
usersAP(activityPubUserService, userQueryService, followerQueryService, transaction) usersAP(activityPubUserService, userQueryService, followerQueryService, transaction)
webfinger(userQueryService) webfinger(webFingerApiService)
route("/api/internal/v1") { route("/api/internal/v1") {
posts(postService) posts(postService)
users(userService, userApiService) users(userService, userApiService)

View File

@ -4,14 +4,14 @@ import dev.usbharu.hideout.config.Config
import dev.usbharu.hideout.domain.model.wellknown.WebFinger import dev.usbharu.hideout.domain.model.wellknown.WebFinger
import dev.usbharu.hideout.exception.IllegalParameterException import dev.usbharu.hideout.exception.IllegalParameterException
import dev.usbharu.hideout.exception.ParameterNotExistException import dev.usbharu.hideout.exception.ParameterNotExistException
import dev.usbharu.hideout.query.UserQueryService import dev.usbharu.hideout.service.api.WebFingerApiService
import dev.usbharu.hideout.util.HttpUtil.Activity import dev.usbharu.hideout.util.HttpUtil.Activity
import io.ktor.http.* import io.ktor.http.*
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.response.* import io.ktor.server.response.*
import io.ktor.server.routing.* import io.ktor.server.routing.*
fun Routing.webfinger(userQueryService: UserQueryService) { fun Routing.webfinger(webFingerApiService: WebFingerApiService) {
route("/.well-known/webfinger") { route("/.well-known/webfinger") {
get { get {
val acct = call.request.queryParameters["resource"]?.decodeURLPart() val acct = call.request.queryParameters["resource"]?.decodeURLPart()
@ -25,7 +25,7 @@ fun Routing.webfinger(userQueryService: UserQueryService) {
.substringAfter("acct:") .substringAfter("acct:")
.trimStart('@') .trimStart('@')
val userEntity = userQueryService.findByNameAndDomain(accountName, Config.configData.domain) val userEntity = webFingerApiService.findByNameAndDomain(accountName, Config.configData.domain)
val webFinger = WebFinger( val webFinger = WebFinger(
subject = acct, subject = acct,

View File

@ -5,12 +5,14 @@ import dev.usbharu.hideout.domain.model.ActivityPubStringResponse
import dev.usbharu.hideout.domain.model.ap.Create import dev.usbharu.hideout.domain.model.ap.Create
import dev.usbharu.hideout.domain.model.ap.Note import dev.usbharu.hideout.domain.model.ap.Note
import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException
import dev.usbharu.hideout.service.core.Transaction
import io.ktor.http.* import io.ktor.http.*
import org.koin.core.annotation.Single import org.koin.core.annotation.Single
@Single @Single
class ActivityPubCreateServiceImpl( class ActivityPubCreateServiceImpl(
private val activityPubNoteService: ActivityPubNoteService private val activityPubNoteService: ActivityPubNoteService,
private val transaction: Transaction
) : ActivityPubCreateService { ) : ActivityPubCreateService {
override suspend fun receiveCreate(create: Create): ActivityPubResponse { override suspend fun receiveCreate(create: Create): ActivityPubResponse {
val value = create.`object` ?: throw IllegalActivityPubObjectException("object is null") val value = create.`object` ?: throw IllegalActivityPubObjectException("object is null")
@ -18,8 +20,10 @@ class ActivityPubCreateServiceImpl(
throw IllegalActivityPubObjectException("object is not Note") throw IllegalActivityPubObjectException("object is not Note")
} }
val note = value as Note return transaction.transaction {
activityPubNoteService.fetchNote(note) val note = value as Note
return ActivityPubStringResponse(HttpStatusCode.OK, "Created") activityPubNoteService.fetchNote(note)
ActivityPubStringResponse(HttpStatusCode.OK, "Created")
}
} }
} }

View File

@ -6,6 +6,7 @@ import dev.usbharu.hideout.domain.model.ap.Like
import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException
import dev.usbharu.hideout.query.PostQueryService import dev.usbharu.hideout.query.PostQueryService
import dev.usbharu.hideout.query.UserQueryService import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.core.Transaction
import dev.usbharu.hideout.service.reaction.IReactionService import dev.usbharu.hideout.service.reaction.IReactionService
import io.ktor.http.* import io.ktor.http.*
import org.koin.core.annotation.Single import org.koin.core.annotation.Single
@ -16,23 +17,26 @@ class ActivityPubLikeServiceImpl(
private val activityPubUserService: ActivityPubUserService, private val activityPubUserService: ActivityPubUserService,
private val activityPubNoteService: ActivityPubNoteService, private val activityPubNoteService: ActivityPubNoteService,
private val userQueryService: UserQueryService, private val userQueryService: UserQueryService,
private val postQueryService: PostQueryService private val postQueryService: PostQueryService,
private val transaction: Transaction
) : ActivityPubLikeService { ) : ActivityPubLikeService {
override suspend fun receiveLike(like: Like): ActivityPubResponse { override suspend fun receiveLike(like: Like): ActivityPubResponse {
val actor = like.actor ?: throw IllegalActivityPubObjectException("actor is null") val actor = like.actor ?: throw IllegalActivityPubObjectException("actor is null")
val content = like.content ?: throw IllegalActivityPubObjectException("content is null") val content = like.content ?: throw IllegalActivityPubObjectException("content is null")
like.`object` ?: throw IllegalActivityPubObjectException("object is null") like.`object` ?: throw IllegalActivityPubObjectException("object is null")
val person = activityPubUserService.fetchPerson(actor) transaction.transaction {
activityPubNoteService.fetchNote(like.`object`!!) val person = activityPubUserService.fetchPerson(actor)
activityPubNoteService.fetchNote(like.`object`!!)
val user = userQueryService.findByUrl( val user = userQueryService.findByUrl(
person.url person.url
?: throw IllegalActivityPubObjectException("actor is not found") ?: throw IllegalActivityPubObjectException("actor is not found")
) )
val post = postQueryService.findByUrl(like.`object`!!) val post = postQueryService.findByUrl(like.`object`!!)
reactionService.receiveReaction(content, actor.substringAfter("://").substringBefore("/"), user.id, post.id) reactionService.receiveReaction(content, actor.substringAfter("://").substringBefore("/"), user.id, post.id)
}
return ActivityPubStringResponse(HttpStatusCode.OK, "") return ActivityPubStringResponse(HttpStatusCode.OK, "")
} }
} }

View File

@ -9,6 +9,7 @@ import dev.usbharu.hideout.domain.model.ap.Follow
import dev.usbharu.hideout.domain.model.job.ReceiveFollowJob import dev.usbharu.hideout.domain.model.job.ReceiveFollowJob
import dev.usbharu.hideout.plugins.postAp import dev.usbharu.hideout.plugins.postAp
import dev.usbharu.hideout.query.UserQueryService import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.core.Transaction
import dev.usbharu.hideout.service.job.JobQueueParentService import dev.usbharu.hideout.service.job.JobQueueParentService
import dev.usbharu.hideout.service.user.IUserService import dev.usbharu.hideout.service.user.IUserService
import io.ktor.client.* import io.ktor.client.*
@ -22,7 +23,8 @@ class ActivityPubReceiveFollowServiceImpl(
private val activityPubUserService: ActivityPubUserService, private val activityPubUserService: ActivityPubUserService,
private val userService: IUserService, private val userService: IUserService,
private val httpClient: HttpClient, private val httpClient: HttpClient,
private val userQueryService: UserQueryService private val userQueryService: UserQueryService,
private val transaction: Transaction
) : ActivityPubReceiveFollowService { ) : ActivityPubReceiveFollowService {
override suspend fun receiveFollow(follow: Follow): ActivityPubResponse { override suspend fun receiveFollow(follow: Follow): ActivityPubResponse {
// TODO: Verify HTTP Signature // TODO: Verify HTTP Signature
@ -35,24 +37,26 @@ class ActivityPubReceiveFollowServiceImpl(
} }
override suspend fun receiveFollowJob(props: JobProps<ReceiveFollowJob>) { override suspend fun receiveFollowJob(props: JobProps<ReceiveFollowJob>) {
val actor = props[ReceiveFollowJob.actor] transaction.transaction {
val targetActor = props[ReceiveFollowJob.targetActor] val actor = props[ReceiveFollowJob.actor]
val person = activityPubUserService.fetchPerson(actor, targetActor) val targetActor = props[ReceiveFollowJob.targetActor]
val follow = Config.configData.objectMapper.readValue<Follow>(props[ReceiveFollowJob.follow]) val person = activityPubUserService.fetchPerson(actor, targetActor)
httpClient.postAp( val follow = Config.configData.objectMapper.readValue<Follow>(props[ReceiveFollowJob.follow])
urlString = person.inbox ?: throw IllegalArgumentException("inbox is not found"), httpClient.postAp(
username = "$targetActor#pubkey", urlString = person.inbox ?: throw IllegalArgumentException("inbox is not found"),
jsonLd = Accept( username = "$targetActor#pubkey",
name = "Follow", jsonLd = Accept(
`object` = follow, name = "Follow",
actor = targetActor `object` = follow,
actor = targetActor
)
) )
)
val targetEntity = userQueryService.findByUrl(targetActor) val targetEntity = userQueryService.findByUrl(targetActor)
val followActorEntity = val followActorEntity =
userQueryService.findByUrl(follow.actor ?: throw java.lang.IllegalArgumentException("Actor is null")) userQueryService.findByUrl(follow.actor ?: throw java.lang.IllegalArgumentException("Actor is null"))
userService.followRequest(targetEntity.id, followActorEntity.id) userService.followRequest(targetEntity.id, followActorEntity.id)
}
} }
} }

View File

@ -5,6 +5,7 @@ import dev.usbharu.hideout.domain.model.ActivityPubStringResponse
import dev.usbharu.hideout.domain.model.ap.Follow import dev.usbharu.hideout.domain.model.ap.Follow
import dev.usbharu.hideout.domain.model.ap.Undo import dev.usbharu.hideout.domain.model.ap.Undo
import dev.usbharu.hideout.query.UserQueryService import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.core.Transaction
import dev.usbharu.hideout.service.user.IUserService import dev.usbharu.hideout.service.user.IUserService
import io.ktor.http.* import io.ktor.http.*
import org.koin.core.annotation.Single import org.koin.core.annotation.Single
@ -14,7 +15,8 @@ import org.koin.core.annotation.Single
class ActivityPubUndoServiceImpl( class ActivityPubUndoServiceImpl(
private val userService: IUserService, private val userService: IUserService,
private val activityPubUserService: ActivityPubUserService, private val activityPubUserService: ActivityPubUserService,
private val userQueryService: UserQueryService private val userQueryService: UserQueryService,
private val transaction: Transaction
) : ActivityPubUndoService { ) : ActivityPubUndoService {
override suspend fun receiveUndo(undo: Undo): ActivityPubResponse { override suspend fun receiveUndo(undo: Undo): ActivityPubResponse {
if (undo.actor == null) { if (undo.actor == null) {
@ -33,11 +35,12 @@ class ActivityPubUndoServiceImpl(
if (follow.`object` == null) { if (follow.`object` == null) {
return ActivityPubStringResponse(HttpStatusCode.BadRequest, "object.object is null") return ActivityPubStringResponse(HttpStatusCode.BadRequest, "object.object is null")
} }
transaction.transaction {
activityPubUserService.fetchPerson(undo.actor!!, follow.`object`) activityPubUserService.fetchPerson(undo.actor!!, follow.`object`)
val follower = userQueryService.findByUrl(undo.actor!!) val follower = userQueryService.findByUrl(undo.actor!!)
val target = userQueryService.findByUrl(follow.`object`!!) val target = userQueryService.findByUrl(follow.`object`!!)
userService.unfollow(target.id, follower.id) userService.unfollow(target.id, follower.id)
}
} }
else -> {} else -> {}

View File

@ -6,10 +6,10 @@ import dev.usbharu.hideout.domain.model.ap.Image
import dev.usbharu.hideout.domain.model.ap.Key import dev.usbharu.hideout.domain.model.ap.Key
import dev.usbharu.hideout.domain.model.ap.Person import dev.usbharu.hideout.domain.model.ap.Person
import dev.usbharu.hideout.domain.model.hideout.dto.RemoteUserCreateDto import dev.usbharu.hideout.domain.model.hideout.dto.RemoteUserCreateDto
import dev.usbharu.hideout.exception.UserNotFoundException
import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException
import dev.usbharu.hideout.plugins.getAp import dev.usbharu.hideout.plugins.getAp
import dev.usbharu.hideout.query.UserQueryService import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.core.Transaction
import dev.usbharu.hideout.service.user.IUserService import dev.usbharu.hideout.service.user.IUserService
import dev.usbharu.hideout.util.HttpUtil.Activity import dev.usbharu.hideout.util.HttpUtil.Activity
import io.ktor.client.* import io.ktor.client.*
@ -22,13 +22,16 @@ import org.koin.core.annotation.Single
class ActivityPubUserServiceImpl( class ActivityPubUserServiceImpl(
private val userService: IUserService, private val userService: IUserService,
private val httpClient: HttpClient, private val httpClient: HttpClient,
private val userQueryService: UserQueryService private val userQueryService: UserQueryService,
private val transaction: Transaction
) : ) :
ActivityPubUserService { ActivityPubUserService {
override suspend fun getPersonByName(name: String): Person { override suspend fun getPersonByName(name: String): Person {
val userEntity = transaction.transaction {
userQueryService.findByNameAndDomain(name, Config.configData.domain)
}
// TODO: JOINで書き直し // TODO: JOINで書き直し
val userEntity = userQueryService.findByNameAndDomain(name, Config.configData.domain)
val userUrl = "${Config.configData.url}/users/$name" val userUrl = "${Config.configData.url}/users/$name"
return Person( return Person(
type = emptyList(), type = emptyList(),
@ -81,7 +84,7 @@ class ActivityPubUserServiceImpl(
publicKeyPem = userEntity.publicKey publicKeyPem = userEntity.publicKey
) )
) )
} catch (ignore: UserNotFoundException) { } catch (ignore: NoSuchElementException) {
val httpResponse = if (targetActor != null) { val httpResponse = if (targetActor != null) {
httpClient.getAp(url, "$targetActor#pubkey") httpClient.getAp(url, "$targetActor#pubkey")
} else { } else {
@ -108,5 +111,6 @@ class ActivityPubUserServiceImpl(
) )
person person
} }
} }
} }

View File

@ -0,0 +1,7 @@
package dev.usbharu.hideout.service.api
import dev.usbharu.hideout.domain.model.hideout.entity.User
interface WebFingerApiService {
suspend fun findByNameAndDomain(name: String, domain: String): User
}

View File

@ -0,0 +1,16 @@
package dev.usbharu.hideout.service.api
import dev.usbharu.hideout.domain.model.hideout.entity.User
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.core.Transaction
import org.koin.core.annotation.Single
@Single
class WebFingerApiServiceImpl(private val transaction: Transaction, private val userQueryService: UserQueryService) :
WebFingerApiService {
override suspend fun findByNameAndDomain(name: String, domain: String): User {
return transaction.transaction {
userQueryService.findByNameAndDomain(name, domain)
}
}
}

View File

@ -2,14 +2,18 @@ package dev.usbharu.hideout.service.auth
import dev.usbharu.hideout.plugins.KtorKeyMap import dev.usbharu.hideout.plugins.KtorKeyMap
import dev.usbharu.hideout.query.UserQueryService import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.core.Transaction
import io.ktor.http.* import io.ktor.http.*
import org.koin.core.annotation.Single import org.koin.core.annotation.Single
import tech.barbero.http.message.signing.SignatureHeaderVerifier import tech.barbero.http.message.signing.SignatureHeaderVerifier
@Single @Single
class HttpSignatureVerifyServiceImpl(private val userQueryService: UserQueryService) : HttpSignatureVerifyService { class HttpSignatureVerifyServiceImpl(
private val userQueryService: UserQueryService,
private val transaction: Transaction
) : HttpSignatureVerifyService {
override fun verify(headers: Headers): Boolean { override fun verify(headers: Headers): Boolean {
val build = SignatureHeaderVerifier.builder().keyMap(KtorKeyMap(userQueryService)).build() val build = SignatureHeaderVerifier.builder().keyMap(KtorKeyMap(userQueryService, transaction)).build()
return true return true
// build.verify(object : HttpMessage { // build.verify(object : HttpMessage {
// override fun headerValues(name: String?): MutableList<String> { // override fun headerValues(name: String?): MutableList<String> {

View File

@ -4,7 +4,7 @@
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> <pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder> </encoder>
</appender> </appender>
<root level="TRACE"> <root level="INFO">
<appender-ref ref="STDOUT"/> <appender-ref ref="STDOUT"/>
</root> </root>
<logger name="org.eclipse.jetty" level="INFO"/> <logger name="org.eclipse.jetty" level="INFO"/>