How to Mock repository Items in ATG - java

I am trying to create a Mock class for droplet. I am able to mock the repository calls and req.getParameter but need help on how to mock the repository item list from the repository. Below is the sample code.
for (final RepositoryItem item : skuList) {
final String skuId = (String) item.getPropertyValue("id");
final String skuType = (String) item.getPropertyValue("skuType");
if (this.isLoggingDebug()) {
this.logDebug("skuType [ " + skuType + " ]");
}
final String skuActive = (String) item.getPropertyValue("isActive");
if EJSD.equalsIgnoreCase(skuType) && (skuActive.equals("1"))) {
eSkuList.add(item);
skuCode = (String) item.getPropertyValue(ESTConstants.SKU_MISC1);
} else (PJPROMIS.equalsIgnoreCase(skuType) && skuId.contains("PP") && (skuActive.equals("1"))) {
personalSkuList.add(item);
String tmp = "";
if (skuId.lastIndexOf("-") > -1) {
tmp = skuId.substring(skuId.lastIndexOf("-") + 1);
tmp = tmp.toUpperCase();
if (this.getDefaultDisplayNameMap() != null) {
String val = this.getDefaultDisplayNameMap().get(tmp);
if (StringUtils.isNotEmpty(val)) {
displayNameMap.put(skuId, val);
} else {
val = (String) item.getPropertyValue("displayName");
displayNameMap.put(skuId, val);
}
} else {
final String val = (String) item.getPropertyValue("displayName");
displayNameMap.put(skuId, val);
}
}
}
}

There are a multitude of ways to 'mock' the list. I've been doing it this was as I feel it is more readable.
#Mock private RepositoryItem skuMockA;
#Mock private RepositoryItem skuMockB;
List<RepositoryItem> skuList = new ArrayList<RepositoryItem>();
#BeforeMethod(groups = { "unit" })
public void setup() throws Exception {
testObj = new YourDropletName();
MockitoAnnotations.initMocks(this);
skuList = new ArrayList<RepositoryItem>();
skuList.add(skuMockA);
skuList.add(skuMockB);
Mockito.when(skuMockA.getPropertyValue("id")).thenReturn("skuA");
Mockito.when(skuMockA.getPropertyValue("skuType")).thenReturn(ActiveSkuDroplet.EJSD);
Mockito.when(skuMockA.getPropertyValue(ESTConstants.SKU_MISC1)).thenReturn("skuCodeA");
Mockito.when(skuMockA.getPropertyValue("displayName")).thenReturn("skuADisplayName");
Mockito.when(skuMockB.getPropertyValue("id")).thenReturn("skuB-PP");
Mockito.when(skuMockB.getPropertyValue("skuType")).thenReturn(ActiveSkuDroplet.PJPROMIS);
Mockito.when(skuMockB.getPropertyValue(ESTConstants.SKU_MISC1)).thenReturn("skuCodeB");
Mockito.when(skuMockB.getPropertyValue("displayName")).thenReturn("skuBDisplayName");
}
So when you then call this within a test it will be something like this:
Mockito.when(someMethodThatReturnsAList).thenReturn(skuList);
So the key really is that you are not mocking the List but instead the contents of the List.

Creating a mock using mockito is a good option.
But I am here explaining a different way of mocking the repository item.
Create a common implementation for RepositoryItem, say MockRepositoryItemImpl like this in your test package.
Public MockRepositoryItemImpl implements RepositoryItem {
private Map<String, Object> properties;
MockRepositoryItemImpl(){
properties = new HashMap<>();
}
#override
public Object getPropertyValue(String propertyName){
return properties.get(propertyName);
}
#override
public void setPropertyValue(String propertyName, Object propertyValue){
properties.put(propertyName, propertyValue);
}
}
Use this implementation to create the mock object in your test case.
RepositoryItem mockSKU = new MockRepositoryItemImpl();
mockSKU.setPropertyValue("id", "sku0001");
mockSKU.setPropertyValue("displayName", "Mock SKU");
mockSKU.setPropertyValue("skuType", "Type1");
mockSKU.setPropertyValue("isActive", "1");

Related

Repository Returning Null while attempting Horizontal Scaling of a Service Class

