Commits (24)
# collected from https://github.com/github/gitignore/tree/master/Global
# ignore certain package pathes, dedicated for local playaround classes not to be committed
src/main/java/test/
# maven
target/
......@@ -82,4 +85,4 @@ local.properties
# Scala IDE specific (Scala & Java development for Eclipse)
.cache-main
.scala_dependencies
.worksheet
\ No newline at end of file
.worksheet
variables:
MAVEN_CLI_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/maven-repo"
cache:
paths:
- maven-repo
maven:compile:
stage: compile
tags:
- docker
image: maven:3-openjdk-11-slim
script:
- mvn $MAVEN_CLI_OPTS clean
- mvn $MAVEN_CLI_OPTS compile test-compile
only:
changes:
- pom.xml
- src/**/*
artifacts:
expire_in: 3h
paths:
- target/
maven:test:
stage: test
tags:
- docker
image: maven:3-openjdk-11-slim
script:
- mvn $MAVEN_CLI_OPTS test
dependencies:
needs:
- maven:compile
only:
changes:
- pom.xml
- src/**/*
.maven:deploy: &mavenDeploy
stage: deploy
tags:
- docker
image: maven:3-openjdk-11-slim
script:
- 'mvn $MAVEN_CLI_OPTS deploy -s transfer-gitlab-settings.xml'
needs:
- maven:compile
- job: maven:test
artifacts: false
maven:publish:master:
<<: *mavenDeploy
only:
refs:
- master
changes:
- pom.xml
- src/**/*
maven:publish:release:
<<: *mavenDeploy
variables:
BUILD_NUMBER: $CI_COMMIT_TAG
only:
refs:
- /^(\d+\.){2}?\d+$/
changes:
- pom.xml
- src/**/*
except:
- branches
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
cache:
paths:
- .cache/pip
- venv/
mkdocs:compile:
stage: compile
image: python:3.8
tags:
- docker
before_script:
- pip install virtualenv
- virtualenv venv
- source venv/bin/activate
script:
- pip install -r requirements.txt -U
- mkdocs build --strict
only:
changes:
- docs/**/*
- mkdocs.yml
artifacts:
expire_in: 3h
paths:
- site/
mkdocs:bundle:
stage: deploy
image: alpine
tags:
- docker
script:
- tar c -zvf documentation.tar.gz site
needs:
- mkdocs:compile
only:
changes:
- docs/**/*
- mkdocs.yml
artifacts:
expire_in: 3 days
paths:
- ./*.tar.gz
mkdocs:deploy:
stage: deploy
tags:
- production
variables:
EXPORT_DIR: "/var/www/html/pages"
script:
- mkdir -p "$EXPORT_DIR/$CI_PROJECT_NAME"
- cp -r site/* "$EXPORT_DIR/$CI_PROJECT_NAME"
needs:
- mkdocs:compile
only:
refs:
- master
changes:
- docs/**/*
- mkdocs.yml
stages:
- compile
- test
- deploy
include:
- local: '/.gitlab-ci-maven.yml'
- local: '/.gitlab-ci-mkdocs.yml'
# Changelog
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## [0.1.0](https://transfer.hft-stuttgart.de///compare/0.0.0...0.1.0) (2020-11-23)
### Features
* add ci pipeline ([d6d6f3a](https://transfer.hft-stuttgart.de///commit/d6d6f3aaa7b09404cef03bb319508087e33f4982))
* add GPLv3 License text ([6016122](https://transfer.hft-stuttgart.de///commit/6016122eb4c9e92e1befd1243b85c6ac509502b8))
* **ci:** add maven settings file for gitlab artifactory ([aacd6b4](https://transfer.hft-stuttgart.de///commit/aacd6b494c3f49a5e6b325232b6532494e1e8bfa))
* **ci:** add repo and dist management parts to pom ([ab131be](https://transfer.hft-stuttgart.de///commit/ab131beb5d3838327e7e1e5aca19c6120d58708c))
* import first state of library code ([b841acd](https://transfer.hft-stuttgart.de///commit/b841acdde74a5e2969bac34db8a3051001ce860e))
### Bug Fixes
* **logger:** set correct package for mainlogger after 19162378 ([04ae3ee](https://transfer.hft-stuttgart.de///commit/04ae3ee468a84e5729dcce9dfab02c6322e15ec1))
* **systems/gitlab:** allow gitlab root at subpath on non-standard port ([baba48d](https://transfer.hft-stuttgart.de///commit/baba48d5e0b70b2c91c3232ecbc9187c504d043c))
This diff is collapsed.
# Unified Ticketing Java Library
This is the repository of the unified-ticketing library.
Documentation for this is done with GitLab Pages using MkDocs and is available here:
https://transfer.hft-stuttgart.de/pages/unified-ticketing/
# Unified Ticketing
!!! Todo
site_name: Unified Ticketing Doku
repo_name: 'Unified Ticketing Java Bibliothek'
repo_url: 'https://transfer.hft-stuttgart.de/gitlab/9Lukas5/unified-ticketing'
edit_uri: 'blob/master/docs/'
docs_dir: docs
copyright: 'Copyright &copy; 2020 Hochschule für Technik Stuttgart'
use_directory_urls: false
nav:
- Home:
- Index: 'index.md'
theme:
name: 'material'
palette:
primary: 'white'
accent: 'teal'
language: 'en'
feature:
tabs: true
logo:
icon: library_books
markdown_extensions:
- admonition
- codehilite:
linenums: true
- toc:
permalink: True
- footnotes
- tables
- plantuml_markdown:
server: http://plantuml.com/plantuml
format: svg
- pymdownx.emoji:
emoji_generator: !!python/name:pymdownx.emoji.to_svg
- pymdownx.tasklist
- pymdownx.details
- pymdownx.superfences
<?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>de.hftstuttgart</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.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>
<repositories>
<repository>
<id>gitlab-maven</id>
<url>https://transfer.hft-stuttgart.de/gitlab/api/v4/projects/${env.CI_PROJECT_ID}/packages/maven</url>
</repository>
</repositories>
<distributionManagement>
<repository>
<id>gitlab-maven</id>
<url>https://transfer.hft-stuttgart.de/gitlab/api/v4/projects/${env.CI_PROJECT_ID}/packages/maven</url>
</repository>
<snapshotRepository>
<id>gitlab-maven</id>
<url>https://transfer.hft-stuttgart.de/gitlab/api/v4/projects/${env.CI_PROJECT_ID}/packages/maven</url>
</snapshotRepository>
</distributionManagement>
</project>
package de.hftstuttgart.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));
}
}
package de.hftstuttgart.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.hftstuttgart.unifiedticketing");
mainLogger.setUseParentHandlers(false);
handler = new ConsoleHandler();
handler.setFormatter(new SimpleFormatter() {
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);
}
}
package de.hftstuttgart.unifiedticketing.core;
import de.hftstuttgart.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();
}
}
package de.hftstuttgart.unifiedticketing.core;
import de.hftstuttgart.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 + '\'' +
'}';
}
}
package de.hftstuttgart.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 + '\'' +
'}';
}
}
package de.hftstuttgart.unifiedticketing.core;
import de.hftstuttgart.unifiedticketing.exceptions.AssertionException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* <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<String> 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 B assignees(String... identifiers)
{
this.assignees = new HashSet<>(Arrays.asList(identifiers));
return (B) this;
}
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();
}
package de.hftstuttgart.unifiedticketing.core;
import de.hftstuttgart.unifiedticketing.exceptions.AssertionException;
import de.hftstuttgart.unifiedticketing.systems.gitlab.GitlabTicketSystem;
import java.util.HashMap;
import java.util.Map;
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());
/**
* 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;
protected Map<String, String> configuration = new HashMap<>();
/**
* 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();
}
package de.hftstuttgart.unifiedticketing.core;
import de.hftstuttgart.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();
}
package de.hftstuttgart.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); }
}