or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-configuration.mdconfiguration.mdindex.mdmethod-locking.mdtask-scheduler.md

advanced-configuration.mddocs/

0

# Advanced Configuration

1

2

Extended configuration extraction and AOP infrastructure for advanced ShedLock use cases.

3

4

## ExtendedLockConfigurationExtractor

5

6

Extended interface for extracting lock configuration from method calls with parameter support.

7

8

```java { .api }

9

public interface ExtendedLockConfigurationExtractor extends LockConfigurationExtractor {

10

11

// Extract lock configuration for method with parameters

12

Optional<LockConfiguration> getLockConfiguration(

13

Object object,

14

Method method,

15

Object[] parameterValues

16

);

17

}

18

```

19

20

This interface extends the core `LockConfigurationExtractor` to provide Spring-specific method parameter handling.

21

22

## SpringLockConfigurationExtractor

23

24

The main implementation of lock configuration extraction with Spring-specific features.

25

26

```java { .api }

27

public class SpringLockConfigurationExtractor implements ExtendedLockConfigurationExtractor {

28

29

// Constructor without BeanFactory (limited functionality)

30

public SpringLockConfigurationExtractor(

31

Duration defaultLockAtMostFor,

32

Duration defaultLockAtLeastFor,

33

StringValueResolver embeddedValueResolver,

34

Converter<String, Duration> durationConverter

35

);

36

37

// Full constructor with BeanFactory support

38

public SpringLockConfigurationExtractor(

39

Duration defaultLockAtMostFor,

40

Duration defaultLockAtLeastFor,

41

StringValueResolver embeddedValueResolver,

42

Converter<String, Duration> durationConverter,

43

BeanFactory beanFactory

44

);

45

46

// Extract configuration from Runnable (for scheduler mode)

47

public Optional<LockConfiguration> getLockConfiguration(Runnable task);

48

49

// Extract configuration from method call with parameters

50

public Optional<LockConfiguration> getLockConfiguration(

51

Object target,

52

Method method,

53

Object[] parameterValues

54

);

55

56

// Duration calculation methods

57

Duration getLockAtMostFor(AnnotationData annotation);

58

Duration getLockAtLeastFor(AnnotationData annotation);

59

60

// Static annotation finding utilities

61

static AnnotationData findAnnotation(Object target, Method method);

62

static <A extends Annotation> A findAnnotation(Object target, Method method, Class<A> annotationType);

63

}

64

```

65

66

### Constructor Parameters

67

68

- **defaultLockAtMostFor**: Default maximum lock duration from `@EnableSchedulerLock`

69

- **defaultLockAtLeastFor**: Default minimum lock duration from `@EnableSchedulerLock`

70

- **embeddedValueResolver**: Spring's value resolver for property placeholders

71

- **durationConverter**: Converter for string duration formats

72

- **beanFactory**: Bean factory for SpEL expression evaluation (optional)

73

74

### Configuration Extraction Process

75

76

1. **Find annotations**: Searches for `@SchedulerLock` on method, then target class method

77

2. **Extract lock name**: Processes SpEL expressions if parameters are available

78

3. **Calculate durations**: Resolves lockAtMostFor and lockAtLeastFor with fallbacks

79

4. **Create configuration**: Returns `LockConfiguration` with current timestamp

80

81

### SpEL Expression Support

82

83

The extractor supports Spring Expression Language (SpEL) in lock names when method parameters are available:

84

85

```java

86

@SchedulerLock(name = "processUser-#{#userId}", lockAtMostFor = "10m")

87

public void processUser(Long userId) {

88

// Lock name becomes "processUser-123" when userId = 123

89

}

90

91

@SchedulerLock(name = "processOrder-#{#order.id}-#{#order.customerId}", lockAtMostFor = "5m")

92

public void processOrder(Order order) {

93

// Lock name becomes "processOrder-456-789"

94

}

95

```

96

97

### Property Placeholder Support

98

99

Lock names and durations can reference application properties:

100

101

```java

102

@SchedulerLock(name = "${app.scheduled.task.name}", lockAtMostFor = "${app.scheduled.task.timeout}")

103

public void configurableTask() {

104

// Values resolved from application.properties

105

}

106

```

107

108

## Annotation Processing

109

110

### AnnotationData Record

111

112

Internal record for holding parsed annotation data:

113

114

```java { .api }

115

record AnnotationData(

116

String name,

117

long lockAtMostFor,

118

String lockAtMostForString,

119

long lockAtLeastFor,

120

String lockAtLeastForString

121

) {}

122

```

123

124

### Annotation Finding Logic

125

126

The extractor uses sophisticated annotation finding that handles Spring AOP proxies:

127

128

1. **Direct method lookup**: Check the method directly for annotations

129

2. **Target class lookup**: If method is proxied, find the same method on the target class

130

3. **Merged annotations**: Uses Spring's `AnnotatedElementUtils.getMergedAnnotation()` for composed annotations

131

132

```java

133

// Static utility method

134

static <A extends Annotation> A findAnnotation(Object target, Method method, Class<A> annotationType) {

135

// First try the method directly

136

A annotation = AnnotatedElementUtils.getMergedAnnotation(method, annotationType);

137

if (annotation != null) {

138

return annotation;

139

}

140

141

// Try to find annotation on proxied class

142

Class<?> targetClass = AopUtils.getTargetClass(target);

143

try {

144

Method methodOnTarget = targetClass.getMethod(method.getName(), method.getParameterTypes());

145

return AnnotatedElementUtils.getMergedAnnotation(methodOnTarget, annotationType);

146

} catch (NoSuchMethodException e) {

147

return null;

148

}

149

}

150

```

