View Javadoc
1   package pl.matsuo.core.web.controller;
2   
3   import org.springframework.beans.factory.annotation.Autowired;
4   import org.springframework.beans.factory.annotation.Value;
5   import org.springframework.http.HttpEntity;
6   import org.springframework.http.HttpHeaders;
7   import org.springframework.transaction.annotation.Transactional;
8   import org.springframework.web.bind.annotation.PathVariable;
9   import org.springframework.web.bind.annotation.RequestBody;
10  import org.springframework.web.bind.annotation.RequestMapping;
11  import org.springframework.web.bind.annotation.RequestParam;
12  import org.springframework.web.bind.annotation.ResponseStatus;
13  import org.springframework.web.util.UriTemplate;
14  import pl.matsuo.core.IQueryRequestParams;
15  import pl.matsuo.core.model.AbstractEntity;
16  import pl.matsuo.core.model.api.Initializer;
17  import pl.matsuo.core.model.query.AbstractQuery;
18  import pl.matsuo.core.model.query.condition.Condition;
19  import pl.matsuo.core.service.db.Database;
20  import pl.matsuo.core.service.facade.IFacadeBuilder;
21  
22  import javax.validation.Valid;
23  import java.net.URI;
24  import java.util.ArrayList;
25  import java.util.List;
26  
27  import static java.util.Arrays.*;
28  import static java.util.Collections.*;
29  import static org.hibernate.criterion.MatchMode.*;
30  import static org.springframework.http.HttpStatus.*;
31  import static org.springframework.http.MediaType.*;
32  import static org.springframework.web.bind.annotation.RequestMethod.*;
33  import static pl.matsuo.core.model.query.QueryBuilder.*;
34  import static pl.matsuo.core.util.ReflectUtil.*;
35  import static pl.matsuo.core.util.StringUtil.*;
36  
37  
38  @Transactional
39  public abstract class AbstractController<E extends AbstractEntity, P extends IQueryRequestParams> {
40  
41  
42    @Autowired
43    protected Database database;
44    @Autowired
45    protected IFacadeBuilder facadeBuilder;
46  
47  
48    @SuppressWarnings("unchecked")
49    protected final Class<E> entityType = resolveType(getClass(), AbstractController.class, 0);
50  
51  
52    /**
53     * Lista pól z którymi należy porównywać wartość parametru 'query' z zapytania listującego
54     * elementy.
55     */
56    protected List<String> queryMatchers() {
57      return emptyList();
58    }
59  
60  
61    /**
62     * Tworzy proste zapytanie na podstawie przekazanej mapy parametrów. Jedynym obsługiwanym
63     * parametrem jest 'query' - na podstawie listy pól zwracanych przez {@link #queryMatchers()}
64     * buduje zapytanie wymagające aby każde słowo z 'query' znalazło się w którymś z pól.
65     */
66    protected AbstractQuery<E> listQuery(P params, Condition... additionalConditions) {
67      List<Condition> conditions = new ArrayList<>(asList(additionalConditions));
68  
69      if (notEmpty(params.getQuery())) {
70        if (queryMatchers().isEmpty()) {
71          throw new IllegalStateException("When using query parameter queryMatchers must be defined");
72        }
73  
74        String[] parts = params.getQuery().split(" ");
75  
76        for (String part : parts) {
77          List<Condition> partConditions = new ArrayList<>();
78  
79          for (String queryMatcher : queryMatchers()) {
80            partConditions.add(ilike(queryMatcher, part.trim(), ANYWHERE));
81          }
82  
83          conditions.add(or(partConditions.toArray(new Condition[0])));
84        }
85      }
86  
87      return entityQuery(conditions.toArray(new Condition[0]));
88    }
89  
90  
91    /**
92     * Domyślna metoda listująca elementy według zadanych parametrów.
93     */
94    @RequestMapping(method = GET)
95    public List<E> list(P params) {
96      AbstractQuery<E> query = listQuery(params);
97  
98      if (params.getLimit() != null && params.getLimit() > 0) {
99        query.limit(params.getLimit());
100     }
101     if (params.getOffset() != null && params.getOffset() > 0) {
102       query.offset(params.getOffset());
103     }
104 
105     List<E> list = database.find(query);
106 
107     return list;
108   }
109 
110 
111   /**
112    * Pomocnicza metoda wyszukiwania gdy zapytanie wymaga jedynie przekazania kryteriów.
113    */
114   protected List<E> list(Condition... conditions) {
115     return database.find(entityQuery(conditions));
116   }
117 
118 
119   /**
120    * Pomocnicza metoda wyszukiwania gdy zapytanie wymaga jedynie przekazania kryteriów.
121    */
122   protected AbstractQuery<E> entityQuery(Condition... conditions) {
123     return query(entityType, conditions).initializer(entityInitializers);
124   }
125 
126 
127   /**
128    * Pobiera listę encji danego typu po kolekcji identyfikatorów.
129    */
130   @RequestMapping(value = "/list/byIds", method = GET, consumes = {APPLICATION_OCTET_STREAM_VALUE})
131   public List<E> listByIds(@RequestParam("ids") List<Integer> ids) {
132     return database.find(entityQuery(in("id", ids)));
133   }
134 
135 
136   /**
137    * Pobiera pojedynczą encję danego typu po id.
138    */
139   @RequestMapping(value = "/{id}", method = GET)
140   public HttpEntity<E> find(@PathVariable("id") Integer id) {
141     try {
142       return new HttpEntity<E>(database.findById(entityType, id, entityInitializers));
143     } catch (IllegalArgumentException e) {
144       throw new EntityNotFoundException(id);
145     }
146   }
147 
148 
149   protected final Initializer<E>[] entityInitializers = entityInitializers().toArray(new Initializer[0]);
150 
151 
152   protected List<? extends Initializer<? super E>> entityInitializers() {
153     return new ArrayList<>();
154   }
155 
156 
157   @RequestMapping(method = POST, consumes = {APPLICATION_JSON_VALUE})
158   @ResponseStatus(CREATED)
159   public HttpEntity<E> create(@RequestBody @Valid E entity,
160                               @Value("#{request.requestURL}") StringBuffer parentUri) {
161     entity = database.create(entity);
162     HttpHeaders headers = new HttpHeaders();
163     headers.setLocation(childLocation(parentUri, entity.getId()));
164     return new HttpEntity<E>(headers);
165   }
166 
167 
168   @RequestMapping(method = PUT, consumes = {APPLICATION_JSON_VALUE})
169   @ResponseStatus(NO_CONTENT)
170   public void update(@RequestBody @Valid E entity) {
171     database.update(entity);
172   }
173 
174 
175   @RequestMapping(value = "/{id}", method = DELETE)
176   @ResponseStatus(NO_CONTENT)
177   public void delete(@PathVariable("id") Integer id) {
178     database.delete(entityType, id);
179   }
180 
181 
182   @RequestMapping(value = "/{id}", method = PUT)
183   @ResponseStatus(NO_CONTENT)
184   public void update(@PathVariable("id") Integer id, @RequestBody E entity) {
185     entity.setId(id);
186     database.create(entity);
187   }
188 
189 
190   protected URI childLocation(StringBuffer parentUri, Object childId) {
191     UriTemplate uri = new UriTemplate(parentUri.append("/{childId}").toString());
192     return uri.expand(childId);
193   }
194 
195 
196   protected <E> HttpEntity<E> httpEntity(AbstractEntity entity, StringBuffer parentUri) {
197     HttpHeaders headers = new HttpHeaders();
198     headers.setLocation(childLocation(parentUri, entity.getId()));
199     return new HttpEntity<E>(headers);
200   }
201 
202 
203   @ResponseStatus(NOT_FOUND)
204   public static class EntityNotFoundException extends RuntimeException {
205     private static final long serialVersionUID = 1L;
206 
207 
208     public EntityNotFoundException(Integer id) {
209       super("Entity '" + id + "' not found.");
210     }
211   }
212 
213 
214   public void setDatabase(Database database) {
215     this.database = database;
216   }
217 
218   public void setFacadeBuilder(IFacadeBuilder facadeBuilder) {
219     this.facadeBuilder = facadeBuilder;
220   }
221 }
222