class AsyncLockUtils {
	private static final TimedCache<String, LockObject> LOCK_MAP = CacheUtil.newTimedCache(Duration.ofDays(1).toMillis());

	public static synchronized LockObject getLock(String key) {
		return LOCK_MAP.get(key);
	}

	public static LockObject waitForLock(String key, Duration duration) throws TimeoutException {<FILL_FUNCTION_BODY>}

	public static class LockObject extends DomainObject {

		public LockObject(String id) {
			this.id = id;
		}
	}
}

		LockObject lockObject;
		synchronized (LOCK_MAP) {
			if (LOCK_MAP.containsKey(key)) {
				lockObject = LOCK_MAP.get(key);
			} else {
				lockObject = new LockObject(key);
				LOCK_MAP.put(key, lockObject);
			}
		}
		Future<?> future = ThreadUtil.execAsync(() -> {
			try {
				lockObject.sleep();
			} catch (InterruptedException e) {
				Thread.currentThread().interrupt();
			}
		});
		try {
			future.get(duration.toMillis(), TimeUnit.MILLISECONDS);
		} catch (InterruptedException e) {
			Thread.currentThread().interrupt();
		} catch (ExecutionException e) {
			// do nothing
		} catch (TimeoutException e) {
			future.cancel(true);
			throw new TimeoutException("Wait Timeout");
		} finally {
			LOCK_MAP.remove(lockObject.getId());
		}
		return lockObject;
	
<no_super_class>