151

152

## Parameter Name Discovery

153

154

For SpEL expression support, the extractor uses Spring's parameter name discovery:

155

156

```java { .api }

157

public static final PrioritizedParameterNameDiscoverer PARAMETER_NAME_DISCOVERER;

158

159

// Supports multiple discovery strategies:

160

// - KotlinReflectionParameterNameDiscoverer (if Kotlin is present)

161

// - SimpleParameterNameDiscoverer (custom implementation)

162

```

163

164

### Custom Parameter Name Discoverer

165

166

```java { .api }

167

private static class SimpleParameterNameDiscoverer implements ParameterNameDiscoverer {

168

public String[] getParameterNames(Method method);

169

public String[] getParameterNames(Constructor<?> ctor);

170

}

171

```

172

173

This custom discoverer avoids calling `hasRealParameterData()` which can cause issues in certain test scenarios.

174

175

## Evaluation Context Creation

176

177

For SpEL expression evaluation, the extractor creates Spring evaluation contexts:

178

179

```java

180

private Optional<EvaluationContext> getEvaluationContext(Method method, Object[] parameterValues) {

181

if (method.getParameters().length > 0 && method.getParameters().length == parameterValues.length) {

182

StandardEvaluationContext evaluationContext = new MethodBasedEvaluationContext(

183

beanFactory, method, parameterValues, PARAMETER_NAME_DISCOVERER

184

);

185

originalEvaluationContext.applyDelegatesTo(evaluationContext);

186

return Optional.of(evaluationContext);

187

}

188

return Optional.empty();

189

}

190

```

191

192

The evaluation context includes:

193

- **Method parameters**: Available as SpEL variables (e.g., `#userId`)

194

- **Bean factory access**: Can reference Spring beans in expressions

195

- **Property resolution**: Access to application properties

196

197

## Duration Resolution

198

199

### Flexible Duration Input

200

201

The extractor supports multiple duration input formats with fallback logic:

202

203

```java

204

private Duration getValue(

205

long valueFromAnnotation, // Numeric milliseconds (-1 if not set)

206

String stringValueFromAnnotation, // String duration ("" if not set)

207

Duration defaultValue, // Default from @EnableSchedulerLock

208

String paramName // For error messages

209

) {

210

// Priority 1: Numeric value (if >= 0)

211

if (valueFromAnnotation >= 0) {

212

return Duration.of(valueFromAnnotation, MILLIS);

213

}

214

215

// Priority 2: String value (if not empty)

216

if (StringUtils.hasText(stringValueFromAnnotation)) {

217

// Resolve properties

218

if (embeddedValueResolver != null) {

219

stringValueFromAnnotation = embeddedValueResolver.resolveStringValue(stringValueFromAnnotation);

220

}

221

// Convert string to duration

222

Duration result = durationConverter.convert(stringValueFromAnnotation);

223

if (result.isNegative()) {

224

throw new IllegalArgumentException("Invalid " + paramName + " value - cannot set negative duration");

225

}

226

return result;

227

}

228

229

// Priority 3: Default value

230

return defaultValue;

231

}

232

```

233

234

### Error Handling

235

236

Invalid duration formats throw descriptive `IllegalArgumentException`:

237

238

```java

239

// Examples of error messages:

240

"Invalid lockAtMostForString value \"invalid\" - cannot parse into long nor duration"

241

"Invalid lockAtLeastForString value \"-5s\" - cannot set negative duration"

242

```

243

244

## Advanced Usage Examples

245

246

### Complex SpEL Expressions

247

248

```java

249

@Component

250

public class AdvancedScheduledTasks {

251

252

@SchedulerLock(

253

name = "user-#{#user.department}-#{#user.id}",

254

lockAtMostFor = "#{#user.department == 'VIP' ? '30m' : '10m'}"

255

)

256

public void processUser(User user) {

257

// Lock name: "user-Engineering-123"

258

// Duration: 30 minutes for VIP department, 10 minutes otherwise

259

}

260

261

@SchedulerLock(

262

name = "batch-#{T(java.time.LocalDate).now()}-#{#batchSize}",

263

lockAtMostFor = "PT#{#batchSize / 100}M" // 1 minute per 100 items

264

)

265

public void processBatch(int batchSize) {

266

// Lock name: "batch-2023-12-01-1000"

267

// Duration: 10 minutes for 1000 items

268

}

269

}

270

```

271

272

### Property-Driven Configuration

273

274

```yaml

275

# application.yml

276

app:

277

locking:

278

user-processing:

279

name: "user-processor-${spring.application.name}"

280

timeout: "PT15M"

281

batch-processing:

282

timeout: "PT1H"

283

```

284

285

```java

286

@SchedulerLock(

287

name = "${app.locking.user-processing.name}",

288

lockAtMostFor = "${app.locking.user-processing.timeout}"

289

)

290

public void processUsers() {

291

// Configuration externalized to properties

292

}

293

```

294

295

### Custom Configuration Extractor

296

297

For advanced use cases, you can provide your own extractor:

298

299

```java

300

@Configuration

301

public class CustomLockConfig {

302

303

@Bean

304

@Primary

305

public ExtendedLockConfigurationExtractor customLockConfigurationExtractor() {

306

return new CustomSpringLockConfigurationExtractor(

307

Duration.ofMinutes(30), // default lockAtMostFor

308

Duration.ZERO, // default lockAtLeastFor

309

embeddedValueResolver,

310

StringToDurationConverter.INSTANCE,

311

beanFactory

312

);

313

}

314

}

315

```