I have a spring boot application, that is up and running. However, trying to get the cart bit to work is tricky.
I have a component that represents the cart
#Component
#SessionScope
public class Cart{
private String user;
private Map<Item, Integer> items;
private int numberOfItems;
... getters setters
my service is as follows
#Service
public class CartServiceImpl implements Cart Service {
#Autowired
Cart cart;
#Override
public Cart addItem(String user, Item item) {
// add item to cart
}
controller is as follows:
#PostMapping(path="/cart/addItem/{user}", consumes = "application/json", produces = "application/json")
public Cart addItem(#PathVariable String user, #RequestBody Item item) {
return cartService.addItem(user, item);
}
Am I missing some sort of configuration, IT pretty much vanila spring boot app with rest endpoint which basically just passes item through to service.
Related
I have a rest API for class Tag, and want to add a link for each object.
but the duplicate link is added on every request(which calls the findAll() method).
see the pictures below
tag after first request
tag after many requests
What should I change to make it appear only once?
Model -
public class Tag extends RepresentationModel<Tag> {
...
}
Controller -
#RestController
#RequestMapping("tags")
public class TagController {
private final TagService tagService;
#Autowired
public TagController(TagService tagService) {
this.tagService = tagService;
}
#GetMapping()
public ResponseEntity<List<Tag>> findAll() {
List<Tag> tags = this.tagService.findAll();
List<Tag> response = new ArrayList<>();
for(Tag t : tags) {
t.add(linkTo(methodOn(TagController.class).find(t.getId())).withSelfRel());
response.add(t);
}
return new ResponseEntity<>(response, HttpStatus.OK);
}
#GetMapping("/{id}")
public ResponseEntity<Tag> find(#PathVariable("id") Long id) {
return ResponseEntity.ok(this.tagService.find(id));
}
I am writing a web service with an authorization and registration form. There are two types of users: regular and administrator. There is a controller that sends to the admin page at a given URL:
#Controller
public class ViewPageController {
#RequestMapping(value = "/admin", method = RequestMethod.GET)
public String sendAdminPage(){
return "AdminPage";
}
}
But ordinary users can also access this page. It is necessary that only those who logged in as an administrator get to the admin page. There are options for how this can be organized? Maybe save the logged in user in the session? (Preferably without Spring Security)
the easy way define a Aspect and A annotation.some code like this
#Inherited
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface Authorize {
//
String[] value() default {};
}
AuthorizationAspect.java
#Slf4j
#Aspect
#Component
#RequiredArgsConstructor
public class AuthorizationAspect {
private final AuthorizationService authorizationService;
private final CacheUtil cacheUtil;
private static final String PRE = "AUTH";
#Before("#annotation(com.jin.learn.config.security.Authorize)")
public void checkPermission(JoinPoint joinPoint) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
Long accountId = JWTUtil.getUserIdFromRequest(request);
Set<String> authorization = cacheUtil.getAllSet(PRE + accountId);
if(authorization==null){
authorization = authorizationService.findByAccountId(accountId);
cacheUtil.save(PRE + accountId, authorization);
}
Authorize authorize = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(Authorize.class);
String[] needAuthorization = authorize.value();
if (needAuthorization.length == 0) return;
if (authorization!=null && !authorization.isEmpty()) {
if (!authorization.containsAll(Arrays.asList(needAuthorization))){
throw new SystemException(ExceptionCode.NO_PERMISSION);
}
} else {
throw new SystemException(ExceptionCode.NO_PERMISSION);
}
}
}
use like this
#Authorize(value="needRight")
#RequestMapping(value = "/admin", method = RequestMethod.GET)
public String sendAdminPage(){
return "AdminPage";
}
besides,there are some security framework shiro and spring-security
so i basically want to process a HTTP-Post with a Controller in Spring and send back a result for the User AND after that i want to make a database call.
So here is my example:
#Controller
public class AngebotController extends WebMvcConfigurerAdapter {
#Autowired
private DatabaseUtils dbUtil;
#Autowired
private MyMailSender mailSender;
#RequestMapping(value = REQUEST_PATH, method = RequestMethod.POST)
public String doPost(#Valid FormInput input, BindingResult bindingResult) {
// .. some input validations here
// after the validation is complete i will have accesss to a object, that i just created, just like the following
// Lets say that this object holds important values for the database query
final MyObject validatedInput = new MyObject();
// Start a new Thread to do the remaining work (the Database Call)
Thread t = new Thread() {
#Override
public void run() {
// so i am starting the database query with the information that i just validated above
// That Database Query will Return a List of Items based on the given MyObject
// This Query will take a long time and i dont want the user to wait, because this data is not nessessary for the user
List<Item> items = dbUtil.getStuffByInput(validatedInput);
for(Item i : items) {
// Now i just want to send some informations about the item via email, this part works
mailSender.sendMail("mail#mail.mail", i);
}
}
}.start();
return "viewname";
}
}
#Service
public class DatabaseUtils {
#Autowired
private ItemRepository repository;
public List<Item> getStuffByInput(MyObject o) {
List<Item> items = repository.findAllByMyObject(o);
// Doing some more stuff with those items here ..
return items;
}
}
// The Implementation will be generated by Spring
public interface ItemRepository extends CrudRepository<Item, Long> {
// will select all Items by comparing the myObject with each Item
// This also works like intended
public List<Item> findAllByMyObject(MyObject myObject);
}
So where is my Problem?
The only Problem i have is, that the Database Query will end throwing an Exception, because the Database Connection was closed (i guess by Spring)
The Exception: Exception in thread "Thread-6" org.hibernate.SessionException: Session is closed!
Any Help appreciated.
Thanks!
I have a simple MongoRepository I would like to modify to return the generated ObjectId on post(save()).
public interface EmployeeRepository extends MongoRepository<Employee, String>
{
public void delete(Employee employee);
public Employee save(Employee employee);
public Employee findOne(String id);
public List<Employee> findAll();
public Employee findByName(String principal);
}
I have explored ways to generate the id client side and pass it in the post BUT I really want Spring to handle this.
I've tried intercepting with a controller and returning the object in the ResponseBody, like so:
#RequestMapping(value=URI_EMPLOYEES, method=RequestMethod.POST)
public #ResponseBody Employee addEmployee(#RequestBody Employee employee) {
return repo.save(employee);
}
Problem with this is it forces me to re-work all the HATEOAS related logic Spring handles for me. Which is a MAJOR pain. (Unless I'm missing something.)
What's the most effective way of doing this without having to replace all of the methods?
Was using #Controller instead of #RepositoryRestController which was causing things to act up.
We can now easily override the POST method on this resource to return whatever we want while keeping spring-data-rest's implementation of the EmployeeRepository intact.
#RepositoryRestController
public class EmployeeController {
private final static String URI_EMPLOYEES = "/employees";
#Autowired private EmployeeRepository repo;
#RequestMapping(value=URI_EMPLOYEES, method=RequestMethod.POST)
public #ResponseBody HttpEntity<Employee> addVideo(#RequestBody Employee employee) {
return new ResponseEntity<Employee>(repo.save(employee), HttpStatus.OK);
}
}
Have a look at the interface:
public Employee save(Employee employee)
You can get the saved entity by simply doing
Employee saved = repository.save(employee);
return saved.getId();
The being said, you surely can generate the Id and set it via setId(). But once an ID is saved, it is immutable. Changing the Id and saving that entity would result in a new document saved in MongoDB.
Is it possible to use Spring's #Value annotation to read and write property values of a custom class type?
For example:
#Component
#PropertySource("classpath:/data.properties")
public class CustomerService {
#Value("${data.isWaiting:#{false}}")
private Boolean isWaiting;
// is this possible for a custom class like Customer???
// Something behind the scenes that converts Custom object to/from property file's string value via an ObjectFactory or something like that?
#Value("${data.customer:#{null}}")
private Customer customer;
...
}
EDITED SOLUTION
Here is how I did it using Spring 4.x APIs...
Created new PropertyEditorSupport class for Customer class:
public class CustomerPropertiesEditor extends PropertyEditorSupport {
// simple mapping class to convert Customer to String and vice-versa.
private CustomerMap map;
#Override
public String getAsText()
{
Customer customer = (Customer) this.getValue();
return map.transform(customer);
}
#Override
public void setAsText(String text) throws IllegalArgumentException
{
Customer customer = map.transform(text);
super.setValue(customer);
}
}
Then in application's ApplicationConfig class:
#Bean
public CustomEditorConfigurer customEditorConfigurer() {
Map<Class<?>, Class<? extends PropertyEditor>> customEditors =
new HashMap<Class<?>, Class<? extends PropertyEditor>>(1);
customEditors.put(Customer.class, CustomerPropertiesEditor.class);
CustomEditorConfigurer configurer = new CustomEditorConfigurer();
configurer.setCustomEditors(customEditors);
return configurer;
}
Cheers,
PM
You have to create a class extending PropertyEditorSupport.
public class CustomerEditor extends PropertyEditorSupport {
#Override
public void setAsText(String text) {
Customer c = new Customer();
// Parse text and set customer fields...
setValue(c);
}
}
It's possible but reading Spring documentation. You could see this example:
Example usage
#Configuration
#PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
#Autowired
Environment env;
#Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
See details here
Spring can read properties and load them directly into a class.
Moreover, you can add #ConfigurationProperties(prefix = "data") on top of the class, instead of wiring each nested property one by one, by making the code cleaner.
Given all that, here is the final example with explanations:
// File: CustomerConfig.java
#Configuration
// Set property source file path (optional)
#PropertySource("classpath:/data.properties")
// Put prefix = "data" here so that Spring read properties under "data.*"
#ConfigurationProperties(prefix = "data")
public class CustomerConfig {
// Note: Property name here is the same as in the file (data.customer)
// Spring will automatically read and put "data.customer.*" properties into this object
private Customer customer;
// Other configs can be added here too... without wiring one-by-one
public setCustomer(Customer customer){
this.customer = customer;
}
public getCustomer(){
return this.customer;
}
}
That's it, now you have "data.customer.*" properties, loaded and accessible via CustomerConfig.getCustomer().
To integrate it into your service (based on your example code):
// File: CustomerService.java
#Component
#PropertySource("classpath:/data.properties")
public class CustomerService {
#Value("${data.isWaiting:#{false}}")
private Boolean isWaiting;
#Autowired // Inject configs, either with #Autowired or using constructor injection
private CustomerConfig customerConfig;
public void myMethod() {
// Now its available for use
System.out.println(customerConfig.getCustomer().toString());
}
}
This way no "magical hack" is required to read configs into a class.
Take a look at the #ConfigurationProperties documentation/examples, and this post for more useful info.
Note: I'd suggest against using PropertyEditorSupport, since
a) it was built for different purpose, may change in future by breaking the code
b) it requires manual "handling" code inside => possible bugs
Instead, use what was built right for that purpose (Spring already has it), in order to both make the code easier to understand, and to gain possible inner improvements/optimizations which might be done in the future (or present).
Further improvements: Your CustomerService seems to be cluttered with configs (#PropertyService) too. I'd suggest reading those properties via another class too (similarly) then wiring that class here, instead of doing all in the CustomerService.
If you want to use it with lists, there is a workaround using array instead.
Define your property as Customer[] instead of List then:
in ApplicationConfig class:
#Bean
public CustomEditorConfigurer customEditorConfigurer() {
Map<Class<?>, Class<? extends PropertyEditor>> customEditors =
new HashMap<Class<?>, Class<? extends PropertyEditor>>(1);
customEditors.put(Customer.class, CustomerPropertiesEditor.class);
customEditors.put(Customer[].class, CustomerPropertiesEditor.class);
CustomEditorConfigurer configurer = new CustomEditorConfigurer();
configurer.setCustomEditors(customEditors);
return configurer;
}
In CustomerEditor:
public class CustomerEditor extends PropertyEditorSupport {
public static final String DEFAULT_SEPARATOR = ",";
#Override
public void setAsText(String text) {
String[] array = StringUtils.delimitedListToStringArray(text, this.separator);
if (this.emptyArrayAsNull && array.length == 0) {
super.setValue((Object) null);
} else {
if (this.trimValues) {
array = StringUtils.trimArrayElements(array);
}
// Convert String[] to Customer[]
super.setValue(...);
}
}
}
If you want to use an existing converter/constructor, you can just call it within your expression.
For example:
#Value("#{T(org.test.CutomerMap).transform('${serialized.customer}')}")
private Customer customer;