I am trying to write a sample in order to learn couchbase. I am trying to use it with spring boot and it’s crud repositories .
So I have downloaded latest docker image but the point is: i could not find the password of the bucket. The couchbase console allows only user creation but in spring, there is no equivalent of this usage like a username/password. It allows only bucketName and password which does not seem compatible with couchbase 5.
Am I missing anything here or is spring not compatible with couchbase 5? If spring is not compatible, which version of couchbase is ok?
Thx
Spring Data Couchbase is compatible with Couchbase Server 5.0. You can achieve the same auth as 4.x by creating a user with the same name as the bucket, then just use that bucket name and password from Spring Data if it's prior to 3.0/Kay.
The docs should cover this and if there's anything confusing there, please click the "feedback" button and offer what could be improved!
https://developer.couchbase.com/documentation/server/5.0/security/security-authorization.html
https://developer.couchbase.com/documentation/server/5.0/security/concepts-rba-for-apps.html
https://developer.couchbase.com/documentation/server/5.0/security/security-resources-under-access-control.html
I faced the same issue. I started debugging by getting into AbstractCouchbaseConfiguration and there i found
public abstract class AbstractCouchbaseConfiguration
extends AbstractCouchbaseDataConfiguration implements CouchbaseConfigurer {
....//some other configuration
#Override
#Bean(name = BeanNames.COUCHBASE_CLUSTER_INFO)
public ClusterInfo couchbaseClusterInfo() throws Exception {
return couchbaseCluster().clusterManager(getBucketName(), getBucketPassword()).info();
}
What i did is created a bucket with the same name as of my couchbase user.
couchbase username : userdetail
couchbase password : ******
bucket name : userdetail
Couchbase driver supports connection to Couchbase 5 buckets using username/password. Problem is that spring-data-couchbase is not developed fast enough to cover all the new features Couchbase introduces. So, we need to help Spring to use a new bucket connection, doing it by overriding Couchbase cluster instantiation method of spring-data-couchbase configuration base class - org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration. This is the method we are looking at :
#Override
#Bean(name = BeanNames.COUCHBASE_CLUSTER_INFO)
public ClusterInfo couchbaseClusterInfo() throws Exception {
return couchbaseCluster().clusterManager(getBucketName(), getBucketPassword()).info();
}
as we can see, it's not using username, just bucket and password, so in our configuration we will override it as following :
#Override
#Bean(name = BeanNames.COUCHBASE_CLUSTER_INFO)
public ClusterInfo couchbaseClusterInfo() throws Exception {
return couchbaseCluster().authenticate(couchbaseUsername, couchbasePassword).clusterManager().info();
}
that's it. Here is the full code of my spring-data-couchbase configuration :
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.cluster.ClusterInfo;
import com.couchbase.client.java.env.CouchbaseEnvironment;
import com.couchbase.client.java.env.DefaultCouchbaseEnvironment;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
import org.springframework.data.couchbase.config.BeanNames;
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
import javax.inject.Inject;
import java.security.KeyStore;
import java.util.List;
/**
* #author by avoinovan
*/
#Configuration
#EnableCouchbaseRepositories
public class ModelConfig extends AbstractCouchbaseConfiguration {
private final static int DEFAULT_HTTP_PORT = 8091;
private final static int DEFAULT_HTTP_SSL_PORT = 18091;
private final static int DEFAULT_CARRIER_PORT = 11210;
private final static int DEFAULT_CARRIER_SSL_PORT = 11207;
private final static long DEFAULT_KEEP_ALIVE_INTERVAL = 30000;
private final static int DEFAULT_SOCKET_CONNECT_TIMEOUT_MS = 5000;
private final static long DEFAULT_CONNECT_TIMEOUT_MS = 5000;
private final static long DEFAULT_MANAGEMENT_TIMEOUT_MS = 75000;
private final static long DEFAULT_DISCONNECT_TIMEOUT_MS = 25000;
private final static String PROPERTY_KEEP_ALIVE_INTERVAL_MS = "couchbase.keep_alive_interval_ms";
private final static String PROPERTY_SOCKET_CONNECT_TIMEOUT_MS = "couchbase.socket_connect_timeout_ms";
private final static String PROPERTY_CONNECT_TIMEOUT_MS = "couchbase.connect_timeout_ms";
private final static String PROPERTY_MANAGEMENT_TIMEOUT_MS = "couchbase.management_timeout_ms";
private final static String PROPERTY_DISCONNECT_TIMEOUT_MS = "couchbase.disconnect_timeout_ms";
private final static String PROPERTY_SSL_ENABLED = "couchbase.ssl.enabled";
private final static String PROPERTY_SSL_KEYSTORE_FILE = "couchbase.ssl.keystore.file";
private final static String PROPERTY_SSL_KEYSTORE_PASSWORD = "couchbase.ssl.keystore.password";
private final static String PROPERTY_SSL_TRUSTSTORE_FILE = "couchbase.ssl.truststore.file";
private final static String PROPERTY_SSL_TRUSTSTORE_PASSWORD = "couchbase.ssl.truststore.password";
private final static String PROPERTY_BOOTSTRAP_HTTP_ENABLED = "couchbase.bootstrap.http.enabled";
private final static String PROPERTY_BOOTSTRAP_HTTP_PORT = "couchbase.bootstrap.http.port";
private final static String PROPERTY_BOOTSTRAP_HTTP_SSL_PORT = "couchbase.bootstrap.http.ssl.port";
private final static String PROPERTY_BOOTSTRAP_CARRIER_ENABLED = "couchbase.bootstrap.carrier.enabled";
private final static String PROPERTY_BOOTSTRAP_CARRIER_PORT = "couchbase.bootstrap.carrier.port";
private final static String PROPERTY_BOOTSTRAP_CARRIER_SSL_PORT = "couchbase.bootstrap.carrier.ssl.port";
#Value("#{'${spring.couchbase.bootstrap-hosts}'.split(',')}")
private List<String> couchbaseBootstrapHosts;
#Value("${spring.couchbase.bucket.name}")
private String bucketName;
#Value("${spring.couchbase.password}")
private String couchbasePassword;
#Value("${spring.couchbase.username}")
private String couchbaseUsername;
private final Environment environment;
private final ResourceLoader resourceLoader;
#Inject
public ModelConfig(final Environment environment,
final ResourceLoader resourceLoader) {
this.environment = environment;
this.resourceLoader = resourceLoader;
}
protected List<String> getBootstrapHosts() {
return couchbaseBootstrapHosts;
}
protected String getBucketName() {
return bucketName;
}
protected String getBucketPassword() {
return couchbasePassword;
}
protected CouchbaseEnvironment getEnvironment() {
return DefaultCouchbaseEnvironment.builder()
.keepAliveInterval(environment.getProperty(PROPERTY_KEEP_ALIVE_INTERVAL_MS,
Long.class,
DEFAULT_KEEP_ALIVE_INTERVAL))
// timeout settings
.socketConnectTimeout(environment.getProperty(PROPERTY_SOCKET_CONNECT_TIMEOUT_MS,
Integer.class,
DEFAULT_SOCKET_CONNECT_TIMEOUT_MS))
.connectTimeout(environment.getProperty(PROPERTY_CONNECT_TIMEOUT_MS,
Long.class,
DEFAULT_CONNECT_TIMEOUT_MS))
.managementTimeout(environment.getProperty(PROPERTY_MANAGEMENT_TIMEOUT_MS,
Long.class,
DEFAULT_MANAGEMENT_TIMEOUT_MS))
.disconnectTimeout(environment.getProperty(PROPERTY_DISCONNECT_TIMEOUT_MS,
Long.class,
DEFAULT_DISCONNECT_TIMEOUT_MS))
// port and ssl
.sslEnabled(environment.getProperty(PROPERTY_SSL_ENABLED, Boolean.class, false))
.bootstrapHttpEnabled(environment.getProperty(PROPERTY_BOOTSTRAP_HTTP_ENABLED,
Boolean.class,
Boolean.TRUE))
.bootstrapHttpDirectPort(environment.getProperty(PROPERTY_BOOTSTRAP_HTTP_PORT,
Integer.class,
DEFAULT_HTTP_PORT))
.bootstrapHttpSslPort(environment.getProperty(PROPERTY_BOOTSTRAP_HTTP_SSL_PORT,
Integer.class,
DEFAULT_HTTP_SSL_PORT))
.bootstrapCarrierEnabled(environment.getProperty(PROPERTY_BOOTSTRAP_CARRIER_ENABLED,
Boolean.class,
Boolean.TRUE))
.bootstrapCarrierDirectPort(environment.getProperty(PROPERTY_BOOTSTRAP_CARRIER_PORT,
Integer.class,
DEFAULT_CARRIER_PORT))
.bootstrapCarrierSslPort(environment.getProperty(PROPERTY_BOOTSTRAP_CARRIER_SSL_PORT,
Integer.class,
DEFAULT_CARRIER_SSL_PORT))
// keystore and trust store
.sslKeystore(createKeyStore(environment, resourceLoader))
.sslTruststore(createTrustStore(environment, resourceLoader))
.build();
}
#Override
#Bean(name = BeanNames.COUCHBASE_CLUSTER_INFO)
public ClusterInfo couchbaseClusterInfo() throws Exception {
return couchbaseCluster().authenticate(couchbaseUsername, couchbasePassword).clusterManager().info();
}
/**
* Return the {#link Bucket} instance to connect to.
*
* #throws Exception on Bean construction failure.
*/
#Override
#Bean(destroyMethod = "close", name = BeanNames.COUCHBASE_BUCKET)
public Bucket couchbaseClient() throws Exception {
//#Bean method can use another #Bean method in the same #Configuration by directly invoking it
return couchbaseCluster().openBucket(getBucketName());
}
private KeyStore createKeyStore(final Environment environment, final ResourceLoader resourceLoader) {
return loadKeyStore(environment, resourceLoader, PROPERTY_SSL_KEYSTORE_FILE, PROPERTY_SSL_KEYSTORE_PASSWORD);
}
private KeyStore createTrustStore(final Environment environment, final ResourceLoader resourceLoader) {
return loadKeyStore(environment, resourceLoader, PROPERTY_SSL_TRUSTSTORE_FILE, PROPERTY_SSL_TRUSTSTORE_PASSWORD);
}
private KeyStore loadKeyStore(final Environment environment,
final ResourceLoader resourceLoader,
final String fileProperty,
final String passwordProperty) {
String file = environment.getProperty(fileProperty);
String password = environment.getProperty(passwordProperty);
if (file != null) {
Resource resource = resourceLoader.getResource(file);
if (resource != null) {
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(resource.getInputStream(), password == null ? null : password.toCharArray());
return keyStore;
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
}
return null;
}
}
Related
I am working on Micro-Services Project and a requirement came where the communication between 2 micro-services need to happen. So, I used FeignClient Interface Approach to get it done.
Here is the FeignClientInterface.java Class:
#AuthorizedFeignClient(name = "fcfhpickorders")
public interface FCFHPickOrderService {
#PutMapping(value = "/api/prescriptions/prescriptionCode", consumes = {"application/json", "application/merge-patch+json"})
public ResponseEntity<PrescriptionDTO> updatePrescriptionForBucketKey(
#NotNull #RequestBody PrescriptionDTO prescriptionDTO
);
}
Below is the function where I used the above declared function:
public Bucket setLinkedPrescriptionCode(Bucket bucket) {
if (bucket.getLinkedPrescriptionCode() != null) {
PrescriptionDTO prescription = new PrescriptionDTO();
prescription.setBucketKey(bucket.getBucketKey());
prescription.setPrescriptionCode(bucket.getLinkedPrescriptionCode());
ResponseEntity<PrescriptionDTO> prescriptionDTOResponseEntity = fcfhPickOrderService.updatePrescriptionForBucketKey(prescription);
if(prescriptionDTOResponseEntity.getStatusCode().isError()) {
bucket.setLinkedPrescriptionCode(null);
bucketRepository.save(bucket);
throw new ResourceNotFoundException("Prescription with " + bucket.getLinkedPrescriptionCode() + "Not Found");
}
}
return bucket;
}
I used the setLinkedPrescriptionCode(Bucket bucket) function while saving and updating an existing bucket. It worked fine.
I have some previously written test cases for creation and updation of bucket that are failing now because of the addition of the REST call.
#IntegrationTest
#ExtendWith(MockitoExtension.class)
#AutoConfigureMockMvc
#WithMockUser
class BucketResourceIT {
private static final String DEFAULT_BUCKET_KEY = "AAAAAAAAAA";
private static final String UPDATED_BUCKET_KEY = "BBBBBBBBBB";
private static final String DEFAULT_QR_CODE = "AAAAAAAAAA";
private static final String UPDATED_QR_CODE = "BBBBBBBBBB";
private static final String DEFAULT_LINKED_PRESCRIPTION_CODE = "AAAAAAAAAA";
private static final String UPDATED_LINKED_PRESCRIPTION_CODE = "BBBBBBBBBB";
private static final String ENTITY_API_URL = "/api/buckets";
private static final String ENTITY_API_URL_ID = ENTITY_API_URL + "/{id}";
private static Random random = new Random();
private static AtomicLong count = new AtomicLong(random.nextInt() + (2 * Integer.MAX_VALUE));
#Autowired
private BucketRepository bucketRepository;
#Mock
private BucketRepository bucketRepositoryMock;
#Autowired
private BucketMapper bucketMapper;
#Mock
private BucketService bucketServiceMock;
#Autowired
private EntityManager em;
#Autowired
private MockMvc restBucketMockMvc;
private Bucket bucket;
#Mock
private FCFHPickOrderService fcfhPickOrderService;
public static Bucket createEntity(EntityManager em) {
Bucket bucket = new Bucket()
.bucketKey(DEFAULT_BUCKET_KEY)
.qrCode(DEFAULT_QR_CODE)
.linkedPrescriptionCode(DEFAULT_LINKED_PRESCRIPTION_CODE);
return bucket;
}
public static Bucket createUpdatedEntity(EntityManager em) {
Bucket bucket = new Bucket()
.bucketKey(UPDATED_BUCKET_KEY)
.qrCode(UPDATED_QR_CODE)
.linkedPrescriptionCode(UPDATED_LINKED_PRESCRIPTION_CODE);
return bucket;
}
#BeforeEach
public void initTest() {
bucket = createEntity(em);
}
#Test
#Transactional
void createBucket() throws Exception {
int databaseSizeBeforeCreate = bucketRepository.findAll().size();
// Create the Bucket
BucketDTO bucketDTO = bucketMapper.toDto(bucket);
restBucketMockMvc
.perform(
post(ENTITY_API_URL)
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content(TestUtil.convertObjectToJsonBytes(bucketDTO))
)
*.andExpect(status().isCreated());*
// Validate the Bucket in the database
List<Bucket> bucketList = bucketRepository.findAll();
assertThat(bucketList).hasSize(databaseSizeBeforeCreate + 1);
Bucket testBucket = bucketList.get(bucketList.size() - 1);
assertThat(testBucket.getBucketKey()).isEqualTo(DEFAULT_BUCKET_KEY);
assertThat(testBucket.getQrCode()).isEqualTo(DEFAULT_QR_CODE);
assertThat(testBucket.getLinkedPrescriptionCode()).isEqualTo(DEFAULT_LINKED_PRESCRIPTION_CODE);
}
#Test
#Transactional
void putNewBucket() throws Exception {
bucketRepository.saveAndFlush(bucket);
int databaseSizeBeforeUpdate = bucketRepository.findAll().size();
// Update the bucket
Bucket updatedBucket = bucketRepository.findById(bucket.getId()).get();
// Disconnect from session so that the updates on updatedBucket are not directly saved in db
em.detach(updatedBucket);
updatedBucket.bucketKey(UPDATED_BUCKET_KEY).qrCode(UPDATED_QR_CODE).linkedPrescriptionCode(UPDATED_LINKED_PRESCRIPTION_CODE);
BucketDTO bucketDTO = bucketMapper.toDto(updatedBucket);
restBucketMockMvc
.perform(
put(ENTITY_API_URL_ID, bucketDTO.getId())
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content(TestUtil.convertObjectToJsonBytes(bucketDTO))
)
*.andExpect(status().isOk());*
// Validate the Bucket in the database
List<Bucket> bucketList = bucketRepository.findAll();
assertThat(bucketList).hasSize(databaseSizeBeforeUpdate);
Bucket testBucket = bucketList.get(bucketList.size() - 1);
assertThat(testBucket.getBucketKey()).isEqualTo(UPDATED_BUCKET_KEY);
assertThat(testBucket.getQrCode()).isEqualTo(UPDATED_QR_CODE);
assertThat(testBucket.getLinkedPrescriptionCode()).isEqualTo(UPDATED_LINKED_PRESCRIPTION_CODE);
}
How should I update the above tests in order to fix the Assertion Errors that I am receiving?
BucketResourceIT > createBucket() FAILED
java.lang.AssertionError at BucketResourceIT.java:133
BucketResourceIT > putNewBucket() FAILED
java.lang.AssertionError at BucketResourceIT.java:294
Given siutation like this, author click publish (activate) a page
Then I have following listener to handleEvent
public class ArticleContentActivationEventHandler implements EventHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(ArticleContentActivationEventHandler.class);
private static final String ARTICLE_PAGE_TEMPLATE = "/conf/myproject/settings/wcm/templates/article-template";
#Reference
private ResourceResolverService resourceResolverService;
#Override
public void handleEvent(Event event) {
ResourceResolver resourceResolver = null;
try {
resourceResolver = resourceResolverService.getServiceResourceResolver();
String resourcePath = getResourcePath(event);
Resource resource = resourceResolver.getResource(resourcePath);
//the resource is null, the resource path is /content/myproject/us/en,
//resourceResolver is not able to resolve it somehow
for (Iterator<Resource> it = resourceResolver.getResource("/content/myproject/us/en").getChildren().iterator(); it.hasNext(); ) {
Resource r = it.next();
String s = r.getPath();
String t = r.getResourceType();
}
if (resource.isResourceType("cq:Page")) {
Resource jcr_content = resource.getChild("jcr:content");
ValueMap vm = jcr_content.getValueMap();
String template = null;
if (vm.containsKey("cq:template")) {
template = PropertiesUtil.toString(vm.get("cq:template"), "");
}
and below is the interface:
public interface ResourceResolverService {
ResourceResolver getServiceResourceResolver() throws LoginException;
void closeResourceResolver(ResourceResolver resourceResolver);
}
and the impl class:
#Component(service = ResourceResolverService.class, immediate = true)
public class ResourceResolverServiceImpl implements ResourceResolverService {
#Reference
private ResourceResolverFactory resourceResolverFactory;
Logger logger = LoggerFactory.getLogger(ResourceResolverServiceImpl.class);
#Activate
protected void activate() {
logger.info("*** Activating Service ResourceResolverServiceImpl");
}
#Override
public ResourceResolver getServiceResourceResolver() throws LoginException {
final Map<String, Object> param = Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, (Object) "getResourceResolver");
//the ResourceResolver returned here may got some issue?
return resourceResolverFactory.getServiceResourceResolver(param);
}
#Override
public void closeResourceResolver(ResourceResolver resourceResolver) {
resourceResolver.close();
}
}
I do have the osgi config setup by following tutorial http://www.aemcq5tutorials.com/tutorials/resourceresolver-from-resourceresolverfactory/
But in my actually even handler class, it never successfully resolved resource resourceResolver is not able to resolve /content/myproject/us/en , resourceResolver keep give me null value
Could anyone experienced this suggest me some code sample to resolve my issue? thanks
Check the User mapping configuration, the bundle id used in the configuration should match to your project core artifact id.
For example: in the configuration
bundleId = com.day.cq.wcm.cq-msm-core
Alternatively you can try below code instead
Map<String, Object> param = new HashMap<String, Object>();
param.put(ResourceResolverFactory.SUBSERVICE, "service-user");
ResourceResolver resolver = resourceResolverFactory.getServiceResourceResolver(param);
I was able to reproduce my problem with a minimal modification of the official Spring Boot guide for Accessing Data with MongoDB, see https://github.com/thokrae/spring-data-mongo-zoneddatetime.
After adding a java.time.ZonedDateTime field to the Customer class, running the example code from the guide fails with a CodecConfigurationException:
Customer.java:
public String lastName;
public ZonedDateTime created;
public Customer() {
output:
...
Caused by: org.bson.codecs.configuration.CodecConfigurationException`: Can't find a codec for class java.time.ZonedDateTime.
at org.bson.codecs.configuration.CodecCache.getOrThrow(CodecCache.java:46) ~[bson-3.6.4.jar:na]
at org.bson.codecs.configuration.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:63) ~[bson-3.6.4.jar:na]
at org.bson.codecs.configuration.ChildCodecRegistry.get(ChildCodecRegistry.java:51) ~[bson-3.6.4.jar:na]
This can be solved by changing the Spring Boot version from 2.0.5.RELEASE to 2.0.1.RELEASE in the pom.xml:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
Now the exception is gone and the Customer objects including the ZonedDateTime fields are written to MongoDB.
I filed a bug (DATAMONGO-2106) with the spring-data-mongodb project but would understand if changing this behaviour is not wanted nor has a high priority.
What is the best workaround? When duckduckgoing for the exception message I find several approaches like registering a custom codec, a custom converter or using Jackson JSR 310. I would prefer to not add custom code to my project to handle a class from the java.time package.
Persisting date time types with time zones was never supported by Spring Data MongoDB, as stated by Oliver Drotbohm himself in DATAMONGO-2106.
These are the known workarounds:
Use a date time type without a time zone, e.g. java.time.Instant. (It is generally advisable to only use UTC in the backend, but I had to extend an existing code base which was following a different approach.)
Write a custom converter and register it by extending AbstractMongoConfiguration. See the branch converter in my test repository for a running example.
#Component
#WritingConverter
public class ZonedDateTimeToDocumentConverter implements Converter<ZonedDateTime, Document> {
static final String DATE_TIME = "dateTime";
static final String ZONE = "zone";
#Override
public Document convert(#Nullable ZonedDateTime zonedDateTime) {
if (zonedDateTime == null) return null;
Document document = new Document();
document.put(DATE_TIME, Date.from(zonedDateTime.toInstant()));
document.put(ZONE, zonedDateTime.getZone().getId());
document.put("offset", zonedDateTime.getOffset().toString());
return document;
}
}
#Component
#ReadingConverter
public class DocumentToZonedDateTimeConverter implements Converter<Document, ZonedDateTime> {
#Override
public ZonedDateTime convert(#Nullable Document document) {
if (document == null) return null;
Date dateTime = document.getDate(DATE_TIME);
String zoneId = document.getString(ZONE);
ZoneId zone = ZoneId.of(zoneId);
return ZonedDateTime.ofInstant(dateTime.toInstant(), zone);
}
}
#Configuration
public class MongoConfiguration extends AbstractMongoConfiguration {
#Value("${spring.data.mongodb.database}")
private String database;
#Value("${spring.data.mongodb.host}")
private String host;
#Value("${spring.data.mongodb.port}")
private int port;
#Override
public MongoClient mongoClient() {
return new MongoClient(host, port);
}
#Override
protected String getDatabaseName() {
return database;
}
#Bean
public CustomConversions customConversions() {
return new MongoCustomConversions(asList(
new ZonedDateTimeToDocumentConverter(),
new DocumentToZonedDateTimeConverter()
));
}
}
Write a custom codec. At least in theory. My codec test branch is unable to unmarshal the data when using Spring Boot 2.0.5 while working fine with Spring Boot 2.0.1.
public class ZonedDateTimeCodec implements Codec<ZonedDateTime> {
public static final String DATE_TIME = "dateTime";
public static final String ZONE = "zone";
#Override
public void encode(final BsonWriter writer, final ZonedDateTime value, final EncoderContext encoderContext) {
writer.writeStartDocument();
writer.writeDateTime(DATE_TIME, value.toInstant().getEpochSecond() * 1_000);
writer.writeString(ZONE, value.getZone().getId());
writer.writeEndDocument();
}
#Override
public ZonedDateTime decode(final BsonReader reader, final DecoderContext decoderContext) {
reader.readStartDocument();
long epochSecond = reader.readDateTime(DATE_TIME);
String zoneId = reader.readString(ZONE);
reader.readEndDocument();
return ZonedDateTime.ofInstant(Instant.ofEpochSecond(epochSecond / 1_000), ZoneId.of(zoneId));
}
#Override
public Class<ZonedDateTime> getEncoderClass() {
return ZonedDateTime.class;
}
}
#Configuration
public class MongoConfiguration extends AbstractMongoConfiguration {
#Value("${spring.data.mongodb.database}")
private String database;
#Value("${spring.data.mongodb.host}")
private String host;
#Value("${spring.data.mongodb.port}")
private int port;
#Override
public MongoClient mongoClient() {
return new MongoClient(host + ":" + port, createOptions());
}
private MongoClientOptions createOptions() {
CodecProvider pojoCodecProvider = PojoCodecProvider.builder()
.automatic(true)
.build();
CodecRegistry registry = CodecRegistries.fromRegistries(
createCustomCodecRegistry(),
MongoClient.getDefaultCodecRegistry(),
CodecRegistries.fromProviders(pojoCodecProvider)
);
return MongoClientOptions.builder()
.codecRegistry(registry)
.build();
}
private CodecRegistry createCustomCodecRegistry() {
return CodecRegistries.fromCodecs(
new ZonedDateTimeCodec()
);
}
#Override
protected String getDatabaseName() {
return database;
}
}
Can we use StringTemplateResolver to populate a string template with Icontext. If so how we can do?
TemplateProcessingParameters and IResourceResolver is removed from Thymeleaf 3. Any working example would greatly help?
I have followed this example and it works great in Thymeleaf 2
Is there a way to make Spring Thymeleaf process a string template?
I didnt see any reference any migration guide as well.
I think I found a solution. If anybody has better answer please let me know.
I did a small mistake earlier. Hope this helps.
private TemplateEngine templateEngine;
private TemplateEngine getTemplateEngine() {
if(null == templateEngine){
templateEngine = new TemplateEngine();
StringTemplateResolver templateResolver =new StringTemplateResolver();
templateResolver.setTemplateMode(TemplateMode.HTML);
templateEngine.setTemplateResolver(templateResolver);
}
return templateEngine;
}
public String getTemplateFromMap(String htmlContent, Map<String, String> dynamicAttibutesMap) {
templateEngine = getTemplateEngine();
String template = null;
final Context ctx = new Context(new Locale(TEMPLATE_LOCAL));
if (!CollectionUtils.isEmpty(emailAttibutesMap)) {
dynamicAttibutesMap.forEach((k,v)->ctx.setVariable(k, v));
}
if (null != templateEngine) {
template = templateEngine.process(htmlContent, ctx);
}
return template;
}
This is how we did it, as a Spring #Service Bean:
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import org.springframework.stereotype.Service;
import org.thymeleaf.TemplateProcessingParameters;
import org.thymeleaf.context.IContext;
import org.thymeleaf.messageresolver.IMessageResolver;
import org.thymeleaf.messageresolver.StandardMessageResolver;
import org.thymeleaf.resourceresolver.IResourceResolver;
import org.thymeleaf.spring4.SpringTemplateEngine;
import org.thymeleaf.templatemode.StandardTemplateModeHandlers;
import org.thymeleaf.templateresolver.ITemplateResolutionValidity;
import org.thymeleaf.templateresolver.ITemplateResolver;
import org.thymeleaf.templateresolver.NonCacheableTemplateResolutionValidity;
import org.thymeleaf.templateresolver.TemplateResolution;
import org.thymeleaf.util.Validate;
import com.rathna.app.model.constants.common.BeanConstants;
/**
* Ref: https://github.com/thymeleaf/thymeleaf-itutorial/blob/2.1-master/src/test/java/org/thymeleaf/tools/memoryexecutor/StaticTemplateExecutorTest.java
* #author anandchakru
*
*/
#Service
public class StaticTemplateService {
public String processTemplateCode(final String code, final IContext context) {
Validate.notNull(code, "Code must be non-null");
Validate.notNull(context, "Context must be non-null");
String templateMode = StandardTemplateModeHandlers.HTML5.getTemplateModeName();
IMessageResolver messageResolver = new StandardMessageResolver();
ITemplateResolver templateResolver = new MemoryTemplateResolver(code, templateMode);
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setMessageResolver(messageResolver);
templateEngine.setTemplateResolver(templateResolver);
templateEngine.initialize();
return templateEngine.process("dummy", context);
}
}
class FixedMemoryResourceResolver implements IResourceResolver {
private static final String NAME = "FixedMemoryResourceResolver";
private final String templateContent;
public FixedMemoryResourceResolver(final String templateContent) {
Validate.notNull(templateContent, "Template content must be non-null");
this.templateContent = templateContent;
}
#Override
public String getName() {
return NAME;
}
#Override
public InputStream getResourceAsStream(final TemplateProcessingParameters tpp, final String templateName) {
return new ByteArrayInputStream(templateContent.getBytes());
}
}
class MemoryTemplateResolver implements ITemplateResolver {
private static final String NAME = "MemoryTemplateResolver";
private static final Integer ORDER = 1;
private final String templateContent;
private final String templateMode;
public MemoryTemplateResolver(final String templateContent, final String templateMode) {
Validate.notNull(templateContent, "Template content must be non-null");
Validate.notNull(templateMode, "Template mode must be non-null");
this.templateContent = templateContent;
this.templateMode = templateMode;
}
#Override
public void initialize() {
}
#Override
public String getName() {
return NAME;
}
#Override
public Integer getOrder() {
return ORDER;
}
#Override
public TemplateResolution resolveTemplate(final TemplateProcessingParameters tpp) {
String templateName = "CustomTemplate";
String resourceName = "CustomResource";
IResourceResolver resourceResolver = new FixedMemoryResourceResolver(templateContent);
ITemplateResolutionValidity validity = new NonCacheableTemplateResolutionValidity();
return new TemplateResolution(templateName, resourceName, resourceResolver, StandardCharsets.UTF_8.toString(),
templateMode, validity);
}
}
and call it like this:
#Autowired
protected StaticTemplateService staticTemplateService;
...
private String getProcessedHtml(){
Context context2 = new Context();
context2.setVariable("greet", "Hello");
return staticTemplateService.processTemplateCode("<div th:text="${greet}">Hi</div> World", context2);
}
With the latest version of spring 5 and thymeleaf its easy to read string from thymeleaf.
If you are using gradle use the below import
compile "org.thymeleaf:thymeleaf:3.0.11.RELEASE"
compile "org.thymeleaf:thymeleaf-spring5:3.0.11.RELEASE"
//Code sample starts here
private TemplateEngine templateEngine;
private final static String TEMPLATE_LOCAL = "US";
public TemplateEngine getTemplateEngine() {
templateEngine = new TemplateEngine();
StringTemplateResolver stringTemplateResolver = new StringTemplateResolver();
templateEngine.setTemplateResolver(stringTemplateResolver);
return templateEngine;
}
public String getTemplateFromAttributes(String htmlContent, Map<String, Object> attr)
{
templateEngine = getTemplateEngine();
Context context = new Context(new Locale(TEMPLATE_LOCAL));
if (!CollectionUtils.isEmpty(attr)) {
attr.forEach((k,v)->context.setVariable(k, v));
}
return templateEngine.process(htmlContent, context);
}
Hope this is a useful snippet
I'm developing a spring boot project which uses spring batch scheduler to read data after 2 sec's and send it to the message broker (Activemq) which works fine with hardcoded fixed delay.
However, i'm now trying to read the #Scheduled(fixedDelay) from database rather hard coded but looks like nothing is working out. I can see the expression contains 10 secs but scheduler doesn't start
#Service
public class QuoteService implements ApplicationListener<BrokerAvailabilityEvent> {
private static Log logger = LogFactory.getLog(QuoteService.class);
private final MessageSendingOperations<String> messagingTemplate;
private final StockQuoteGenerator quoteGenerator = new StockQuoteGenerator();
private AtomicBoolean brokerAvailable = new AtomicBoolean();
private ReadCronExpressionDataService readCronExpressionDataService;
private int expression;
#Autowired
public QuoteService(MessageSendingOperations<String> messagingTemplate,ReadCronExpressionDataService readCronExpressionDataService) {
this.messagingTemplate = messagingTemplate;
this.readCronExpressionDataService=readCronExpressionDataService;
expression = readCronExpressionDataService.readData();
}
#Scheduled(fixedDelay=expression) //#Scheduled(fixedDelay=2000)
public void sendQuotes() {
for (Quote quote : this.quoteGenerator.generateQuotes()) {
if (logger.isTraceEnabled()) {
logger.trace("Sending quote " + quote);
}
if (this.brokerAvailable.get()) {
this.messagingTemplate.convertAndSend("/topic/price.stock." + quote.getTicker(), quote);
}
}
}
private static class StockQuoteGenerator {
private static final MathContext mathContext = new MathContext(2);
private final Random random = new Random();
private final Map<String, String> prices = new ConcurrentHashMap<>();
public StockQuoteGenerator() {
this.prices.put("CTXS", "24.30");
this.prices.put("DELL", "13.03");
this.prices.put("EMC", "24.13");
this.prices.put("GOOG", "893.49");
this.prices.put("MSFT", "34.21");
this.prices.put("ORCL", "31.22");
this.prices.put("RHT", "48.30");
this.prices.put("VMW", "66.98");
}
public Set<Quote> generateQuotes() {
Set<Quote> quotes = new HashSet<>();
for (String ticker : this.prices.keySet()) {
BigDecimal price = getPrice(ticker);
quotes.add(new Quote(ticker, price));
}
return quotes;
}
private BigDecimal getPrice(String ticker) {
BigDecimal seedPrice = new BigDecimal(this.prices.get(ticker), mathContext);
double range = seedPrice.multiply(new BigDecimal(0.02)).doubleValue();
BigDecimal priceChange = new BigDecimal(String.valueOf(this.random.nextDouble() * range), mathContext);
return seedPrice.add(priceChange);
}
}
}
Any idea?