[ISSUE] repo always returns null when I call repo methods, while stepping through, throws null pointer exception. then front end receives
500: Http failure response for http://localhost:4200/api/aiprollout/updatecsv: 500 Internal Server Error
[HAVE TRIED] Adjusting AutoWired and components and service annotations.
[QUESTIONS]
1- Does every repo method need its own service and controller method?
2- Is it okay to create a new service that uses an existing controller?
3- If this new service uses SuperCsv and I create custom CsvCellProcessors, can these cell processors also call the repo? Should these cell processors perform logic? or should it be done else where? What class annotations should these cellProcessors classes have? #Component?
Any advice is greatly appreciated, feel a little lost at this point not even sure what to do.
[CODE]
Controller:
#RestController
#EnableConfigurationProperties({SpoofingConfigurationProperties.class})
#RequestMapping(value = "")
public class AipRolloutController {
private final Logger logger = some logger
private final AipRolloutService AipRolloutService;
private final CsvParserService csvParserService;
#Autowired
public AipRolloutController(AipRolloutService aipRolloutService, CsvParserService csvParserService) {
this.AipRolloutService = aipRolloutService;
this.csvParserService = csvParserService;
}
#PostMapping(value = "/updatecsv", produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public ResponseEntity<?> processCsv(#RequestParam("csvFile") MultipartFile csvFile) throws IOException {
if (csvFile.isEmpty()) return new ResponseEntity(
responceJson("please select a file!"),
HttpStatus.NO_CONTENT
);
csvParserService.parseCsvFile(csvFile);
return new ResponseEntity(
responceJson("Successfully uploaded - " + csvFile.getOriginalFilename()),
new HttpHeaders(),
HttpStatus.CREATED
);
}
Service:
#Service
public class AipRolloutService {
private static final Logger logger = some logger
#Autowired
private AIPRolloutRepository AIPRolloutRepository;
New Csv parser Service
#Service
public class CsvParserService {
#Autowired private AipRolloutService aipRolloutService;
public CsvParserService(AipRolloutService aipRolloutService) {
this.aipRolloutService = aipRolloutService;
}
public void parseCsvFile(MultipartFile csvFile) throws IOException {
CsvMapReader csvMapReader = new CsvMapReader(new InputStreamReader(csvFile.getInputStream()), CsvPreference.STANDARD_PREFERENCE);
parseCsv(csvMapReader);
csvMapReader.close();
}
private void parseCsv(CsvMapReader csvMapReader) throws IOException {
String[] header = csvMapReader.getHeader(true);
List<String> headers = Arrays.asList(header);
verifySourceColumn(headers);
verifyPovColumn(headers);
final CellProcessor[] processors = getProcessors(headers);
Map<String, Object> csvImportMap = null;
while ((csvImportMap = csvMapReader.read(header, processors)) != null) {
CsvImportDTO csvImportDto = new CsvImportDTO(csvImportMap);
if ( activationTypeP(csvImportDto) ){
int mssValue = Integer.parseInt(csvImportDto.getMssValue());
aipRolloutService.updateAipRollout(csvImportDto.getSource(),
csvImportDto.getPov(),
csvImportDto.getActivationType(),
mssValue);
}
}
}
private CellProcessor[] getProcessors(List<String> headers) {
CellProcessor[] processors = new CellProcessor[headers.size()];
int index = 0;
for (String header : headers) {
if (header.contains(SOURCE_ID)) {
processors[index++] = new CsvSourceIdCellParser();
} else if (header.contains(POV)) {
processors[index++] = new CsvPovCellParser();
} else if (header.contains(ACTIVATION_TYPE)) {
processors[index++] = new CsvActivationTypeCellParser();
} else if (header.contains(ACTIVATION_DATE)) {
processors[index++] = new Optional();
} else if (header.contains(DEACTIVATION_DATE)) {
processors[index++] = new Optional();
} else if (header.contains(MSS_VALUE)) {
processors[index++] = new CsvMssValueCellParser();
} else {
processors[index++] = null; // throw exception? wrong header info instead of allowing null?
}
}
return processors;
}
Custom Cell Processor that calls repo and returns null
public class CsvSourceIdCellParser extends CellProcessorAdaptor {
#Autowired AIPRolloutRepository aipRolloutRepository;
public CsvSourceIdCellParser(){ super(); }
// this constructor allows other processors to be chained
public CsvSourceIdCellParser(CellProcessor next){ super(next); }
#Override
public Object execute(Object value, CsvContext csvContext) {
// throws an Exception if the input is null
validateInputNotNull(value, csvContext);
// get rid of description only need first 3 #'s
value = value.toString().substring(0,3);
// check if WH exists
if( aipRolloutRepository.dcExistsInDatabase(value.toString()) )
return value;
else
throw new RuntimeException("Check Warehouse Value, Value Not Found "
+ "Row number: " + csvContext.getRowNumber()
+ " Column number: " + csvContext.getColumnNumber());
}
}
Repository
#Repository
public class AIPRolloutRepository {
private static final Logger logger = LoggerFactory.getLogger(AIPRolloutRepository.class);
#Autowired
JdbcTemplate jdbcTemplate;
public AIPRolloutRepository() {
}
public boolean dcExistsInDatabase(String dc){
// Query for a count saves time and memory, query for distinct saves time and memory on execution
boolean hasRecord =
jdbcTemplate
.query( "select count (distinct '" + dc +"')" +
"from xxcus.XX_AIP_ROLLOUT" +
"where DC = '" + dc + "';",
new Object[] { dc },
(ResultSet rs) -> {
if (rs.next()) {
return true;
}
return false;
}
);
return hasRecord;
}

Can I create an abstraction to hide my "checking the cache" logic?

I have getResponse() methods which build a response from data fetched from the database (or pre-saved in a SavedData object). I would like to find a way to abstract out the "check savedData" logic from my getResponse() methods. Ideally, I want to figure out a way where my getResponse() methods don't even know SavedData exists, it is just hidden behind some interface. Is there a good abstraction here I can use to clean up this code?
The following is just pseudo-code. For each field which is returned in the getResponse() JSON object, they first check if that field has been saved in some SavedData and use it if it has, otherwise, they need to query the DB for the field.
interface ResponseGetter {
public Response getResponse(String userID, SavedData savedData);
}
class A implements ResponseGetter {
public Response getResponse(String userID, SavedData savedData) {
List<String> foo;
int bar;
String bizz;
foo = savedData.get(userID, "foo");
if (foo == null) {
foo = loadFooFromDB(userID);
}
bar = savedData.get(userID, "bar");
if (bar == null) {
bar = loadBarFromDB(userID);
}
bizz = savedData.get(userID, "bizz");
if (bizz == null) {
bizz = loadBizzFromDB(userID);
}
JSONObject json = new JSONObject();
json.put("foo", foo);
json.put("bar", bar);
json.put("bizz", biz);
return new Response(json);
}
private List<String> loadFooFromDB(String userID) {
List<String> returnList = new ArrayList<String>();
DB db = this.getDB();
String query = "SELECT foo FROM SomeTable WHERE user_id=" + userID;
Results results = db.executeQuery(query);
for (Result result : results) {
returnList.add(result.toString());
}
return returnList;
}
}
class B implements ResponseGetter {
public Response getResponse(String userID, SavedData savedData) {
List<String> baz;
int qux;
String corge;
baz = savedData.get(userID, "baz");
if (baz == null) {
baz = loadBazFromDB(userID);
}
qux = savedData.get(userID, "qux");
if (qux == null) {
qux = loadQuxFromDB(userID);
}
corge = savedData.get(userID, "corge");
if (corge == null) {
corge = loadCorgeFromDB(userID);
}
JSONObject json = new JSONObject();
json.put("baz", baz);
json.put("qux", qux);
json.put("corge", corge);
return new Response(json);
}
}
I would use generics and the Strategy Design Pattern.
I didn't really know what types and classes you could give up, since you provided a pseudo-code.
If I forgot to put something importante, please comment.
But I would go with something like this:
interface ResponseGetter<R> {
R getResponse(String userId, String dataToCheck, SavedData<R> savedData, Function<String, R> lambda);
}
class DB<R> {
public R executeQuery(String query) {
return null;
}
}
interface SavedData<R> {
public R get(String userId, String name);
}
class SomeClass<R> implements ResponseGetter<R> {
#Override
public R getResponse(String userId, String dataToCheck, SavedData<R> savedData, Function<String, R> lambda) {
R checkedData = savedData.get(userId, dataToCheck);
return checkedData == null ? loadDataFromDB(userId, lambda) : checkedData;
}
public R loadDataFromDB(String userId, Function<String, R> lambda) {
return lambda.apply(userId);
}
}
class SavedDataImpl<R> implements SavedData<R> {
#Override
public R get(String userId, String name) {
return null;
}
}
class Main {
public static void main(String[] args) {
// foo
SomeClass<List<String>> someClass = new SomeClass<>();
SavedDataImpl<List<String> savedDataImpl = new SavedDataImpl<>();
DB<List<String>> db = new DB<>();
List<String> foo = someClass.getResponse("1", "foo", savedDataImpl, (String userId) -> {
List<String> result = db.executeQuery("SELECT foo FROM SomeTable WHERE user_id=" + userId);
return result;
});
// bar
SomeClass<Integer> someClassTwo = new SomeClass<>();
SavedDataImpl<Integer savedDataImplTwo = new SavedDataImpl<>();
DB<Integer> dbTwo = new DB<>();
Integer bar = someClassTwo.getResponse("1", "bar", savedDataImplTwo, (String userId) -> {
Integer result = dbTwo.executeQuery("SELECT bar FROM SomeTable WHERE user_id=" + userId);
return result;
});
// bizz
SomeClass<String> someClassThree = new SomeClass<>();
SavedDataImpl<String savedDataImplThree = new SavedDataImpl<>();
DB<String> dbThree = new DB<>();
String bizz = someClassThree.getResponse("1", "bizz", savedDataImplThree, (String userId) -> {
String result = dbThree.executeQuery("SELECT bizz FROM SomeTable WHERE user_id=" + userId);
return result;
});
Map json = new HashMap();
json.put("foo", foo);
json.put("bar", bar);
json.put("bizz", bizz);
}
}
That way you are abstracting the behavior of the how to get the data and wich data to get.
I just formatted the response in a Map for simplicity.

Java service return the Result message along with Object class

Hi i have to return the ResultMessage along with my object class in my below code.
public List<EmployeeInformation> findAll(String IndexName) throws Exception {
String ResultMessage = new String();
if (Strings.isNullOrEmpty(IndexName)) {
ResultMessage= "Index name is null or empty";
} else if (isTextContainUpperCase(IndexName)) {
ResultMessage= "Index name should be in lowercase";
} else if (!checkEsConnection(client)) {
ResultMessage= "Elasticsearch deployment is not reachable";
}
SearchRequest searchRequest = new SearchRequest(IndexName);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
return getSearchResult(searchResponse);
}
private List<EmployeeInformation> getSearchResult(SearchResponse response) {
SearchHit[] searchHit = response.getHits().getHits();
List<EmployeeInformation> EmployeeDocuments = new ArrayList<>();
for (SearchHit hit : searchHit) {
EmployeeDocuments
.add(objectMapper
.convertValue(hit
.getSourceAsMap(), EmployeeInformation.class));
}
return EmployeeDocuments;
}
What is the best way to return the ResultMessage along with my employeeinformation object. Could some one Modify the above code accordingly and post your answer or suggest me how to achieve this? thanks in advance
You can return ImmutablePair<ResultMessage, List<EmployeeInformation>>.
This objects then be accessed by getLeft() and getRight() methods. However, you will need org.apache.commons lib for this. It is worth adding this lib as it has many more such utils and wrapper classes. https://mvnrepository.com/artifact/org.apache.commons/commons-lang3
This code is my suggested sample code for your requirement.
public class Test {
public static void main(String args[]) {
Test test = new Test();
String dept = "test";
Object[] result = test.searchUser(dept);
// Here must and should be follow the initailize order.
String something = null;
EmployeeInformation employee = null;
if (result.length>= 0 && result[0] instanceof String) {
something = (String) result[0];
}
if (result.length>= 1 && result[1] instanceof EmployeeInformation) {
employee = (EmployeeInformation) result[1];
}
// do something
}
public Object[] searchUser(String dept) {
Object[] result = new Object[2];
result[0] = dept;
result[1] = getUser(dept);
return result;
}
private List<EmployeeInformation> getUser(String dept) {
List<EmployeeInformation> userList = null;
// some logic for get the EmployeeInformationInformation List based on
// department.
return userList;
}
}

Why does this REST-service not return while the other does?

SITUATION
In the code below you can see 2 REST services which both should return a MessageVO. The first service (serviceThatDoesWork) returns a MessageVO as excpected, but the second service (serviceThatDoesNotWork) refuses to, it doesn't even give any output at all.
However returning a Response (java.ws.rs.core.Response) with serviceThatDoesNotWork does give an output. Even when I skip the 'doStuff'-methods and create a dummy-MessageVO that is exactly the same for each service, the 2nd one doesn't return anything.
QUESTION
Why does the 2nd service fail to return a MessageVO? It doens't return anything when I try returning a MessageVO, and nothing out of the ordinary appears in the logging.
The two services need to return exactly the same kind of thing but still one of them doesn't want to return anything, what am I not seeing here?
Could it be because of the path (and/or the amount of parameters)?
CODE
MyServices.java:
#Path("/myService")
...
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("/myPath/{param1}/{param2}/{param3}")
public MessageVO serviceThatDoesWork(#PathParam("param1") Integer param1_id, #PathParam("param2") Integer param2_id, #PathParam("param2") Integer param2_id)
{
List<SomethingVO> lstO = MyRestServiceBusiness.doStuff(param1_id, param2_id, param3_id);
//return SUCCESS or FAIL message
MessageVO msg = new MessageVO();
if(lstO.size() > 0)
{
List<String> s = new ArrayList<String>();
for(SomethingVO k : lstO)
{
s.add(k.getId().toString());
}
msg.setItem_ids(s);
msg.setMsg("SUCCESS");
}
else
{
msg.setMsg("FAIL");
}
return msg;
}
...
#GET
#Path("/myPath/{param1}/{param2}/{param3}/{param4}/.../{param15}{a:(/a/[^/]+?)?}{b:(/b/[^/]+?)?}")
public Response serviceThatDoesNotWork(#PathParam("param1")Integer param1_id, ..., #PathParam("param15") Integer param15_id,
#PathParam("a") String a_id, #PathParam("b") String b_id)
{
//PUT 'OPTIONAL' PARAMS IN A LIST
List<Integer> lstI = new ArrayList<Integer>();
String aId = a_id != null ? a_id.split("/")[2] : null;
String bId = b_id != null ? b_id.split("/")[2] : null;
if(aId != null)
{
lstI.add(Integer.parseInt(aId ));
}
if(bId != null)
{
lstI.add(Integer.parseInt(bId ));
}
//DO STUFF
String afsId = "";
if(lstI.size() > 0)
{
afsId = MyRestServiceBusiness.doStuff(param1, ..., lstI);
}
//return SUCCESS or FAIL message
MessageVO msg = new MessageVO();
if(afsId != null && !afsId.isEmpty())
{
List<String> s = new ArrayList<String>();
s.add(afsId);
msg.setItem_ids(s);
msg.setMsg("SUCCESS");
}
else
{
List<String> s = new ArrayList<String>();
for(Integer i : lstI)
{
s.add(i.toString());
}
msg.setItem_ids(s);
msg.setMsg("FAIL");
}
//WENT THROUGH ALL ABOVE CODE AS EXPECTED, MESSAGEVO HAS BEEN FILLED PROPERLY
return msg;
}
CODE MessageVO.java:
#XmlRootElement
public class MessageVO
{
private String msg;
private List<String> item_ids;
//GETTERS
#XmlElement(name = "Message")
public String getMsg() {
return msg;
}
#XmlElement(name = "Item ID's")
public List<String> getItem_ids() {
return item_ids;
}
//SETTERS
public void setMsg(String msg) {
this.msg = msg;
}
public void setItem_ids(List<String> item_ids) {
this.item_ids = item_ids;
}
If I need to provide extra information please ask, this is my first attempt at (REST-) services.
As Vaseph mentioned in a comment I just forgot the #Produces annotation in the 2nd service.

How to intercept and change sql query dynamically in mybatis

I use mybatis to perform sql queries in my project. I need to intercept sql query before executing to apply some changed dynamically. I've read about #Interseptors like this:
#Intercepts({#Signature(type= Executor.class, method = "query", args = {...})})
public class ExamplePlugin implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
return invocation.proceed();
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
}
}
And it really intercepts executions, but there is no way to change sql query since appropriate field is not writable. Should I build new instance of whole object manually to just replace sql query? Where is the right place to intercept query execution to change it dynamically? Thank.
I hope it will help you:
#Intercepts( { #Signature(type = Executor.class, method = "query", args = {
MappedStatement.class, Object.class, RowBounds.class,
ResultHandler.class
})
})
public class SelectCountSqlInterceptor2 implements Interceptor
{
public static String COUNT = "_count";
private static int MAPPED_STATEMENT_INDEX = 0;
private static int PARAMETER_INDEX = 1;
#Override
public Object intercept(Invocation invocation) throws Throwable
{
processCountSql(invocation.getArgs());
return invocation.proceed();
}
#SuppressWarnings("rawtypes")
private void processCountSql(final Object[] queryArgs)
{
if (queryArgs[PARAMETER_INDEX] instanceof Map)
{
Map parameter = (Map) queryArgs[PARAMETER_INDEX];
if (parameter.containsKey(COUNT))
{
MappedStatement ms = (MappedStatement) queryArgs[MAPPED_STATEMENT_INDEX];
BoundSql boundSql = ms.getBoundSql(parameter);
String sql = ms.getBoundSql(parameter).getSql().trim();
BoundSql newBoundSql = new BoundSql(ms.getConfiguration(),
getCountSQL(sql), boundSql.getParameterMappings(),
boundSql.getParameterObject());
MappedStatement newMs = copyFromMappedStatement(ms,
new OffsetLimitInterceptor.BoundSqlSqlSource(newBoundSql));
queryArgs[MAPPED_STATEMENT_INDEX] = newMs;
}
}
}
// see: MapperBuilderAssistant
#SuppressWarnings({ "unchecked", "rawtypes" })
private MappedStatement copyFromMappedStatement(MappedStatement ms,
SqlSource newSqlSource)
{
Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms
.getId(), newSqlSource, ms.getSqlCommandType());
builder.resource(ms.getResource());
builder.fetchSize(ms.getFetchSize());
builder.statementType(ms.getStatementType());
builder.keyGenerator(ms.getKeyGenerator());
// setStatementTimeout()
builder.timeout(ms.getTimeout());
// setParameterMap()
builder.parameterMap(ms.getParameterMap());
// setStatementResultMap()
List<ResultMap> resultMaps = new ArrayList<ResultMap>();
String id = "-inline";
if (ms.getResultMaps() != null)
{
id = ms.getResultMaps().get(0).getId() + "-inline";
}
ResultMap resultMap = new ResultMap.Builder(null, id, Long.class,
new ArrayList()).build();
resultMaps.add(resultMap);
builder.resultMaps(resultMaps);
builder.resultSetType(ms.getResultSetType());
// setStatementCache()
builder.cache(ms.getCache());
builder.flushCacheRequired(ms.isFlushCacheRequired());
builder.useCache(ms.isUseCache());
return builder.build();
}
private String getCountSQL(String sql)
{
String lowerCaseSQL = sql.toLowerCase().replace("\n", " ").replace("\t", " ");
int index = lowerCaseSQL.indexOf(" order ");
if (index != -1)
{
sql = sql.substring(0, index);
}
return "SELECT COUNT(*) from ( select 1 as col_c " + sql.substring(lowerCaseSQL.indexOf(" from ")) + " ) cnt";
}
#Override
public Object plugin(Object target)
{
return Plugin.wrap(target, this);
}
#Override
public void setProperties(Properties properties)
{
}
}
You may consider using a string template library (eg Velocity, Handlebars, Mustache) to help you
As of to date, there is even MyBatis-Velocity (http://mybatis.github.io/velocity-scripting/) to help you to do scripting for the sql.
Depending on the changes you want to make, you may want to use the dynamic sql feature of mybatis 3

Categories