Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
Lukas Wiest
unified-ticketing
Commits
b841acdd
Unverified
Commit
b841acdd
authored
Nov 04, 2020
by
Lukas Wiest
🚂
Browse files
feat: import first state of library code
parent
dd0df320
Changes
23
Show whitespace changes
Inline
Side-by-side
pom.xml
0 → 100644
View file @
b841acdd
<?xml version="1.0" encoding="UTF-8"?>
<project
xmlns=
"http://maven.apache.org/POM/4.0.0"
xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=
"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
>
<modelVersion>
4.0.0
</modelVersion>
<groupId>
org.example
</groupId>
<artifactId>
unified-ticketing
</artifactId>
<version>
${buildNumber}
</version>
<properties>
<buildNumber>
devel
</buildNumber>
<java.version>
8
</java.version>
<project.build.sourceEncoding>
UTF-8
</project.build.sourceEncoding>
</properties>
<profiles>
<profile>
<id>
ci
</id>
<activation>
<property><name>
env.BUILD_NUMBER
</name></property>
</activation>
<properties>
<buildNumber>
${env.BUILD_NUMBER}
</buildNumber>
</properties>
</profile>
</profiles>
<dependencies>
<!-- <!– https://mvnrepository.com/artifact/com.squareup.retrofit2/retrofit –>-->
<!-- <dependency>-->
<!-- <groupId>com.squareup.retrofit2</groupId>-->
<!-- <artifactId>retrofit</artifactId>-->
<!-- <version>2.9.0</version>-->
<!-- </dependency>-->
<!-- <!– https://mvnrepository.com/artifact/com.squareup.retrofit2/converter-jackson –>-->
<!-- <dependency>-->
<!-- <groupId>com.squareup.retrofit2</groupId>-->
<!-- <artifactId>converter-jackson</artifactId>-->
<!-- <version>2.9.0</version>-->
<!-- </dependency>-->
<!-- https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp -->
<dependency>
<groupId>
com.squareup.okhttp3
</groupId>
<artifactId>
okhttp
</artifactId>
<version>
4.9.0
</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>
com.fasterxml.jackson.core
</groupId>
<artifactId>
jackson-databind
</artifactId>
<version>
2.11.3
</version>
</dependency>
<dependency>
<groupId>
org.junit.jupiter
</groupId>
<artifactId>
junit-jupiter-api
</artifactId>
<version>
5.7.0
</version>
<scope>
test
</scope>
</dependency>
<dependency>
<groupId>
org.junit.jupiter
</groupId>
<artifactId>
junit-jupiter-engine
</artifactId>
<version>
5.7.0
</version>
<scope>
test
</scope>
</dependency>
<dependency>
<groupId>
org.mockito
</groupId>
<artifactId>
mockito-core
</artifactId>
<version>
3.6.0
</version>
<scope>
test
</scope>
</dependency>
<dependency>
<groupId>
org.mockito
</groupId>
<artifactId>
mockito-junit-jupiter
</artifactId>
<version>
3.6.0
</version>
<scope>
test
</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>
org.apache.maven.plugins
</groupId>
<artifactId>
maven-compiler-plugin
</artifactId>
<version>
3.8.1
</version>
<configuration>
<source>
1.8
</source>
<target>
1.8
</target>
</configuration>
</plugin>
<plugin>
<groupId>
org.apache.maven.plugins
</groupId>
<artifactId>
maven-surefire-plugin
</artifactId>
<version>
2.21.0
</version>
<dependencies>
<dependency>
<groupId>
org.junit.platform
</groupId>
<artifactId>
junit-platform-surefire-provider
</artifactId>
<version>
1.3.2
</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
\ No newline at end of file
src/main/java/de/hft/unifiedticketing/core/Filter.java
0 → 100644
View file @
b841acdd
package
de.hft.unifiedticketing.core
;
import
java.util.*
;
import
java.util.logging.Level
;
import
java.util.logging.Logger
;
/**
* <b>Filtered Ticket Request</b><br>
*<br>
* This class provides a builder like interface,
* to get a filtered list of Tickets.<br>
* <br>
* It provides some basic filter options by itself.
* The request logic has to be provided from each System specific
* implementation for it.
*
* @param <T> The type of Ticket this Filter implementation delivers
* @param <F> The type of the System specific implementation of this class
*/
public
abstract
class
Filter
<
T
extends
Ticket
,
F
extends
Filter
<?,?>>
{
private
static
Logger
logger
=
Logging
.
getLogger
(
Filter
.
class
.
getName
());
protected
Map
<
String
,
Object
>
setFilters
;
// = new HashMap<>();
protected
enum
FilterNames
{
ASSIGNEEID
,
ASSIGNEENAME
,
DESCRIPTION_CONTAIN
,
DESCRIPTION_MATCH
,
IDS
,
LABELS
,
OPEN
,
PAGE
,
PAGINATION
,
TITLE_CONTAINS
,
TITLE_MATCH
,
}
public
Filter
()
{
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"%s request builder started"
,
this
.
getClass
().
getSimpleName
()
));
setFilters
=
new
HashMap
<>();
}
/**
* Add assignee id to query
* @return this query builder
*/
public
F
withAssigneeId
(
String
id
)
{
addToSet
(
FilterNames
.
ASSIGNEEID
,
this
.
setFilters
,
id
);
return
(
F
)
this
;
}
/**
* Add assignee name to query
* @return this query builder
*/
public
F
withAssigneeName
(
String
name
)
{
addToSet
(
FilterNames
.
ASSIGNEENAME
,
this
.
setFilters
,
name
);
return
(
F
)
this
;
}
/**
* adds description contain constraint
* @param substring element to be contained in the description
* @return this query builder
*/
public
F
withDescriptionContain
(
String
substring
)
{
setFilters
.
put
(
FilterNames
.
DESCRIPTION_CONTAIN
.
name
(),
substring
);
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"added constraint: %s %s"
,
FilterNames
.
DESCRIPTION_CONTAIN
.
name
(),
substring
));
return
(
F
)
this
;
}
/**
* regex that the whole description must match
* @param regex fully qualified regex
* @return this query builder
*/
public
F
withDescriptionMatch
(
String
regex
)
{
setFilters
.
put
(
FilterNames
.
DESCRIPTION_MATCH
.
name
(),
regex
);
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"added constraint: %s %s"
,
FilterNames
.
DESCRIPTION_MATCH
.
name
(),
regex
));
return
(
F
)
this
;
}
/**
* adds an ticket id to the constraint list
* @param id
* @return this query builder
*/
public
F
withId
(
String
id
)
{
addToSet
(
FilterNames
.
IDS
,
this
.
setFilters
,
id
);
return
(
F
)
this
;
}
/**
* adds a constraint for only open considered tickets
* @return this query builder
*/
public
F
isOpen
()
{
setFilters
.
put
(
FilterNames
.
OPEN
.
name
(),
true
);
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"added constraint: %s %s"
,
FilterNames
.
OPEN
.
name
(),
true
));
return
(
F
)
this
;
}
/**
* adds a constraint for only closed considered tickets
* @return this query builder
*/
public
F
isClosed
()
{
setFilters
.
put
(
FilterNames
.
OPEN
.
name
(),
false
);
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"added constraint: %s %s"
,
FilterNames
.
OPEN
.
name
(),
false
));
return
(
F
)
this
;
}
/**
* add a label to the constraints list<br>
*<br>
* multiple calls of this delivers only tickets holding ALL given labels.
* @param label
* @return this query builder
*/
public
F
withLabel
(
String
label
)
{
addToSet
(
FilterNames
.
LABELS
,
this
.
setFilters
,
label
);
return
(
F
)
this
;
}
/**
* set the page number of the pagination<br>
*<br>
* <b>Attention:</b> some systems paginate by default.
* You can check that with {@link TicketSystem}s "has..." methods.
* @param page
* @return this query builder
*/
public
F
setPage
(
int
page
)
{
this
.
setFilters
.
put
(
FilterNames
.
PAGE
.
name
(),
page
);
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"set page: %d"
,
page
));
return
(
F
)
this
;
}
/**
* defines the page size of a single pagination page<br>
*<br>
* <b>Attention:</b> some systems have a default in place, if nothing custom is set.
* You can check that with {@link TicketSystem}s "has..." methods.
* @param size
* @return this query builder
*/
public
F
setPageSize
(
int
size
)
{
this
.
setFilters
.
put
(
FilterNames
.
PAGINATION
.
name
(),
size
);
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"set pagination size: %d"
,
size
));
return
(
F
)
this
;
}
/**
* adds a constraint for title containing the given string
* @param substring string the title must contain
* @return this query builder
*/
public
F
withTitleContain
(
String
substring
)
{
setFilters
.
put
(
FilterNames
.
TITLE_CONTAINS
.
name
(),
substring
);
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"added constraint: %s %s"
,
FilterNames
.
TITLE_CONTAINS
.
name
(),
substring
));
return
(
F
)
this
;
}
/**
* adds a regex constraint, that the title has to match
* @param regex fully qualified regex
* @return this query builder
*/
public
F
withTitleMatch
(
String
regex
)
{
setFilters
.
put
(
FilterNames
.
TITLE_MATCH
.
name
(),
regex
);
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"added constraint: %s %s"
,
FilterNames
.
TITLE_MATCH
.
name
(),
regex
));
return
(
F
)
this
;
}
/**
* <b>Requesting the tickets</b><br>
*<br>
* This will prepare the request according to the system specific implementation,
* run the request, if needed post-filter the results and form it into a List
* @return List of Tickets, matching all previously set constraints
*/
public
abstract
List
<
T
>
get
();
protected
static
void
addToSet
(
FilterNames
filterName
,
Map
<
String
,
Object
>
map
,
String
newValue
)
{
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"attempting to add new %s constraint"
,
filterName
.
name
()));
Set
<
String
>
values
;
if
(
map
.
containsKey
(
filterName
.
name
()))
{
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"existing %s constraints found"
,
filterName
.
name
()));
Object
stored
=
map
.
get
(
filterName
.
name
());
if
(
stored
instanceof
Set
<?>)
{
values
=
(
Set
<
String
>)
stored
;
}
else
{
logger
.
log
(
Level
.
WARNING
,
String
.
format
(
"found filter object under key %s was not from Type %s but %s"
,
filterName
.
name
(),
Set
.
class
.
getName
(),
stored
.
getClass
().
getName
()));
values
=
new
HashSet
<>();
map
.
put
(
filterName
.
name
(),
values
);
logger
.
log
(
Level
.
INFO
,
String
.
format
(
"replaced wrong typed filter object under key %s with new instance of type %s"
,
filterName
.
name
(),
Set
.
class
.
getName
()));
}
}
else
{
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"no previous %s constraints found"
,
filterName
));
values
=
new
HashSet
<>();
map
.
put
(
filterName
.
name
(),
values
);
}
values
.
add
(
newValue
);
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"added constraint: %s %s"
,
filterName
.
name
(),
newValue
));
}
}
src/main/java/de/hft/unifiedticketing/core/Logging.java
0 → 100644
View file @
b841acdd
package
de.hft.unifiedticketing.core
;
import
java.util.Date
;
import
java.util.logging.Formatter
;
import
java.util.logging.*
;
public
class
Logging
{
private
static
Handler
handler
;
private
static
Logger
mainLogger
;
private
static
Logger
logger
;
static
{
mainLogger
=
Logger
.
getLogger
(
"de.hft.unifiedticketing"
);
mainLogger
.
setUseParentHandlers
(
false
);
handler
=
new
ConsoleHandler
();
handler
.
setFormatter
(
new
SimpleFormatter
()
{
private
static
final
String
format
=
"[%1$tFT%1$tT%1$tz] [%2$-7s] [%3$s.%4$s] %5$s%n"
;
@Override
public
synchronized
String
format
(
LogRecord
lr
)
{
return
String
.
format
(
format
,
new
Date
(
lr
.
getMillis
()),
lr
.
getLevel
(),
lr
.
getSourceClassName
(),
lr
.
getSourceMethodName
(),
lr
.
getMessage
()
);
}
});
handler
.
setLevel
(
Level
.
INFO
);
mainLogger
.
addHandler
(
handler
);
logger
=
getLogger
(
Logging
.
class
.
getName
());
}
public
static
Logger
getLogger
(
String
name
)
{
return
Logger
.
getLogger
(
name
);
}
public
static
void
setFormatter
(
Formatter
formatter
)
{
handler
.
setFormatter
(
formatter
);
}
public
static
void
setLevel
(
Level
level
)
{
handler
.
setLevel
(
level
);
mainLogger
.
setLevel
(
level
);
}
public
static
void
test
(
String
message
)
{
logger
.
log
(
Level
.
FINEST
,
message
);
logger
.
log
(
Level
.
FINER
,
message
);
logger
.
log
(
Level
.
FINE
,
message
);
logger
.
log
(
Level
.
INFO
,
message
);
logger
.
log
(
Level
.
WARNING
,
message
);
logger
.
log
(
Level
.
SEVERE
,
message
);
}
}
src/main/java/de/hft/unifiedticketing/core/RegisteredSystems.java
0 → 100644
View file @
b841acdd
package
de.hft.unifiedticketing.core
;
import
de.hft.unifiedticketing.systems.gitlab.GitlabTicketSystemBuilder
;
import
java.util.logging.Level
;
import
java.util.logging.Logger
;
/**
* This class is where for each supported system a method is placed, that returns a new Builder instance for it.
*/
public
class
RegisteredSystems
{
private
static
Logger
logger
=
Logging
.
getLogger
(
RegisteredSystems
.
class
.
getName
());
private
static
RegisteredSystems
instance
=
null
;
protected
RegisteredSystems
()
{
}
protected
static
RegisteredSystems
getInstance
()
{
if
(
instance
==
null
)
{
instance
=
new
RegisteredSystems
();
logger
.
log
(
Level
.
FINEST
,
"Singleton instance created"
);
}
return
instance
;
}
/**
* Starts the builder mechanism for a GitLab connection
*/
public
GitlabTicketSystemBuilder
gitlab
()
{
return
new
GitlabTicketSystemBuilder
();
}
}
src/main/java/de/hft/unifiedticketing/core/Ticket.java
0 → 100644
View file @
b841acdd
package
de.hft.unifiedticketing.core
;
import
de.hft.unifiedticketing.exceptions.AssertionException
;
import
java.util.*
;
import
java.util.logging.Level
;
import
java.util.logging.Logger
;
/**
* <b>Ticket representation</b><br/>
* <br/>
* This class is the generic representation of a ticket for the supported systems.
* It defines a few fields as well as getters and setters for them.<br/>
* <br/>
* Next there's a save method, to persist your changes back into the real ticket system.
* This is is a method implemented in each of the system specific child classes.
*
* @param <TS> Ticketsystem a ticket instance belongs to
* @param <T> child implementation of this class an instance belongs to
*/
public
abstract
class
Ticket
<
TS
extends
TicketSystem
,
T
extends
Ticket
>
{
private
static
Logger
logger
=
Logging
.
getLogger
(
Ticket
.
class
.
getName
());
protected
Set
<
TicketAssignee
>
assignees
;
protected
String
description
;
protected
String
id
;
protected
Set
<
String
>
labels
;
protected
boolean
open
;
protected
String
title
;
protected
Set
<
String
>
updatedFields
;
protected
TS
parent
;
protected
enum
FieldNames
{
ASSIGNEES
,
DESCRIPTION
,
ID
,
LABELS
,
OPEN
,
TITLE
,
}
/**
* @param parent Ticketsystem instance, this ticket is from
*/
protected
Ticket
(
TS
parent
)
{
if
(
parent
==
null
)
{
logger
.
log
(
Level
.
SEVERE
,
"every ticket instance needs a belonging ticket system, but received null!"
);
throw
new
AssertionException
(
String
.
format
(
"Got null as parent ticket system on creating new %s"
,
this
.
getClass
().
getSimpleName
()));
}
this
.
assignees
=
new
HashSet
<>();
this
.
labels
=
new
HashSet
<>();
this
.
open
=
true
;
this
.
parent
=
parent
;
this
.
updatedFields
=
new
HashSet
<>();
}
public
Set
<?
extends
TicketAssignee
>
getAssignees
()
{
if
(
this
.
assignees
==
null
)
return
new
HashSet
<>();
// TODO remove as assignees should NEVER turn null
else
return
this
.
assignees
;
}
/**
* adds an new assignee to the ticket, by the systems api primary identifier for assignees
* @return this ticket
*/
public
abstract
T
addAssignee
(
String
identifier
);
/**
* clears all assignees
* @return this ticket
*/
public
T
clearAssignees
()
{
this
.
assignees
.
clear
();
this
.
updatedFields
.
add
(
FieldNames
.
ASSIGNEES
.
name
());
return
(
T
)
this
;
}
/**
* removes a single assignee identified by the platforms api primary identifier for assignees
* @return this ticket
*/
public
abstract
T
removeAssignee
(
String
identifier
);
public
String
getDescription
()
{
return
description
;
}
/**
* replaces the current ticket description with the new given one
* @return this ticket
*/
public
T
setDescription
(
String
description
)
{
this
.
description
=
description
;
this
.
updatedFields
.
add
(
FieldNames
.
DESCRIPTION
.
name
());
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"[Ticket: %s] %s marked for update"
,
this
.
id
,
FieldNames
.
DESCRIPTION
.
name
()
));
return
(
T
)
this
;
}
public
String
getId
()
{
return
id
;
}
/**
* Adds the given label, preserving the yet assigned ones
* @return this ticket
*/
public
T
addLabel
(
String
label
)
{
this
.
labels
.
add
(
label
);
this
.
updatedFields
.
add
(
FieldNames
.
LABELS
.
name
());
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"[Ticket: %s] %s marked for update"
,
this
.
id
,
FieldNames
.
LABELS
.
name
()
));
return
(
T
)
this
;
}
public
Set
<
String
>
getLabels
()
{
return
labels
;
}
/**
* replaces all labels of the ticket with the new ones
* @param labels collection to replace old ones
* @return this ticket
*/
public
T
setLabels
(
Set
<
String
>
labels
)
{
this
.
labels
=
labels
;
this
.
updatedFields
.
add
(
FieldNames
.
LABELS
.
name
());
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"[Ticket: %s] %s marked for update"
,
this
.
id
,
FieldNames
.
LABELS
.
name
()
));
return
(
T
)
this
;
}
/**
* replaces all labels of the ticket with the new ones
* @param labels array to replace old ones
* @return this ticket
*/
public
T
setLabels
(
String
[]
labels
)
{
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"[Ticket %s] transforming labels Array to Set"
,
this
.
id
));
return
this
.
setLabels
(
new
HashSet
<>(
Arrays
.
asList
(
labels
)));
}
public
boolean
isOpen
()
{
return
open
;
}
/**
* set's the ticket state to a open considered state
* @return this ticket
*/
public
T
open
()
{
this
.
updatedFields
.
add
(
FieldNames
.
OPEN
.
name
());
this
.
open
=
true
;
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"[Ticket: %s] %s marked for update"
,
this
.
id
,
FieldNames
.
OPEN
.
name
()
));
return
(
T
)
this
;
}
/**
* set's the ticket state to a closed considered state
* @return this ticket
*/
public
T
close
()
{
this
.
updatedFields
.
add
(
FieldNames
.
OPEN
.
name
());
this
.
open
=
false
;
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"[Ticket: %s] %s marked for update"
,
this
.
id
,
FieldNames
.
OPEN
.
name
()
));
return
(
T
)
this
;
}
public
TS
getParent
()
{
return
parent
;
}
public
String
getTitle
()
{
return
title
;
}
/**
* replaces old title
* @return this ticket
*/
public
T
setTitle
(
String
title
)
{
this
.
updatedFields
.
add
(
FieldNames
.
TITLE
.
name
());
this
.
title
=
title
;
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"[Ticket: %s] %s marked for update"
,
this
.
id
,
FieldNames
.
TITLE
.
name
()
));
return
(
T
)
this
;
}
/**
* persist all fields marked as tampered into a request to update them in the real ticket system
* @return the changed ticket requested from the real ticket system after updating
*/
public
abstract
T
save
();
@Override
public
boolean
equals
(
Object
o
)
{
if
(
this
==
o
)
return
true
;
if
(
o
==
null
||
!
Objects
.
equals
(
o
.
getClass
(),
this
.
getClass
()))
return
false
;
Ticket
<?,
?>
ticket
=
(
Ticket
<?,
?>)
o
;
return
Objects
.
equals
(
id
,
ticket
.
id
)
&&
parent
==
ticket
.
parent
;
}
@Override
public
int
hashCode
()
{
return
Objects
.
hash
(
id
,
parent
);
}
@Override
public
String
toString
()
{
return
"Ticket{"
+
"assignees: "
+
assignees
+
", description='"
+
description
+
'\''
+
", id='"
+
id
+
'\''
+
", labels="
+
labels
+
", open="
+
open
+
", title='"
+
title
+
'\''
+
'}'
;
}
}
src/main/java/de/hft/unifiedticketing/core/TicketAssignee.java
0 → 100644
View file @
b841acdd
package
de.hft.unifiedticketing.core
;
import
java.util.Objects
;
public
class
TicketAssignee
{
public
final
String
email
;
public
final
String
id
;
public
final
String
fullName
;
public
final
String
username
;
protected
TicketAssignee
(
String
email
,
String
id
,
String
username
,
String
fullName
)
{
this
.
email
=
email
;
this
.
id
=
id
;
this
.
fullName
=
fullName
;
this
.
username
=
username
;
}
@Override
public
boolean
equals
(
Object
o
)
{
if
(
this
==
o
)
return
true
;
if
(!(
o
instanceof
TicketAssignee
))
return
false
;
TicketAssignee
that
=
(
TicketAssignee
)
o
;
return
Objects
.
equals
(
id
,
that
.
id
);
}
@Override
public
int
hashCode
()
{
return
Objects
.
hash
(
id
);
}
@Override
public
String
toString
()
{
return
"TicketAssignee{"
+
"email='"
+
email
+
'\''
+
", id='"
+
id
+
'\''
+
", fullName='"
+
fullName
+
'\''
+
", username='"
+
username
+
'\''
+
'}'
;
}
}
src/main/java/de/hft/unifiedticketing/core/TicketBuilder.java
0 → 100644
View file @
b841acdd
package
de.hft.unifiedticketing.core
;
import
de.hft.unifiedticketing.exceptions.AssertionException
;
import
java.util.*
;
import
java.util.logging.Level
;
import
java.util.logging.Logger
;
import
java.util.stream.Collectors
;
/**
* <b>Ticket Builder</b><br/>
* <br/>
* This class provides a builder mechanism, to define a new Ticket and create it afterwards.
* @param <B> implementation of this class this instance belongs to
* @param <T> type of ticket implementation this builder will create
* @param <TS> type of ticket system this builder belongs to
*/
public
abstract
class
TicketBuilder
<
B
extends
TicketBuilder
,
T
extends
Ticket
,
TS
extends
TicketSystem
>
{
private
static
Logger
logger
=
Logging
.
getLogger
(
TicketBuilder
.
class
.
getName
());
public
final
TS
parent
;
protected
Set
<
Integer
>
assignees
;
protected
String
description
;
protected
Set
<
String
>
labels
;
protected
String
title
;
protected
TicketBuilder
(
TS
parent
)
{
if
(
parent
==
null
)
{
logger
.
log
(
Level
.
SEVERE
,
"every ticket builder instance needs a belonging ticket system, but received null!"
);
throw
new
AssertionException
(
String
.
format
(
"Got null as parent ticket system on creating new %s"
,
this
.
getClass
().
getSimpleName
()));
}
this
.
parent
=
parent
;
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"%s instantiated"
,
this
.
getClass
().
getSimpleName
()));
}
public
abstract
B
assignees
(
String
...
identifiers
);
public
B
description
(
String
description
)
{
this
.
description
=
description
;
logger
.
log
(
Level
.
FINEST
,
"description added to new Ticket"
);
return
(
B
)
this
;
}
public
B
labels
(
Set
<
String
>
labels
)
{
this
.
labels
=
labels
;
logger
.
log
(
Level
.
FINEST
,
"labels added to new Ticket"
);
return
(
B
)
this
;
}
public
B
labels
(
String
[]
labels
)
{
logger
.
log
(
Level
.
FINEST
,
"transforming labels Array to Set"
);
return
this
.
labels
(
new
HashSet
<>(
Arrays
.
asList
(
labels
)));
}
public
B
title
(
String
title
)
{
this
.
title
=
title
;
logger
.
log
(
Level
.
FINEST
,
"title added to new Ticket"
);
return
(
B
)
this
;
}
/**
* persists the defined ticket into a create request to the ticket system connected
* @return newly created ticket
*/
public
abstract
T
create
();
}
src/main/java/de/hft/unifiedticketing/core/TicketSystem.java
0 → 100644
View file @
b841acdd
package
de.hft.unifiedticketing.core
;
import
de.hft.unifiedticketing.exceptions.AssertionException
;
import
de.hft.unifiedticketing.systems.gitlab.GitlabTicketSystem
;
import
java.util.HashMap
;
import
java.util.logging.Level
;
import
java.util.logging.Logger
;
import
java.util.regex.Matcher
;
import
java.util.regex.Pattern
;
/**
* Defines the representation of a ticket system connection<br/>
* <br/>
*
* @param <T> ticket implementation this ticket system uses
* @param <TS> implementation of this class an instance belongs to
* @param <TB> ticket builder this ticket system uses
* @param <F> filter implementation this ticket system uses
*/
public
abstract
class
TicketSystem
<
T
extends
Ticket
,
TS
extends
TicketSystem
,
TB
extends
TicketBuilder
,
F
extends
Filter
>
{
private
static
Logger
logger
=
Logging
.
getLogger
(
TicketSystem
.
class
.
getName
());
protected
HashMap
<
String
,
String
>
configuration
=
new
HashMap
<>();
/**
* Global configuration options for ticket system instances, settable with the method config
*/
public
enum
ConfigurationOptions
{
RETURN_NULL_ON_ERROR
}
protected
TicketSystem
()
{
}
/**
* creates a ticketsystem from an uri<br/>
* <br/>
* a valid uri for this library always starts with "unifiedticketing" followed by a colon.
* Next is the identifier for the desired ticket system.
* The available systems and what identifier they need, can be found in the documentation.
* <br/>
* example: {@code "unifiedticketing:<systemidentifier>:<system specific things>"}
*
* @return ticket system instance
*/
public
static
TicketSystem
fromUri
(
String
uri
)
{
Pattern
pattern
=
Pattern
.
compile
(
"^(unifiedticketing):([a-zA-Z0-9_]+):(.*)$"
);
Matcher
matcher
=
pattern
.
matcher
(
uri
);
if
(!
matcher
.
matches
())
{
logger
.
log
(
Level
.
SEVERE
,
"given URI not for unifiedticketing!"
);
throw
new
AssertionException
(
String
.
format
(
"URI %s not for unifiedticketing"
,
uri
));
}
switch
(
matcher
.
group
(
2
))
{
case
"gitlab"
:
return
GitlabTicketSystem
.
fromUri
(
matcher
.
group
(
3
));
default
:
logger
.
log
(
Level
.
SEVERE
,
String
.
format
(
"Unknown system identifier: %s"
,
matcher
.
group
(
2
)
));
throw
new
AssertionException
(
String
.
format
(
"no supported system implementation found for %s"
,
matcher
.
group
(
2
)
));
}
}
/**
* @return builder selection
*/
public
static
RegisteredSystems
fromBuilder
()
{
return
RegisteredSystems
.
getInstance
();
}
// Instance definition =====================================================================//
public
String
apiKey
;
public
String
baseUrl
;
public
String
password
;
public
String
username
;
/**
* sets a value configuration
* @param option configuration to set
* @param value value to be stored
* @return this ticket system
*/
public
TS
config
(
ConfigurationOptions
option
,
String
value
)
{
this
.
configuration
.
put
(
option
.
name
(),
value
);
return
(
TS
)
this
;
}
/**
* enables/disables a configuration
* @param option configuration to set
* @param value true to enable the setting
* @return this ticket system
*/
public
TS
config
(
ConfigurationOptions
option
,
boolean
value
)
{
return
config
(
option
,
String
.
valueOf
(
value
));
}
/**
* @param option configuration to check
* @return true if configuration has stored "true"
*/
public
boolean
getConfigTrue
(
ConfigurationOptions
option
)
{
return
Boolean
.
parseBoolean
(
this
.
configuration
.
get
(
option
.
name
()));
}
/**
* @param option configuration to get stored value
* @return stored value of given config
*/
public
Object
getConfigValue
(
ConfigurationOptions
option
)
{
return
this
.
configuration
.
get
(
option
.
name
());
}
/**
* starts ticket builder for this ticket system
*/
public
abstract
TB
createTicket
();
/**
* starts ticket filter for this ticket system
*/
public
abstract
F
find
();
// public abstract List<T> getAllTickets();
/**
* requests a single ticket by it's id
*/
public
abstract
T
getTicketById
(
String
id
);
public
T
getTicketById
(
int
id
)
{
logger
.
log
(
Level
.
FINEST
,
"transforming id to String"
);
return
this
.
getTicketById
(
String
.
valueOf
(
id
));
}
public
abstract
boolean
hasAssigneeSupport
();
public
abstract
boolean
hasDefaultPagination
();
public
abstract
boolean
hasLabelSupport
();
public
abstract
boolean
hasPaginationSupport
();
public
abstract
boolean
hasReturnNullOnErrorSupport
();
// public static boolean hasUriBuildSupport();
}
src/main/java/de/hft/unifiedticketing/core/TicketSystemBuilder.java
0 → 100644
View file @
b841acdd
package
de.hft.unifiedticketing.core
;
import
de.hft.unifiedticketing.exceptions.AssertionException
;
import
java.util.logging.Level
;
import
java.util.logging.Logger
;
/**
* Builder class for ticket systems
* @param <B> type of builder implementation
* @param <TS> type of ticket system this builder creates
*/
public
abstract
class
TicketSystemBuilder
<
B
extends
TicketSystemBuilder
,
TS
extends
TicketSystem
>
{
private
static
Logger
logger
=
Logging
.
getLogger
(
TicketSystemBuilder
.
class
.
getName
());
public
String
baseUrl
;
/**
* set's the base url of a system
* @param url base url without protocol prefix
* @return this builder
*/
public
B
withBaseUrl
(
String
url
)
{
if
(
url
.
startsWith
(
"http://"
)
||
url
.
startsWith
(
"https://"
))
{
logger
.
log
(
Level
.
SEVERE
,
"base url not allowed to start with protocol!"
);
throw
new
AssertionException
(
String
.
format
(
"url %s started with protocol which is not allowed"
,
url
));
}
baseUrl
=
url
;
logger
.
log
(
Level
.
FINEST
,
"set base url to "
+
baseUrl
);
return
(
B
)
this
;
}
/**
* creates a new ticket system instance from the previously set information
* @return new ticket system instance
*/
public
abstract
TS
build
();
}
src/main/java/de/hft/unifiedticketing/exceptions/AssertionException.java
0 → 100644
View file @
b841acdd
package
de.hft.unifiedticketing.exceptions
;
public
class
AssertionException
extends
UnifiedticketingException
{
public
AssertionException
()
{
super
();
}
public
AssertionException
(
String
msg
)
{
super
(
msg
);
}
public
AssertionException
(
String
msg
,
Throwable
cause
)
{
super
(
msg
,
cause
);
}
public
AssertionException
(
Throwable
suppressed
)
{
super
(
suppressed
);
}
}
src/main/java/de/hft/unifiedticketing/exceptions/DeserializationException.java
0 → 100644
View file @
b841acdd
package
de.hft.unifiedticketing.exceptions
;
public
class
DeserializationException
extends
UnifiedticketingException
{
public
DeserializationException
()
{
super
();
}
public
DeserializationException
(
String
msg
)
{
super
(
msg
);
}
public
DeserializationException
(
String
msg
,
Throwable
cause
)
{
super
(
msg
,
cause
);
}
public
DeserializationException
(
Throwable
suppressed
)
{
super
(
suppressed
);
}
}
src/main/java/de/hft/unifiedticketing/exceptions/HttpReqeustException.java
0 → 100644
View file @
b841acdd
package
de.hft.unifiedticketing.exceptions
;
public
class
HttpReqeustException
extends
UnifiedticketingException
{
public
HttpReqeustException
()
{
super
();
}
public
HttpReqeustException
(
String
msg
)
{
super
(
msg
);
}
public
HttpReqeustException
(
String
msg
,
Throwable
casue
)
{
super
(
msg
,
casue
);
}
public
HttpReqeustException
(
Throwable
suppressed
)
{
super
(
suppressed
);
}
}
src/main/java/de/hft/unifiedticketing/exceptions/HttpResponseException.java
0 → 100644
View file @
b841acdd
package
de.hft.unifiedticketing.exceptions
;
public
class
HttpResponseException
extends
UnifiedticketingException
{
public
final
int
status
;
public
HttpResponseException
(
int
status
)
{
super
();
this
.
status
=
status
;
}
public
HttpResponseException
(
String
msg
,
int
status
)
{
super
(
msg
);
this
.
status
=
status
;
}
public
HttpResponseException
(
String
msg
,
Throwable
cause
,
int
status
)
{
super
(
msg
,
cause
);
this
.
status
=
status
;
}
public
HttpResponseException
(
Throwable
suppressed
,
int
status
)
{
super
(
suppressed
);
this
.
status
=
status
;
}
}
src/main/java/de/hft/unifiedticketing/exceptions/SerializationException.java
0 → 100644
View file @
b841acdd
package
de.hft.unifiedticketing.exceptions
;
public
class
SerializationException
extends
UnifiedticketingException
{
public
SerializationException
()
{
super
();
}
public
SerializationException
(
String
msg
)
{
super
(
msg
);
}
public
SerializationException
(
String
msg
,
Throwable
cause
)
{
super
(
msg
,
cause
);
}
public
SerializationException
(
Throwable
suppressed
)
{
super
(
suppressed
);
}
}
src/main/java/de/hft/unifiedticketing/exceptions/UnifiedticketingException.java
0 → 100644
View file @
b841acdd
package
de.hft.unifiedticketing.exceptions
;
public
class
UnifiedticketingException
extends
RuntimeException
{
public
UnifiedticketingException
()
{
super
();
}
public
UnifiedticketingException
(
String
msg
)
{
super
(
msg
);
}
public
UnifiedticketingException
(
String
msg
,
Throwable
cause
)
{
super
(
msg
,
cause
);
}
public
UnifiedticketingException
(
Throwable
suppressed
)
{
this
(
suppressed
.
getMessage
());
this
.
addSuppressed
(
suppressed
);
}
}
src/main/java/de/hft/unifiedticketing/exceptions/UnsupportedFunctionException.java
0 → 100644
View file @
b841acdd
package
de.hft.unifiedticketing.exceptions
;
public
class
UnsupportedFunctionException
extends
UnifiedticketingException
{
public
UnsupportedFunctionException
()
{
super
();
}
public
UnsupportedFunctionException
(
String
msg
)
{
super
(
msg
);
}
public
UnsupportedFunctionException
(
String
msg
,
Throwable
cause
)
{
super
(
msg
,
cause
);
}
public
UnsupportedFunctionException
(
Throwable
suppressed
)
{
super
(
suppressed
);
}
}
src/main/java/de/hft/unifiedticketing/systems/gitlab/GitlabFilter.java
0 → 100644
View file @
b841acdd
package
de.hft.unifiedticketing.systems.gitlab
;
import
com.fasterxml.jackson.core.type.TypeReference
;
import
com.fasterxml.jackson.databind.DeserializationFeature
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
import
de.hft.unifiedticketing.core.Filter
;
import
de.hft.unifiedticketing.core.Logging
;
import
de.hft.unifiedticketing.core.TicketSystem
;
import
de.hft.unifiedticketing.exceptions.AssertionException
;
import
de.hft.unifiedticketing.exceptions.DeserializationException
;
import
de.hft.unifiedticketing.exceptions.HttpReqeustException
;
import
de.hft.unifiedticketing.exceptions.HttpResponseException
;
import
okhttp3.*
;
import
java.io.IOException
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Set
;
import
java.util.logging.Level
;
import
java.util.logging.Logger
;
import
java.util.stream.Collectors
;
import
java.util.stream.Stream
;
public
class
GitlabFilter
extends
Filter
<
GitlabTicket
,
GitlabFilter
>
{
private
static
Logger
logger
=
Logging
.
getLogger
(
GitlabFilter
.
class
.
getName
());
protected
final
GitlabTicketSystem
parent
;
protected
GitlabFilter
(
GitlabTicketSystem
parent
)
{
this
.
parent
=
parent
;
}
protected
OkHttpClient
getHttpClient
()
{
return
new
OkHttpClient
();
}
/**
* this builds up a request with as much of the given constraints directed to the
* gitlab api. Everything not possible through the api will be filtered afterwards in java
* @return ticket list matching given constraints
*/
@Override
public
List
<
GitlabTicket
>
get
()
{
OkHttpClient
client
;
ObjectMapper
mapper
=
new
ObjectMapper
()
.
configure
(
DeserializationFeature
.
FAIL_ON_UNKNOWN_PROPERTIES
,
false
);
Request
.
Builder
requestBuilder
=
new
Request
.
Builder
()
.
url
(
String
.
format
(
"%s/issues"
,
parent
.
baseUrl
))
.
get
();
if
(
parent
.
apiKey
!=
null
)
{
requestBuilder
.
addHeader
(
"PRIVATE-TOKEN"
,
parent
.
apiKey
);
}
HttpUrl
.
Builder
urlBuilder
=
requestBuilder
.
build
().
url
().
newBuilder
();
for
(
Map
.
Entry
<
String
,
Object
>
mapEntry
:
setFilters
.
entrySet
())
{
String
f
=
mapEntry
.
getKey
();
Object
v
=
mapEntry
.
getValue
();
try
{
if
(
f
.
equals
(
FilterNames
.
ASSIGNEEID
.
name
()))
{
((
Set
<
String
>)
v
).
stream
()
.
findFirst
()
.
ifPresent
(
value
->
urlBuilder
.
addQueryParameter
(
"assignee_id"
,
value
));
}
else
if
(
f
.
equals
(
FilterNames
.
ASSIGNEENAME
.
name
()))
{
urlBuilder
.
addQueryParameter
(
"assignee_username"
,
((
Set
<
String
>)
v
).
stream
()
.
reduce
((
u1
,
u2
)
->
u1
+
","
+
u2
)
.
orElse
(
""
));
}
else
if
(
f
.
equals
(
FilterNames
.
DESCRIPTION_CONTAIN
.
name
()))
{
urlBuilder
.
addQueryParameter
(
"search"
,
(
String
)
v
);
}
else
if
(
f
.
equals
(
FilterNames
.
DESCRIPTION_MATCH
.
name
()))
{
logger
.
log
(
Level
.
FINE
,
"Regex matching only possible after request |"
+
" Filter: "
+
FilterNames
.
DESCRIPTION_MATCH
.
name
());
}
else
if
(
f
.
equals
(
FilterNames
.
IDS
.
name
()))
{
urlBuilder
.
addQueryParameter
(
"iids"
,
((
Set
<
String
>)
v
).
stream
()
.
reduce
((
id1
,
id2
)
->
id1
+
","
+
id2
)
.
orElse
(
""
));
}
else
if
(
f
.
equals
(
FilterNames
.
LABELS
.
name
()))
{
urlBuilder
.
addQueryParameter
(
"labels"
,
((
Set
<
String
>)
v
).
stream
()
.
reduce
((
l1
,
l2
)
->
l1
+
","
+
l2
)
.
orElse
(
""
));
}
else
if
(
f
.
equals
(
FilterNames
.
PAGE
.
name
()))
{
urlBuilder
.
addQueryParameter
(
"page"
,
String
.
valueOf
(
v
));
}
else
if
(
f
.
equals
(
FilterNames
.
PAGINATION
.
name
()))
{
urlBuilder
.
addQueryParameter
(
"per_page"
,
String
.
valueOf
(
v
));
}
else
if
(
f
.
equals
(
FilterNames
.
OPEN
.
name
()))
{
urlBuilder
.
addQueryParameter
(
"state"
,
((
boolean
)
v
)
?
"opened"
:
"closed"
);
}
else
if
(
f
.
equals
(
FilterNames
.
TITLE_CONTAINS
.
name
()))
{
urlBuilder
.
addQueryParameter
(
"search"
,
(
String
)
v
);
}
else
if
(
f
.
equals
(
FilterNames
.
TITLE_MATCH
.
name
()))
{
logger
.
log
(
Level
.
FINE
,
"Regex matching only possible after request |"
+
" Filter: "
+
FilterNames
.
TITLE_MATCH
.
name
());
}
else
{
logger
.
log
(
Level
.
WARNING
,
String
.
format
(
"unrecognized filter key: %s"
,
f
));
}
}
catch
(
ClassCastException
e
)
{
logger
.
log
(
Level
.
SEVERE
,
"Filter with key "
+
f
+
" unexpectedly had type "
+
v
.
getClass
().
getName
());
if
(!
this
.
parent
.
getConfigTrue
(
TicketSystem
.
ConfigurationOptions
.
RETURN_NULL_ON_ERROR
))
{
throw
new
AssertionException
(
e
);
}
}
}
requestBuilder
.
url
(
urlBuilder
.
build
());
client
=
getHttpClient
();
Response
response
;
try
{
Request
request
=
requestBuilder
.
build
();
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"created request:\n%s"
,
(
this
.
parent
.
apiKey
!=
null
)
?
request
.
toString
().
replace
(
this
.
parent
.
apiKey
,
"SECRET"
)
:
request
.
toString
()
));
response
=
client
.
newCall
(
request
).
execute
();
}
catch
(
IOException
e
)
{
logger
.
log
(
Level
.
SEVERE
,
String
.
format
(
"get request FAILED with: %s"
,
e
.
getMessage
()));
if
(
this
.
parent
.
getConfigTrue
(
TicketSystem
.
ConfigurationOptions
.
RETURN_NULL_ON_ERROR
))
return
null
;
else
throw
new
HttpReqeustException
(
e
);
}
if
(
response
.
code
()
>=
400
)
{
logger
.
log
(
Level
.
SEVERE
,
String
.
format
(
"request failed with response code %d"
,
response
.
code
()
));
if
(
this
.
parent
.
getConfigTrue
(
TicketSystem
.
ConfigurationOptions
.
RETURN_NULL_ON_ERROR
))
return
null
;
else
throw
new
HttpResponseException
(
String
.
format
(
"ticket query failed, error response code: %d"
,
response
.
code
()),
response
.
code
());
}
logger
.
log
(
Level
.
FINEST
,
"response received\n"
);
ResponseBody
responseBody
;
responseBody
=
response
.
body
();
if
(
responseBody
==
null
)
{
logger
.
log
(
Level
.
SEVERE
,
"query didn't deliver a body in response"
);
if
(
this
.
parent
.
getConfigTrue
(
TicketSystem
.
ConfigurationOptions
.
RETURN_NULL_ON_ERROR
))
return
null
;
else
throw
new
HttpResponseException
(
"ticket query failed, no response body"
,
response
.
code
());
}
List
<
GitlabTicketResponse
>
tr
;
try
{
tr
=
mapper
.
readValue
(
responseBody
.
bytes
(),
new
TypeReference
<
List
<
GitlabTicketResponse
>>(){});
logger
.
log
(
Level
.
FINER
,
"parsed response body to ticketResponse list instance"
);
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"found %d items pre post-filter"
,
tr
.
size
()));
}
catch
(
IOException
e
)
{
logger
.
log
(
Level
.
SEVERE
,
String
.
format
(
"parsing query response FAILED with: %s"
,
e
.
getMessage
()));
if
(
this
.
parent
.
getConfigTrue
(
TicketSystem
.
ConfigurationOptions
.
RETURN_NULL_ON_ERROR
))
return
null
;
else
throw
new
DeserializationException
(
e
);
}
logger
.
log
(
Level
.
FINER
,
"starting query post filter"
);
Stream
<
GitlabTicketResponse
>
ticketStream
=
tr
.
stream
();
for
(
Map
.
Entry
<
String
,
Object
>
entry
:
setFilters
.
entrySet
())
{
String
f
=
entry
.
getKey
();
Object
v
=
entry
.
getValue
();
try
{
if
(
f
.
equals
(
FilterNames
.
DESCRIPTION_MATCH
.
name
()))
{
ticketStream
=
ticketStream
.
filter
(
t
->
t
.
description
.
matches
((
String
)
v
));
}
else
if
(
f
.
equals
(
FilterNames
.
TITLE_MATCH
.
name
()))
{
ticketStream
=
ticketStream
.
filter
(
t
->
t
.
title
.
matches
((
String
)
v
));
}
}
catch
(
ClassCastException
e
)
{
logger
.
log
(
Level
.
SEVERE
,
"Filter with key "
+
f
+
" unexpectedly had type "
+
v
.
getClass
().
getName
());
}
}
logger
.
log
(
Level
.
FINER
,
"post-filter finished"
);
List
<
GitlabTicket
>
ret
=
ticketStream
.
map
(
t
->
GitlabTicket
.
fromTicketResponse
(
parent
,
t
))
.
collect
(
Collectors
.
toList
());
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"remaining items: %d"
,
ret
.
size
()));
return
ret
;
}
}
src/main/java/de/hft/unifiedticketing/systems/gitlab/GitlabTicket.java
0 → 100644
View file @
b841acdd
package
de.hft.unifiedticketing.systems.gitlab
;
import
com.fasterxml.jackson.core.JsonProcessingException
;
import
com.fasterxml.jackson.databind.DeserializationFeature
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
import
com.fasterxml.jackson.databind.node.ArrayNode
;
import
com.fasterxml.jackson.databind.node.ObjectNode
;
import
de.hft.unifiedticketing.core.Logging
;
import
de.hft.unifiedticketing.core.Ticket
;
import
de.hft.unifiedticketing.core.TicketSystem
;
import
de.hft.unifiedticketing.exceptions.*
;
import
okhttp3.*
;
import
okio.Buffer
;
import
java.io.IOException
;
import
java.util.HashSet
;
import
java.util.Objects
;
import
java.util.logging.Level
;
import
java.util.logging.Logger
;
import
java.util.stream.Collectors
;
public
class
GitlabTicket
extends
Ticket
<
GitlabTicketSystem
,
GitlabTicket
>
{
private
static
Logger
logger
=
Logging
.
getLogger
(
GitlabTicket
.
class
.
getName
());
protected
GitlabTicket
(
GitlabTicketSystem
parent
)
{
super
(
parent
);
}
/**
* method to parse a library conform ticket instance from the JSON response of gitlab api
* @param parent ticket system instance to be placed as parent
* @param response api response
* @return library conform ticket instance
*/
protected
static
GitlabTicket
fromTicketResponse
(
GitlabTicketSystem
parent
,
GitlabTicketResponse
response
)
{
GitlabTicket
ret
=
new
GitlabTicket
(
parent
);
ret
.
description
=
response
.
description
;
ret
.
id
=
String
.
valueOf
(
response
.
iid
);
if
(
response
.
labels
!=
null
)
{
ret
.
labels
=
response
.
labels
;
}
ret
.
open
=
(
response
.
state
!=
null
)
?
!
response
.
state
.
equalsIgnoreCase
(
"closed"
)
:
true
;
ret
.
title
=
response
.
title
;
if
(
response
.
assignees
!=
null
)
{
ret
.
assignees
=
response
.
assignees
.
stream
()
.
map
(
a
->
new
GitlabTicketAssignee
(
a
.
id
,
a
.
name
,
a
.
username
))
.
collect
(
Collectors
.
toCollection
(
HashSet:
:
new
));
}
return
ret
;
}
/**
* adds assignee by it's user id
*
* @param userId gitlab user id parsable as integer
* @return this ticket
* @throws AssertionException if {@code userId} is not integer parsable
*/
@Override
public
GitlabTicket
addAssignee
(
String
userId
)
{
try
{
return
this
.
addAssignee
(
Integer
.
parseInt
(
userId
));
}
catch
(
NumberFormatException
e
)
{
logger
.
log
(
Level
.
SEVERE
,
String
.
format
(
"%s is not applicable for a Gitlab user id"
,
userId
));
if
(
this
.
parent
.
getConfigTrue
(
TicketSystem
.
ConfigurationOptions
.
RETURN_NULL_ON_ERROR
))
return
this
;
else
throw
new
AssertionException
(
e
);
}
}
/**
* adds assignee by it's user id
*
* @param userId gitlab user id
* @return this ticket
*/
public
GitlabTicket
addAssignee
(
int
userId
)
{
this
.
assignees
.
add
(
new
GitlabTicketAssignee
(
userId
,
null
,
null
));
this
.
updatedFields
.
add
(
FieldNames
.
ASSIGNEES
.
name
());
return
this
;
}
/**
* removes assignee by it's user id
*
* @param userId gitlab user id parsable as integer
* @return this ticket
* @throws AssertionException if {@code userId} is not integer parsable
*/
@Override
public
GitlabTicket
removeAssignee
(
String
userId
)
{
int
id
;
try
{
return
this
.
removeAssignee
(
Integer
.
parseInt
(
userId
));
}
catch
(
NumberFormatException
e
)
{
logger
.
log
(
Level
.
SEVERE
,
String
.
format
(
"%s is not applicable for a Gitlab user id"
,
userId
));
if
(
this
.
parent
.
getConfigTrue
(
TicketSystem
.
ConfigurationOptions
.
RETURN_NULL_ON_ERROR
))
return
this
;
else
throw
new
AssertionException
(
e
);
}
}
/**
* removes assignee by it's user id
*
* @param userId gitlab user id parsable as integer
* @return this ticket
*/
public
GitlabTicket
removeAssignee
(
int
userId
)
{
this
.
assignees
.
removeIf
(
a
->
Objects
.
equals
(
a
.
id
,
String
.
valueOf
(
userId
)));
this
.
updatedFields
.
add
(
FieldNames
.
ASSIGNEES
.
name
());
return
this
;
}
protected
OkHttpClient
getHttpClient
()
{
return
new
OkHttpClient
();
}
/**
* checks for changed fields and serializes them into a PUT request
* to update this ticket instance.
* @return newly received ticket data as new {@link GitlabTicket instance}
*/
@Override
public
GitlabTicket
save
()
{
if
(
updatedFields
.
size
()
==
0
)
{
logger
.
info
(
"No changed fields, no save required"
);
return
this
;
}
OkHttpClient
client
=
this
.
getHttpClient
();
ObjectMapper
mapper
=
new
ObjectMapper
()
.
configure
(
DeserializationFeature
.
FAIL_ON_UNKNOWN_PROPERTIES
,
false
);
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"[Ticket %s] preparing body for update request"
,
this
.
id
));
ObjectNode
body
=
mapper
.
createObjectNode
();
for
(
String
name
:
this
.
updatedFields
)
{
if
(
FieldNames
.
ASSIGNEES
.
name
().
equals
(
name
))
{
ArrayNode
ids
=
body
.
putArray
(
"assignee_ids"
);
try
{
this
.
assignees
.
forEach
(
a
->
ids
.
add
(
Integer
.
parseInt
(
a
.
id
)));
}
catch
(
NumberFormatException
e
)
{
logger
.
log
(
Level
.
SEVERE
,
"parsing assignee id failed on serialization"
);
if
(
this
.
parent
.
getConfigTrue
(
TicketSystem
.
ConfigurationOptions
.
RETURN_NULL_ON_ERROR
))
{
body
.
remove
(
"assignee_ids"
);
continue
;
}
else
{
throw
new
SerializationException
(
e
);
}
}
}
else
if
(
FieldNames
.
DESCRIPTION
.
name
().
equals
(
name
))
{
body
.
put
(
"description"
,
this
.
description
);
}
else
if
(
FieldNames
.
LABELS
.
name
().
equals
(
name
))
{
body
.
put
(
"labels"
,
this
.
labels
.
stream
().
reduce
((
l1
,
l2
)
->
l1
+
","
+
l2
).
orElse
(
""
));
}
else
if
(
FieldNames
.
OPEN
.
name
().
equals
(
name
))
{
body
.
put
(
"state_event"
,
(
this
.
open
)
?
"reopen"
:
"close"
);
}
else
if
(
FieldNames
.
TITLE
.
name
().
equals
(
name
))
{
body
.
put
(
"title"
,
this
.
title
);
}
else
{
logger
.
log
(
Level
.
WARNING
,
String
.
format
(
"[Ticket %s] unknown field %s will be ignored from update"
,
this
.
id
,
name
));
continue
;
}
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"[Ticket %s] %s added to update"
,
this
.
id
,
name
));
}
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"[Ticket %s] request body for update prepared"
,
this
.
id
));
Request
.
Builder
builder
=
new
Request
.
Builder
()
.
url
(
String
.
format
(
"%s/issues/%s"
,
this
.
parent
.
baseUrl
,
this
.
id
));
try
{
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"[Ticket %s] serializing update request body"
,
this
.
id
));
String
bodyJson
=
mapper
.
writeValueAsString
(
body
);
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"[Ticket %s] serialized JSON:\n%s"
,
this
.
id
,
bodyJson
));
builder
.
put
(
RequestBody
.
create
(
bodyJson
,
MediaType
.
get
(
"application/json"
)));
}
catch
(
JsonProcessingException
e
)
{
logger
.
log
(
Level
.
SEVERE
,
String
.
format
(
"[Ticket %s] serializing update request body FAILED!"
,
this
.
id
));
if
(
this
.
parent
.
getConfigTrue
(
TicketSystem
.
ConfigurationOptions
.
RETURN_NULL_ON_ERROR
))
return
null
;
else
throw
new
SerializationException
(
e
);
}
if
(
parent
.
apiKey
!=
null
)
{
builder
.
addHeader
(
"PRIVATE-TOKEN"
,
parent
.
apiKey
);
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"[Ticket %s] added token authentication header"
,
this
.
id
));
}
Response
response
;
try
{
Request
request
=
builder
.
build
();
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"[Ticket %s] created request:\n%s"
,
this
.
id
,
(
this
.
parent
.
apiKey
!=
null
)
?
request
.
toString
().
replace
(
this
.
parent
.
apiKey
,
"SECRET"
)
:
request
.
toString
()
));
response
=
client
.
newCall
(
request
).
execute
();
}
catch
(
IOException
e
)
{
logger
.
log
(
Level
.
SEVERE
,
String
.
format
(
"[Ticket %s] update request FAILED"
,
this
.
id
));
if
(
this
.
parent
.
getConfigTrue
(
TicketSystem
.
ConfigurationOptions
.
RETURN_NULL_ON_ERROR
))
return
null
;
else
throw
new
HttpReqeustException
(
e
);
}
if
(
response
.
code
()
>=
400
)
{
logger
.
log
(
Level
.
SEVERE
,
String
.
format
(
"[Ticket %s] update request failed with response code %d"
,
this
.
id
,
response
.
code
()
));
if
(
this
.
parent
.
getConfigTrue
(
TicketSystem
.
ConfigurationOptions
.
RETURN_NULL_ON_ERROR
))
return
null
;
else
throw
new
HttpResponseException
(
String
.
format
(
"ticket save failed, error response code: %d"
,
response
.
code
()),
response
.
code
());
}
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"[Ticket %s] response received"
,
this
.
id
));
ResponseBody
responseBody
;
responseBody
=
response
.
body
();
if
(
responseBody
==
null
)
{
logger
.
log
(
Level
.
SEVERE
,
String
.
format
(
"[Ticket %s] update request didn't deliver a body in response"
,
this
.
id
));
if
(
this
.
parent
.
getConfigTrue
(
TicketSystem
.
ConfigurationOptions
.
RETURN_NULL_ON_ERROR
))
return
null
;
else
throw
new
HttpResponseException
(
"ticket save failed, no response body"
,
response
.
code
());
}
GitlabTicketResponse
ticketResponse
;
try
{
ticketResponse
=
mapper
.
readValue
(
responseBody
.
bytes
(),
GitlabTicketResponse
.
class
);
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"[Ticket %s] parsed response body to ticketResponse instance"
,
this
.
id
));
}
catch
(
IOException
e
)
{
logger
.
log
(
Level
.
SEVERE
,
String
.
format
(
"parsing update response FAILED with: %s"
,
e
.
getMessage
()));
if
(
this
.
parent
.
getConfigTrue
(
TicketSystem
.
ConfigurationOptions
.
RETURN_NULL_ON_ERROR
))
return
null
;
else
throw
new
DeserializationException
(
e
);
}
this
.
updatedFields
.
clear
();
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"[Ticket %s] update mark state reset"
,
this
.
id
));
return
GitlabTicket
.
fromTicketResponse
(
this
.
parent
,
ticketResponse
);
}
/**
* compares a given Object to this one, including all data fields.
* The normal {@link #equals(Object)} method does only compare Ticket Type and id,
* but not the data fields like this one.
* @param o Object to compare to this
* @return true if the given object is equal in terms of type, id and content to this
*/
public
boolean
deepEquals
(
Object
o
)
{
if
(!
equals
(
o
))
return
false
;
GitlabTicket
t
=
(
GitlabTicket
)
o
;
return
Objects
.
equals
(
title
,
t
.
title
)
&&
Objects
.
equals
(
description
,
t
.
description
)
&&
Objects
.
equals
(
labels
,
t
.
labels
)
&&
Objects
.
equals
(
assignees
,
t
.
assignees
);
}
}
src/main/java/de/hft/unifiedticketing/systems/gitlab/GitlabTicketAssignee.java
0 → 100644
View file @
b841acdd
package
de.hft.unifiedticketing.systems.gitlab
;
import
de.hft.unifiedticketing.core.TicketAssignee
;
public
class
GitlabTicketAssignee
extends
TicketAssignee
{
protected
GitlabTicketAssignee
(
int
id
,
String
fullName
,
String
username
)
{
super
(
null
,
String
.
valueOf
(
id
),
fullName
,
username
);
}
}
src/main/java/de/hft/unifiedticketing/systems/gitlab/GitlabTicketBuilder.java
0 → 100644
View file @
b841acdd
package
de.hft.unifiedticketing.systems.gitlab
;
import
com.fasterxml.jackson.core.JsonProcessingException
;
import
com.fasterxml.jackson.databind.DeserializationFeature
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
import
com.fasterxml.jackson.databind.node.ArrayNode
;
import
com.fasterxml.jackson.databind.node.ObjectNode
;
import
de.hft.unifiedticketing.core.Logging
;
import
de.hft.unifiedticketing.core.TicketBuilder
;
import
de.hft.unifiedticketing.core.TicketSystem
;
import
de.hft.unifiedticketing.exceptions.*
;
import
okhttp3.*
;
import
java.io.IOException
;
import
java.util.Arrays
;
import
java.util.HashSet
;
import
java.util.logging.Level
;
import
java.util.logging.Logger
;
import
java.util.stream.Collectors
;
public
class
GitlabTicketBuilder
extends
TicketBuilder
<
GitlabTicketBuilder
,
GitlabTicket
,
GitlabTicketSystem
>
{
private
static
Logger
logger
=
Logging
.
getLogger
(
GitlabTicketBuilder
.
class
.
getName
());
protected
GitlabTicketBuilder
(
GitlabTicketSystem
parent
)
{
super
(
parent
);
}
@Override
public
GitlabTicketBuilder
assignees
(
String
...
identifiers
)
{
try
{
assignees
(
Arrays
.
stream
(
identifiers
)
.
mapToInt
(
Integer:
:
parseInt
)
.
toArray
());
}
catch
(
NumberFormatException
e
)
{
logger
.
log
(
Level
.
SEVERE
,
String
.
format
(
"not as integer parsable assignee id encountered!"
));
throw
new
AssertionException
(
e
);
}
return
this
;
}
public
GitlabTicketBuilder
assignees
(
int
...
identifiers
)
{
assignees
=
Arrays
.
stream
(
identifiers
)
.
mapToObj
(
i
->
(
Integer
)
i
)
.
collect
(
Collectors
.
toSet
());
return
this
;
}
protected
OkHttpClient
getHttpClient
()
{
return
new
OkHttpClient
();
}
/**
* creates a new ticket with the collected data and retrieves a new ticket instance from
* the api response
* @return newly created Ticket as {@link GitlabTicket} instance
*/
@Override
public
GitlabTicket
create
()
{
logger
.
log
(
Level
.
FINEST
,
"starting Ticket creation from builder data"
);
ObjectMapper
mapper
=
new
ObjectMapper
()
.
configure
(
DeserializationFeature
.
FAIL_ON_UNKNOWN_PROPERTIES
,
false
);
ObjectNode
body
=
mapper
.
createObjectNode
();
if
(
this
.
title
==
null
)
{
logger
.
log
(
Level
.
SEVERE
,
"mandatory field title not set before building Ticket"
);
if
(
this
.
parent
.
getConfigTrue
(
TicketSystem
.
ConfigurationOptions
.
RETURN_NULL_ON_ERROR
))
return
null
;
else
throw
new
AssertionException
(
"GitLab Tickets need at-least a title"
);
}
else
body
.
put
(
"title"
,
this
.
title
);
logger
.
log
(
Level
.
FINEST
,
"title set"
);
if
(
this
.
assignees
!=
null
)
{
ArrayNode
ids
=
body
.
putArray
(
"assignee_ids"
);
assignees
.
forEach
(
ids:
:
add
);
logger
.
log
(
Level
.
FINEST
,
"assignees set"
);
}
if
(
this
.
description
!=
null
)
{
body
.
put
(
"description"
,
this
.
description
);
logger
.
log
(
Level
.
FINEST
,
"description set"
);
}
if
(
this
.
labels
!=
null
)
{
body
.
put
(
"labels"
,
this
.
labels
.
stream
()
.
reduce
((
l1
,
l2
)
->
l1
+
","
+
l2
)
.
orElse
(
""
));
logger
.
log
(
Level
.
FINEST
,
"labels set"
);
}
String
jsonBody
;
try
{
jsonBody
=
mapper
.
writeValueAsString
(
body
);
logger
.
log
(
Level
.
FINER
,
String
.
format
(
"body for new Ticket serialized as json:\n%s"
,
jsonBody
));
}
catch
(
JsonProcessingException
e
)
{
logger
.
log
(
Level
.
SEVERE
,
"json serialization FAILED"
);
if
(
this
.
parent
.
getConfigTrue
(
TicketSystem
.
ConfigurationOptions
.
RETURN_NULL_ON_ERROR
))
return
null
;
else
throw
new
SerializationException
(
e
);
}
OkHttpClient
client
=
getHttpClient
();
Request
.
Builder
builder
=
new
Request
.
Builder
()
.
url
(
String
.
format
(
"%s/issues"
,
parent
.
baseUrl
));
builder
.
post
(
RequestBody
.
create
(
jsonBody
,
MediaType
.
get
(
"application/json"
)));
if
(
this
.
parent
.
apiKey
!=
null
)
{
builder
.
addHeader
(
"PRIVATE-TOKEN"
,
this
.
parent
.
apiKey
);
logger
.
log
(
Level
.
FINEST
,
"added token authentication header"
);
}
Response
response
;
try
{
Request
request
=
builder
.
build
();
logger
.
log
(
Level
.
FINEST
,
String
.
format
(
"created request:\n%s"
,
(
this
.
parent
.
apiKey
!=
null
)
?
request
.
toString
().
replace
(
this
.
parent
.
apiKey
,
"SECRET"
)
:
request
.
toString
()
));
response
=
client
.
newCall
(
request
).
execute
();
}
catch
(
IOException
e
)
{
logger
.
log
(
Level
.
SEVERE
,
"create request FAILED"
);
if
(
this
.
parent
.
getConfigTrue
(
TicketSystem
.
ConfigurationOptions
.
RETURN_NULL_ON_ERROR
))
return
null
;
else
throw
new
HttpReqeustException
(
e
);
}
if
(
response
.
code
()
>=
400
)
{
logger
.
log
(
Level
.
SEVERE
,
String
.
format
(
"create request failed with response code %d"
,
response
.
code
()
));
if
(
this
.
parent
.
getConfigTrue
(
TicketSystem
.
ConfigurationOptions
.
RETURN_NULL_ON_ERROR
))
return
null
;
else
throw
new
HttpResponseException
(
String
.
format
(
"ticket creation failed, error response code %d"
,
response
.
code
()),
response
.
code
());
}
logger
.
log
(
Level
.
FINEST
,
"response received\n"
);
ResponseBody
responseBody
;
responseBody
=
response
.
body
();
if
(
responseBody
==
null
)
{
logger
.
log
(
Level
.
SEVERE
,
"create request didn't deliver a body in response"
);
if
(
this
.
parent
.
getConfigTrue
(
TicketSystem
.
ConfigurationOptions
.
RETURN_NULL_ON_ERROR
))
return
null
;
else
throw
new
HttpResponseException
(
"ticket creation failed, no response body"
,
response
.
code
());
}
GitlabTicketResponse
ticketResponse
;
try
{
ticketResponse
=
mapper
.
readValue
(
responseBody
.
bytes
(),
GitlabTicketResponse
.
class
);
logger
.
log
(
Level
.
FINEST
,
"parsed response body to ticketResponse instance"
);
}
catch
(
IOException
e
)
{
logger
.
log
(
Level
.
SEVERE
,
String
.
format
(
"parsing update response FAILED with: %s"
,
e
.
getMessage
()));
if
(
this
.
parent
.
getConfigTrue
(
TicketSystem
.
ConfigurationOptions
.
RETURN_NULL_ON_ERROR
))
return
null
;
else
throw
new
DeserializationException
(
e
);
}
return
GitlabTicket
.
fromTicketResponse
(
this
.
parent
,
ticketResponse
);
}
}
Prev
1
2
Next
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment