I have a common DAO class with the logger initialized by its name.
I have something like this:
public class QueueDaoImpl implements QueueDao {
private static Logger log = LoggerFactory.getLogger(QueueDaoImpl.class);
#Autowired
private StoredProcedureFactory storedProcedureFactory;
#Override
#Transactional(readOnly = true, value = "transactionManager")
public EnqueueRespVO enqueueMessage(EnqueueMsgReqVO req) throws DAOException {
EnqueueRespVO toResponse;
try {
EnqueueMessageStoredProcedure sp = storedProcedureFactory.getEnqueueMessageSP();
Map<String, String> inParams = BeanUtils.describe(req);
Map<String, Object> out = sp.execute(inParams);
toResponse = new EnqueueRespVO();
toResponse.setErrCode((Integer) out.get(EnqueueMessageStoredProcedure.ERROR_CODE_OUT_PARAM));
toResponse.setErrDescription((String) out.get(EnqueueMessageStoredProcedure.ERROR_DESC_OUT_PARAM));
} catch (DataAccessException e) {
final String msg = String.format("A database error has occurred. Error = ", e);
//log.error("[login] - Ended with an error. ", msg);
throw new DAOException(msg, e);
} catch (IllegalStateException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
final String msg = String.format("A internal error has occurred. Error = ", e);
//log.error("[login] - Ended with an error. ", msg);
throw new DAOException(msg, e);
}
return toResponse;
}
}
Then, I have two classes (posted below), 'RetryCallUssdJob' and 'SendErrorDetail' that use 'QueryDaoImpl' as composed object to do the database logic.
(I'm using Spring to inject the Dao beans)
Example of RetryCallUssdJob class
#Component
public class RetryCallUssdJob {
private static Logger log = LoggerFactory.getLogger(RetryCallUssdJob.class);
#Autowired
private QueueDao queueDao;
public synchronized void execute() {
here i'm using the queueDao instance injected by the spring context
}
}
Example of SendErrorDetail class
public class SendErrorDetailHandlerImpl implements ISendErrorDetailHandler {
private static Logger log = LoggerFactory.getLogger(SendErrorDetailHandlerImpl.class);
#Autowired
private QueueDao queueDaoImpl;
#Override
public BaseRespVO execute(Map<String, Object> params) {
here i'm using the queueDao instance injected by the spring context
}
}
I would like to know how to make output of 'QueryDaoImpl' logger redirected to different log files depending on what calling class is?
Thanks in advance.
This is tricky, I'd do one of two things:
1) Pass in the Logger in on the method call
or
2) Have different marker interfaces for the DAO that have different loggers injected by spring.
1 is easier, 2 is more declarative.
Related
I have a simple spring-cloud-stream application with this Function implementation :
#Configuration
public class StarZFunction {
private static final Logger LOGGER = LoggerFactory.getLogger(StarZFunction.class);
#Bean
public Function<StarZ, StarZ> processEvents() {
return starZ -> {
starZ.setVmNr(starZ.getVmNr()*1000);
starZ.setTuCode(starZ.getTuCode().toLowerCase(Locale.ROOT));
return starZ;
};
}
}
And I'm trying to thest this code by simply sending and receiving the object:
#SpringBootTest(classes = SampleApp.class)
#Import({TestChannelBinderConfiguration.class})
public class IntegrationTest1 {
private static final Logger LOGGER = LoggerFactory.getLogger(IntegrationTest1.class);
#Autowired
private InputDestination input;
#Autowired
private OutputDestination output;
#Test
void sendAndReceive() {
StarZ starZ = new StarZ(10, "SBB");
input.send(new GenericMessage<StarZ>(starZ));
GenericMessageConverter genericMessageConverter = new GenericMessageConverter();
assertThat(output.receive().getPayload()).isEqualTo(starZ);
}
}
This fails, because output.receive().getPayload() only returns a byte[]. How can I get the StarZ Object?
It looks like the data is converted to json by default. Since I did not find a way to get something like Message<StarZ> directly from the API I did the conversion by hand.
#Test
void sendAndReceive() {
StarZ starZ = new StarZ(10, "SBB");
input.send(new GenericMessage<>(starZ));
Message<byte[]> messageWithByte = output.receive();
assertThat(deserializeMessage(messageWithByte)).isEqualTo(starZ);
}
private StarZ deserializeMessage(Message<byte[]> m) {
LOGGER.info(new String(m.getPayload()));
try {
return objectMapper.readValue(m.getPayload(), StarZ.class);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
Looks a little bit akward, but does the job.
I'm more than happy to accept a better answer though.
is it possible to pass a parameter in a service that allows me to use one method rather than another?
Below is a controller and a service. I want to pass a (persistenceType) parameter that allows me to use mybatis rather than jpa. I'll pass it as queryparam in the REST call.
#Service
public class ManufacturerService implements IManufacturerService {
#Autowired
private ManufacturerRepository manufacturerRepository;
#Autowired
private ManufacturerMapper manufacturerMapper;
#Override
#Transactional
public Manufacturer save(Manufacturer manufacturer) {
//if persistenceType.equals(MYBATIS)
//manufacturerMapper.insert(manufacturer);
//else manufacturerRepository.save(manufacturer);
manufacturerMapper.insert(manufacturer);
return null;
//return manufacturerRepository.save(manufacturer);
}
}
#RestController
#RequestMapping("/manufacturers")
public class ManufacturesController {
public static final Logger LOG = LogManager.getLogger(ManufacturesController.class);
#Autowired
private ManufacturerService manufacturerService;
#PostMapping
public ResponseEntity<Manufacturer> createManufacturer(#RequestBody ManufacturerDTO manufacturer, #Param persistenceType) {
LOG.info("START - createManufacturer");
try {
Manufacturer _manufacturer = ManufacturerMapper.toEntity(manufacturer);
manufacturerService(persistenceType).save(_manufacturer);
LOG.info("STOP - createManufacturer");
return new ResponseEntity<>(_manufacturer, HttpStatus.CREATED);
} catch (Exception e) {
LOG.error("Error description: ", e);
LOG.info("STOP - createManufacturer");
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
I know it's not like that, but it was right to make you understand what my purpose is.
It is perfectly possible. I would start by creating a PersistenceType enum:
enum PersistenceType {
JPA,
MYBATIS
}
Then you need to declare you need to receive a PersistenceType in your POST endpoint and pass it to the underlying ManufacturerService as follows:
#RestController
#RequestMapping("/manufacturers")
public class ManufacturesController {
public static final Logger LOG = LogManager.getLogger(ManufacturesController.class);
#Autowired
private ManufacturerService manufacturerService;
#PostMapping
public ResponseEntity<Manufacturer> createManufacturer(#RequestBody ManufacturerDTO manufacturer, #RequestParm(required = true) PersistenceType persistenceType) {
LOG.info("START - createManufacturer");
try {
Manufacturer _manufacturer = ManufacturerMapper.toEntity(manufacturer);
manufacturerService.save(_manufacturer, persistenceType);
LOG.info("STOP - createManufacturer");
return new ResponseEntity<>(_manufacturer, HttpStatus.CREATED);
} catch (Exception e) {
LOG.error("Error description: ", e);
LOG.info("STOP - createManufacturer");
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
The final thing is to change your ManufacturerService to accept an additional parameter in the save method:
#Service
public class ManufacturerService implements IManufacturerService {
#Autowired
private ManufacturerRepository manufacturerRepository;
#Autowired
private ManufacturerMapper manufacturerMapper;
#Override
#Transactional
public Manufacturer save(Manufacturer manufacturer, PersistenceType persistenceType) {
if (persistenceType.equals(PersistenceType.MYBATIS)) {
return manufacturerMapper.insert(manufacturer);
} else {
return manufacturerRepository.save(manufacturer);
}
}
}
As a suggestion, I would include persistenceType in the request body and not as a separate query param. It works as-is, but it would be more concise and easier to understand for the consumer of your API if everything is passed in the request body (or as a path variable).
If you specify a property in your application.properties file that corresponds to the persistenceType, that will allow you to dynamically switch between mybatis and jpa.
For more information, there are some docs here:
https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config
First I'm not sure if it's a good idea to do all this.
Goal is to create some interfaces with annotations to hide legacy position based string access out of a configuration database, without implementing each interface.
Declarative configured Interface:
public interface LegacyConfigItem extends ConfigDbAccess{
#Subfield(length=3)
String BWHG();
#Subfield(start = 3, length=1)
int BNKST();
#Subfield(start = 4, length=1)
int BEINH();
:
}
Base interface for runtime identification
public interface ConfigDbAccess{
}
Dummy implementation without functionality, may change.
public class EmptyImpl {
}
Beanfactory and MethodInvocation interceptor, to handle the unimplemented methods.
#Component
public class InterfaceBeanFactory extends DefaultListableBeanFactory {
protected static final int TEXT_MAX = 400;
#Autowired
private EntityRepo entityRepo;
public <T> T getInstance(Class<T> legacyInterface, String key) {
ProxyFactory factory = new ProxyFactory(new EmptyImpl());
factory.setInterfaces(legacyInterface);
factory.setExposeProxy(true);
factory.addAdvice(new MethodInterceptor() {
#Override
public Object invoke(MethodInvocation invocation) throws Throwable {
KEY keyAnnotation = invocation.getThis().getClass().getAnnotation(Key.class);
String key= keyAnnotation.key().toUpperCase();
String ptart = invocation.getMethod().getDeclaringClass().getSimpleName();
Vpt result = entityRepo.getOne(new EntityId(ptart.toUpperCase(), schl.toUpperCase()));
Subfield sub = invocation.getMethod().getAnnotation(Subfield.class);
//TODO: Raise missing Subfield annotation
int start = sub.start();
int length = sub.length();
if (start + length > TEXT_MAX) {
//TODO: Raise invalid Subfield config
}
String value = result.getTextField().substring(start,start+length);
return value;
}
});
return (T) factory.getProxy();
}
#Override
protected Map<String, Object> findAutowireCandidates(String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {
Map<String, Object> map = super.findAutowireCandidates(beanName, requiredType, descriptor);
if (ConfigDbAccess.class.isAssignableFrom(requiredType )) {
:
#SpringBootApplication
public class JpaDemoApplication {
#Autowired
private ApplicationContext context;
public static void main(String[] args) {
SpringApplication app = new SpringApplication(JpaDemoApplication.class);
// app.setApplicationContextClass(InterfaceInjectionContext .class);
app.run(args);
}
public class InterfaceInjectionContext extends AnnotationConfigApplicationContext {
public VptInjectionContext () {
super (new InterfaceBeanFactory ());
}
}
So far I got all this stuff working, except when I try to set the applications Context class to my DefaultListableBeanFactory, I'm killing the Spring boot starter web. The application starts, injects the the Autowired fields with my intercepted pseudo implementaition --- and ends.
I think I'm doing something wrong with registering the DefaultListableBeanFactory, but I've no idea how to do it right.
To get this answered:
M. Deinum pointed me to a much simpler solution:
Instead of creating a BeanFactory I installed a BeanPostProcessor with this functioniality.
#RestController
public class DemoRestController {
#Autowired
VptService vptService;
#ConfigItem(key="KS001")
private PrgmParm prgmKs001;
#ConfigItem(key="KS002")
private PrgmParm prgmKs002;
public DemoRestController() {
super();
}
Where the ConfigItem annotation defines the injection point.
Next I created a CustomBeanPostProcessor which scans all incoming beans for
fields having a ConfigItem annotation
#Component
public class CustomBeanPostProcessor implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
for (Field field : bean.getClass().getDeclaredFields()) {
SHL cfgDef = field.getAnnotation(ConfigItem.class);
if (cfgDef != null) {
Object instance = getlInstance(field.getType(), cfgDef.key());
boolean accessible = field.isAccessible();
field.setAccessible(true);
try {
field.set(bean, instance);
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
field.setAccessible(accessible);
}
}
return bean;
}
The getInstnce(field.getType(),cfgDef.key()) creates a proxy with the MethodInterceptor, which does the work.
There are a lot of things to finalize, but all in all it looks good to me.
I'm using Swarm Wildfly to deploy this application.
Basically I'm making a websocket enabled application.
I'd like to inject a singleton which will be started on the startup which modify the variable result.
Upon accessing the "/rafflethis" link, user will be able to see the result which will be sent via session.
The result is that the roll variable null
This is the class
#Singleton
#Startup
#ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
public class runMe implements RaffleManager{
private static final Logger LOGGER = Logger.getLogger(runMe.class.getName());
private static String result;
#PostConstruct
public void onStartup() {
System.out.println("Initialization success.");
}
#Schedule(second = "*/10", minute = "*", hour = "*", persistent = false)
public void run() throws Exception{
int i = 0;
while (true) {
Thread.sleep(1000L);
result = UUID.randomUUID().toString().toUpperCase();
i++;
LOGGER.log(Level.INFO, "i : " + i);
}
}
public String getResult() {
return result;
}
}
interface
public interface RaffleManager {
String getResult();
}
And the "/rafflethis"
#ServerEndpoint("/rafflethis")
public class RaffleThis implements Serializable {
#EJB
RaffleManager roll;
private static final Set<Session> sessions = Collections.synchronizedSet(new HashSet<Session>());
private static void sendMessageToAll(String message) {
for (Session s : sessions) {
try {
s.getBasicRemote().sendText(message);
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
#OnOpen
public void monitorLuckyDip(Session session) throws Exception {
sessions.add(session);
while(true) {
sendMessageToAll(roll.getResult());
}
}
}
Any lead where should I head from this? Thanks!
Looking at the source, I would probably assume a name issue.
Your singleton bean "runMe" is the actual name of the bean not the interface.
SIDE NOTE: Best practice for class names is capitalize the first letter. RunMe instead of runMe.
#Singleton - without parameters will automatically name your bean for lookup using the bean convention from your class name. Imagine if you implement multiple interface, how does EJB pick the name? So it is just logical to use the class name. E.g. If your classname is TestMe, the ejb name will be testMe.
In your case since your class name is runMe, I would think the bean name will be runMe.
To ensure the lookup will not fail, you can specific the name in #Singleton and #EJB.
#Singleton(name = "runMe")
#Startup
#ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
public class runMe implements RaffleManager{
Then in your Service end point class
#ServerEndpoint("/rafflethis")
public class RaffleThis implements Serializable {
#EJB(beanName ="runMe")
RaffleManager roll;
The solution is rather hacky but a very simple one indeed. Take a look at the provided diagram.
And here's the code
Logic Implementation:
#Startup
#Singleton
public class RunMe{
private static final Logger LOGGER = Logger.getLogger(RunMe.class.getName());
#Inject
MessageDTO messageDTO;
#PostConstruct
public void onStartup() {
System.out.println("Initialization success.");
}
#Schedule(second = "*/10", minute = "*", hour = "*", persistent = false)
public void run() throws Exception{
//You can also substitute this method with constructor of the class -- removing the #Schedule annotation.
int i = 0;
while (true) {
Thread.sleep(1000L);
messageDTO.setText(UUID.randomUUID().toString().toUpperCase());
i++;
LOGGER.log(Level.INFO, "i : " + i);
}
}
}
MessageDTO:
#Singleton
public class MessageDTO {
private static String text;
public static String getText() {
return text;
}
public static void setText(String text) {
MessageDTO.text = text;
}
}
Websocket Implementation:
#ServerEndpoint("/rafflethis")
public class RaffleThis implements Serializable {
private static final Logger LOGGER = Logger.getLogger(RaffleThis.class.getName());
private static final Set<Session> sessions = Collections.synchronizedSet(new HashSet<Session>());
#Inject
MessageDTO messageDTO;
private static void sendMessageToAll(String message) {
for (Session s : sessions) {
try {
s.getBasicRemote().sendText(message);
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
#OnOpen
public void monitorLuckyDip(Session session) throws Exception {
sessions.add(session);
while (true) {
Thread.sleep(200);
sendMessageToAll(messageDTO.getText());
}
}
}
I have Spring service, which is actually actor, it is received info, but I cant pass it to another Spring service, because injection fails.
#Service("mailContainer")
#Scope("prototype")
#Component
public class MailContainer extends UntypedActor {
private final LoggingAdapter LOG = Logging.getLogger(getContext().system(), this);
private Mail value;
private List<Mail> mailList = new ArrayList<Mail>();
private Integer size;
#Autowired
#Qualifier("springService")
private SpringService springService;
//#Autowired
public void setSpringService(SpringService springService) {
this.springService = springService;
}
public MailContainer(Mail value) {
this.value = value;
}
#Override
public void onReceive(Object message) throws Exception {
// LOG.debug("+ MailContainer message: {} ", message);
if (message instanceof Mail) {
value = (Mail) message;
System.out.println("MailContainer get message with id " + value.getId());
System.out.println("With time " + value.getDateSend());
//getSender().tell(value, getSelf()); //heta uxarkum
//this.saveIt(value);
springService.add(value);
}
}
and second service
#Service("springService")
//#Component
#Scope("session")
public class SpringService {
private List<Mail> mailList = new ArrayList<Mail>();
public void add(Mail mail) {
System.out.println("Saving mail from Spring " +mail.getId());
mailList.add(mail);
}
public List<Mail> getMailList() {
return mailList;
}
}
Spring config, this is from akka spring example
#Configuration
//#EnableScheduling
//EnableAsync
#ComponentScan(basePackages = {"com"}, excludeFilters = {
#ComponentScan.Filter(Configuration.class)})
//#ImportResource("classpath:META-INF/spring/spring-data-context.xml")
//#EnableTransactionManagement
//#EnableMBeanExport
//#EnableWebMvc
public class CommonCoreConfig {
// the application context is needed to initialize the Akka Spring Extension
#Autowired
private ApplicationContext applicationContext;
/**
* Actor system singleton for this application.
*/
#Bean
public ActorSystem actorSystem() {
ActorSystem system = ActorSystem.create("AkkaJavaSpring");
// initialize the application context in the Akka Spring Extension
SpringExtProvider.get(system).initialize(applicationContext);
return system;
}
}
So, how I can inject just another Spring service?????????
Based on our discussions, I think it is due to the way you create the MailContainer actor. You aren't using the SpringExtProvider and instead are using Props.create directly. This means that Spring doesn't get the opportunity to perform dependency injection on your new actor.
Try changing this code:
#Override
public void preStart() throws Exception {
System.out.println("Mail collector preStart: {} ");
getContext().actorOf(Props.create(MailContainer.class, result), "one");
}
to use the the SpringExtProvider like this:
#Override
public void preStart() throws Exception {
System.out.println("Mail collector preStart: {} ");
getContext().actorOf(SpringExtProvider.get(getContext().system()).props("mailContainer"), "one");
}
This way you are asking the Spring extension to create the new actor and inject any required dependecnies.