package eu.doppel_helix.netbeans.mantisintegration.repository;
import biz.futureware.mantisconnect.AttachmentData;
import biz.futureware.mantisconnect.FilterSearchData;
import biz.futureware.mantisconnect.IssueData;
import biz.futureware.mantisconnect.IssueHeaderData;
import biz.futureware.mantisconnect.IssueNoteData;
import biz.futureware.mantisconnect.MantisConnectLocator;
import biz.futureware.mantisconnect.MantisConnectPortType;
import biz.futureware.mantisconnect.ObjectRef;
import biz.futureware.mantisconnect.ProjectData;
import biz.futureware.mantisconnect.RelationshipData;
import biz.futureware.mantisconnect.TagData;
import biz.futureware.mantisconnect.UserData;
import eu.doppel_helix.netbeans.mantisintegration.MantisConnector;
import eu.doppel_helix.netbeans.mantisintegration.data.Version;
import eu.doppel_helix.netbeans.mantisintegration.issue.MantisIssue;
import eu.doppel_helix.netbeans.mantisintegration.query.MantisQuery;
import eu.doppel_helix.netbeans.mantisintegration.swing.EDTInvocationHandler;
import eu.doppel_helix.netbeans.mantisintegration.util.ExceptionHandler;
import java.awt.Image;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Proxy;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.xml.rpc.ServiceException;
import org.apache.axis.client.Call;
import org.apache.axis.client.Stub;
import org.netbeans.modules.bugtracking.spi.RepositoryInfo;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.util.ImageUtilities;
import org.openide.util.RequestProcessor;
import org.openide.util.lookup.InstanceContent;
public class MantisRepository {
public static final String PROP_SCHEDULE_DATE_FIELD = "MantisRepository.scheduleDateField";
public static final String PROP_SCHEDULE_LENGTH_FIELD = "MantisRepository.scheduleLengthField";
public static final String PROP_COMMIT_RESOLUTION_FIELD = "MantisRepository.commitResolution";
public static final String PROP_COMMIT_STATUS_FIELD = "MantisRepository.commitStatus";
private static final String BASE_CONFIG_PATH = "MantisIntegration"; // NOI18N
private static final Logger logger = Logger.getLogger(MantisRepository.class.getName());
private static final Image ICON = ImageUtilities.loadImage(
"eu/doppel_helix/netbeans/mantisintegration/icon.png");
private final transient InstanceContent ic;
private final transient Capabilities capabilities = new Capabilities(this);
private final IssueCache cache = new IssueCache();
private final RequestProcessor requestProzessor = new RequestProcessor(MantisRepository.class);
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
private RepositoryInfo info;
private MantisRepositoryController controller;
private MantisConnectPortType client;
private Version version;
private IssueInfosHandler issueInfosHandler;
private UserData account = null;
private MantisRepositoryQueryStore queryStore;
private ExceptionHandler exceptionHandler;
private final transient MasterData masterData = new MasterData(this);
String getBaseConfigPath() {
return String.format("%s/%s", BASE_CONFIG_PATH, getInfo().getID());
}
FileObject getBaseConfigFileObject() {
FileObject root = FileUtil.getConfigRoot();
try {
return FileUtil.createFolder(root, getBaseConfigPath());
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
public synchronized IssueInfosHandler getIssueInfosHandler() {
if(issueInfosHandler == null) {
issueInfosHandler = new IssueInfosHandler(this);
}
return issueInfosHandler;
}
private static MantisConnectPortType initClient(String baseUrl, String httpUsername, String httpPassword) throws ServiceException, MalformedURLException {
ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(MantisRepository.class.getClassLoader());
MantisConnectPortType result = null;
try {
if (!baseUrl.endsWith("/api/soap/mantisconnect.php")) {
baseUrl += "/api/soap/mantisconnect.php";
}
MantisConnectLocator mcl = new MantisConnectLocator();
result = mcl.getMantisConnectPort(new URL(baseUrl));
if(httpUsername != null && (!httpUsername.isEmpty())
&& httpPassword != null && (!httpPassword.isEmpty())) {
// enable Basic HTTP Authentication:
((Stub) result)._setProperty(Call.USERNAME_PROPERTY, httpUsername);
((Stub) result)._setProperty(Call.PASSWORD_PROPERTY, httpPassword);
}
if (MantisRepository.class.desiredAssertionStatus()) {
EDTInvocationHandler invocationHandler = new EDTInvocationHandler(result);
result = (MantisConnectPortType) Proxy.newProxyInstance(
MantisRepository.class.getClassLoader(),
new Class[]{MantisConnectPortType.class},
invocationHandler);
}
} finally {
Thread.currentThread().setContextClassLoader(origLoader);
}
return result;
}
static ConnectionCheckResult checkConnection(String url, String username, String password, String httpUser, String httpPassword) throws ServiceException, RemoteException {
try {
ConnectionCheckResult ccr = new ConnectionCheckResult();
MantisConnectPortType mcpt = initClient(url, httpUser, httpPassword);
// Test Authentication information
mcpt.mc_projects_get_user_accessible(username, password);
ccr.setVersion(new Version(mcpt.mc_version()));
ccr.setResolutionList(mcpt.mc_enum_resolutions(username, password));
ccr.setStatusList(mcpt.mc_enum_status(username, password));
return ccr;
} catch (MalformedURLException ex) {
throw new ServiceException("Broken client url:" + url, ex);
}
}
public MantisRepository() {
ic = new InstanceContent();
init(null);
}
public MantisRepository(RepositoryInfo ri) {
this();
init(ri);
}
private void init(RepositoryInfo ri) {
if(ri == null) {
ri = new RepositoryInfo(
MantisConnector.ID + System.currentTimeMillis(),
MantisConnector.ID,
"http://<host>/<mantis-basepath>",
"",
"");
}
setInfo(ri);
queryStore = new MantisRepositoryQueryStore(this, pcs);
}
public RequestProcessor getRequestProcessor() {
return requestProzessor;
}
private IssueCache getIssueCache() {
return cache;
}
public Image getIcon() {
return ICON;
}
public void remove() {
// Done
}
public MantisRepositoryController getController() {
if (controller == null) {
controller = new MantisRepositoryController(this);
}
return controller;
}
public RepositoryInfo getInfo() {
return info;
}
public void setInfo(RepositoryInfo info) {
this.info = info;
client = null;
}
public MasterData getMasterData() {
return masterData;
}
public List<MantisIssue> getIssues(boolean onlyCached, String... issues) throws ServiceException, RemoteException {
BigInteger[] issueIds = new BigInteger[issues.length];
for(int i = 0; i < issues.length; i++) {
issueIds[i] = new BigInteger(issues[i]);
}
return getIssues(onlyCached, issueIds);
}
public List<MantisIssue> getIssues(boolean onlyCached, BigInteger... issues) throws ServiceException, RemoteException {
List<MantisIssue> results = new ArrayList<>();
for (int i = 0; i < issues.length; i++) {
MantisIssue issue = getIssueCache().getIssue(issues[i]);
if (issue == null && (! onlyCached)) {
issue = new MantisIssue(this);
issue.setId(issues[i]);
issue.refresh();
}
if(issue != null) {
results.add(issue);
}
}
return results;
}
public Collection<MantisIssue> simpleSearch(String criteria) throws ServiceException, RemoteException {
int pageSize = 1000;
List<MantisIssue> result = new ArrayList<>();
try {
BigInteger possibleId = new BigInteger(criteria);
List<MantisIssue> issues = getIssues(false, possibleId.toString());
result.addAll(issues);
} catch (NumberFormatException ex) {
}
if (result.isEmpty()) {
Pattern p = null;
try {
p = Pattern.compile(criteria);
} catch (PatternSyntaxException ex) {
}
MantisConnectPortType mcpt = getClient();
List<BigInteger> matchingIds = new ArrayList<>();
OUTER:
for (ProjectData project : masterData.getProjects()) {
for (int i = 0; i < 1000; i++) {
IssueHeaderData[] ids = mcpt.mc_project_get_issue_headers(
info.getUsername(),
new String(info.getPassword()),
project.getId(),
BigInteger.valueOf(i),
BigInteger.valueOf(pageSize));
for (IssueHeaderData id : ids) {
if (id.getSummary().contains(criteria)
|| (p != null && p.matcher(id.getSummary()).find())) {
matchingIds.add(id.getId());
if (result.size() > 250) {
break OUTER;
}
}
}
if (ids.length < pageSize) {
break;
}
}
}
result.addAll(getIssues(false, matchingIds.toArray(new BigInteger[0])));
}
return result;
}
public MantisQuery createQuery() {
return new MantisQuery(this);
}
public MantisQuery getQuery(String id) {
return queryStore.getMantisQuery(id);
}
public void saveQuery(MantisQuery mq, boolean createIfNotExists) {
queryStore.saveMantisQuery(mq, createIfNotExists);
}
public MantisIssue createIssue() {
return new MantisIssue(this);
}
public Collection<MantisQuery> getQueries() {
return queryStore.getQueries();
}
public void deleteQuery(String id) {
queryStore.removeMantisQuery(id);
}
public Version getVersion() throws ServiceException, RemoteException {
if (version == null) {
String versionString = getClient().mc_version();
version = new Version(versionString);
}
return version;
}
public URI getIssueUrl(MantisIssue issue) {
try {
if (issue.getId() != null) {
return new URI(getBaseUrl() + "/view.php?id=" + issue.getIdAsString());
} else {
return new URI(getBaseUrl());
}
} catch (URISyntaxException ex) {
logger.log(Level.WARNING, "Failed to create issue url", ex);
return null;
}
}
protected String getBaseUrl() {
String baseUrl = info.getUrl();
return cleanUpUrl(baseUrl);
}
private static String cleanUpUrl(String baseUrl) {
if (baseUrl.endsWith("/api/soap/mantisconnect.php")) {
baseUrl = baseUrl.replaceAll("/api/soap/mantisconnect.php$", "");
}
if (baseUrl.endsWith("/")) {
baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
}
return baseUrl;
}
/**
* Note! Only present in mantis 1.2.12 or later
*
* @return
* @throws ServiceException
* @throws RemoteException
*/
public UserData getAccount() throws ServiceException, RemoteException {
if (account == null) {
account = getClient().mc_login(info.getUsername(),
new String(info.getPassword()));
}
return account;
}
protected synchronized MantisConnectPortType getClient() throws ServiceException {
if (client == null) {
String baseUrl = getBaseUrl();
try {
client = initClient(baseUrl, info.getHttpUsername(), new String(info.getHttpPassword()));
} catch (MalformedURLException ex) {
throw new ServiceException("Broken client url:" + baseUrl, ex);
}
}
return client;
}
public boolean updateIssueFromRepository(final MantisIssue issue) throws ServiceException, RemoteException {
try {
if (issue.getId() == null) {
return false;
}
MantisConnectPortType mcpt = getClient();
IssueData id = mcpt.mc_issue_get(
info.getUsername(),
new String(info.getPassword()),
issue.getId());
issue.updateFromIssueData(id);
getIssueCache().setIssueData(id.getId(), issue);
return true;
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
public void addIssue(final MantisIssue issue, IssueData newData)
throws ServiceException, RemoteException {
if (issue.getId() != null) {
this.updateIssue(issue, newData);
return;
}
MantisConnectPortType mcpt = getClient();
BigInteger id = mcpt.mc_issue_add(
info.getUsername(),
new String(info.getPassword()),
newData);
issue.setId(id);
issue.refresh();
}
public void updateIssue(final MantisIssue issue, IssueData newData)
throws ServiceException, RemoteException {
if (issue.getId() == null) {
addIssue(issue, newData);
return;
}
MantisConnectPortType mcpt = getClient();
mcpt.mc_issue_update(
info.getUsername(),
new String(info.getPassword()),
issue.getId(),
newData);
issue.refresh();
}
public void addComment(MantisIssue issue, String comment, ObjectRef viewState, BigInteger timetracking)
throws ServiceException, RemoteException {
MantisConnectPortType mcpt = getClient();
IssueNoteData ind = new IssueNoteData();
ind.setText(comment);
ind.setView_state(viewState);
if(timetracking == null) {
ind.setTime_tracking(BigInteger.ZERO);
} else {
ind.setTime_tracking(timetracking);
}
mcpt.mc_issue_note_add(
info.getUsername(),
new String(info.getPassword()),
issue.getId(),
ind);
issue.refresh();
}
public byte[] getFile(MantisIssue mi, AttachmentData ad) throws ServiceException, RemoteException{
MantisConnectPortType mcpt = getClient();
return mcpt.mc_issue_attachment_get(
info.getUsername(),
new String(info.getPassword()),
ad.getId());
}
public void removeFile(MantisIssue mi, AttachmentData ad) throws ServiceException, RemoteException {
MantisConnectPortType mcpt = getClient();
mcpt.mc_issue_attachment_delete(
info.getUsername(),
new String(info.getPassword()),
ad.getId());
updateIssueFromRepository(mi);
}
public void addFile(MantisIssue issue, File f, String comment) throws ServiceException, RemoteException, IOException {
MantisConnectPortType mcpt = getClient();
mcpt.mc_issue_attachment_add(
info.getUsername(),
new String(info.getPassword()),
issue.getId(),
f.getName(),
null,
fileGetContents(f));
if (comment != null && (!comment.isEmpty())) {
IssueNoteData ind = new IssueNoteData();
ind.setText(comment);
mcpt.mc_issue_note_add(
info.getUsername(),
new String(info.getPassword()),
issue.getId(),
ind);
}
issue.refresh();
}
public void removeRelationship(final MantisIssue issue, final RelationshipData rd)
throws ServiceException, RemoteException {
MantisConnectPortType mcpt = getClient();
mcpt.mc_issue_relationship_delete(
info.getUsername(),
new String(info.getPassword()),
issue.getId(),
rd.getId());
issue.refresh();
}
public void addRelationship(final MantisIssue issue, final ObjectRef type, final BigInteger target)
throws ServiceException, RemoteException {
MantisConnectPortType mcpt = getClient();
RelationshipData rd = new RelationshipData();
rd.setTarget_id(target);
rd.setType(type);
mcpt.mc_issue_relationship_add(
info.getUsername(),
new String(info.getPassword()),
issue.getId(),
rd);
issue.refresh();
}
public List<MantisIssue> findIssues(MantisQuery mq) throws ServiceException, RemoteException {
MantisConnectPortType mcpt = getClient();
Set<BigInteger> matchingIds = new HashSet<>();
if (mq.getServersideFilterId() != null) {
IssueHeaderData[] ids = mcpt.mc_filter_get_issue_headers(
info.getUsername(),
new String(info.getPassword()),
mq.getProjectId(),
mq.getServersideFilterId(),
BigInteger.valueOf(0),
BigInteger.valueOf(-1));
for (IssueHeaderData id : ids) {
if (mq.matchesFilter(id)) {
matchingIds.add(id.getId());
}
}
} else {
IssueHeaderData[] ids;
if (getVersion().compareTo(new Version("2.0.0")) >= 0) {
// The mc_project_get_issue_headers suppresses configurable
// issue states (by default the closed issues). The
// mc_filter_search_issue_headers was added with mantis 2.0.0
// and is even useful if no filter is specified, as at least
// this method returns the complete list
ids = mcpt.mc_filter_search_issue_headers(
info.getUsername(),
new String(info.getPassword()),
mq.getAsServerFilter(),
BigInteger.valueOf(0),
BigInteger.valueOf(-1)
);
} else {
ids = mcpt.mc_project_get_issue_headers(
info.getUsername(),
new String(info.getPassword()),
mq.getProjectId(),
BigInteger.valueOf(0),
BigInteger.valueOf(-1));
}
for (IssueHeaderData id : ids) {
if (mq.matchesFilter(id)) {
matchingIds.add(id.getId());
}
}
}
return getIssues(false, matchingIds.toArray(new BigInteger[matchingIds.size()]));
}
public void addTag(MantisIssue issue, String... newTagsString) throws ServiceException, RemoteException {
MantisConnectPortType mcpt = getClient();
ObjectRef[] oldList = issue.getTags();
List<TagData> newList = new ArrayList<>();
if (oldList != null) {
for (ObjectRef or : oldList) {
newList.add(masterData.getTag(or.getId()));
}
}
for (String tag : newTagsString) {
TagData newTag = masterData.addTag(tag);
boolean inList = false;
for (TagData td : newList) {
if (td.getId().equals(newTag.getId())) {
inList = true;
break;
}
}
if (!inList) {
newList.add(newTag);
}
}
mcpt.mc_issue_set_tags(
info.getUsername(),
new String(info.getPassword()),
issue.getId(),
newList.toArray(new TagData[0]));
issue.refresh();
}
public void removeTag(MantisIssue issue, ObjectRef... tagsToBeRemoved) throws ServiceException, RemoteException {
List<BigInteger> toBeRemoved = new ArrayList<>();
for (ObjectRef tag : tagsToBeRemoved) {
toBeRemoved.add(tag.getId());
}
ObjectRef[] oldList = issue.getTags();
List<TagData> newList = new ArrayList<>();
if (oldList != null) {
for (ObjectRef or : oldList) {
if (!toBeRemoved.contains(or.getId())) {
newList.add(masterData.getTag(or.getId()));
}
}
}
getClient().mc_issue_set_tags(
info.getUsername(),
new String(info.getPassword()),
issue.getId(),
newList.toArray(new TagData[0]));
issue.refresh();
}
public Capabilities getCapabilities() {
return capabilities;
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcs.removePropertyChangeListener(listener);
}
private static byte[] fileGetContents(File f) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (FileInputStream fis = new FileInputStream(f)) {
int read;
byte[] buffer = new byte[1024];
while ((read = fis.read(buffer)) > 0) {
baos.write(buffer, 0, read);
}
}
return baos.toByteArray();
}
public ExceptionHandler getExceptionHandler() {
if(exceptionHandler == null) {
exceptionHandler = new ExceptionHandler(this);
}
return exceptionHandler;
}
public ObjectRef getCommitStatus() {
return readObjectRef(info, PROP_COMMIT_STATUS_FIELD);
}
public ObjectRef getCommitResolution() {
return readObjectRef(info, PROP_COMMIT_RESOLUTION_FIELD);
}
static ObjectRef readObjectRef(RepositoryInfo ri, String key) {
String fieldString = ri.getValue(key);
if (fieldString != null) {
String[] parts = fieldString.split("#", 2);
if(parts.length == 2) {
try {
return new ObjectRef(new BigInteger(parts[0]), parts[1]);
} catch (NumberFormatException ex) {
return null;
}
} else {
return null;
}
} else {
return null;
}
}
static void writeObjectRef(RepositoryInfo ri, String key, ObjectRef value) {
if(value == null) {
ri.putValue(key, null);
} else {
ri.putValue(key, value.getId() + "#" + value.getName());
}
}
}