diff --git a/LICENSE.TXT b/LICENSE.TXT
new file mode 100644
index 00000000..5e1186fb
--- /dev/null
+++ b/LICENSE.TXT
@@ -0,0 +1,3 @@
+These software source code resources are distributed under the
+terms of the MIT License.
+http://opensource.org/licenses/MIT
diff --git a/README.TXT b/README.TXT
new file mode 100644
index 00000000..a04effff
--- /dev/null
+++ b/README.TXT
@@ -0,0 +1,76 @@
+Haiku Depot Server
+~~~~~~~~~~~~~~~~~~
+
+This collection of files represents the source code for the "Haiku Depot Server"; a web-services and HTML environment for working with Haiku packages. This is a maven java project that primarily builds an application server.
+
+---
+REQUIREMENTS, BUILD AND SETUP
+
+To build and use this software, you will require;
+
+* Java JDK 1.6 or better
+* Apache Maven 3.0.3 or better
+* A Postgres 9.1 or better database server
+* An internet connection
+
+On a debian 7 host, the following packages can be installed;
+
+ apt-get install default-jdk
+ apt-get install maven
+ apt-get install postgresql postgresql-client
+
+The project consists of a number of modules. The "haikudepotserver-webapp" is the application server module. This module requires a database in order to function. It is expecting to work with a Postgres database server. Create a blank postgres database and ensure that you are able to access the database over an internet socket authenticating as some user. You can leave the database devoid of schema objects for now because the application server will populate necessary schema objects on the first launch.
+
+The following file in the "haikudepotserver-webapp" is a template configuration file for the application server;
+
+ src/main/resources/local-sample.properties
+
+Copy this to an adjacent file in the same directory called "local.properties". You will need to edit properties starting with "jdbc..." in order to let the application server know how to access your postgres database.
+
+The first build will take longer than 'normal' because it will need to download a number of dependencies from the internet in order to undertake the build. Some downloads are related to web-resources. These downloads are only indirectly managed by the maven build process. For this reason, your first step should be to complete a command-line build by issuing the following command in the same directory as this file;
+
+ mvn package
+
+This step will ensure that the web-resources are populated. You should now be able to either continue to use the project in the command line environment or you can switch to use a java IDE.
+
+To start-up the application server for development purposes, issue the following command from the same top level of the project; the same directory as this file.
+
+ mvn org.apache.tomcat.maven:tomcat7-maven-plugin:2.1:run
+
+This may take some time to start-up; especially the first time. Once it has started-up, it should be possible to connect to the application server using the following URL;
+
+ http://localhost:8080/
+
+There won't be any repositories or data loaded, and because of this, it is not possible to view any data. Now a repository can be added to obtain packages from. Open a SQL terminal and add a repository;
+
+ INSERT INTO
+ haikudepot.repository (
+ id, active, create_timestamp, modify_timestamp,
+ architecture_id, code, url)
+ VALUES (
+ nextval('haikudepot.repository_seq'), true, now(), now(),
+ (SELECT id FROM haikudepot.architecture WHERE code='x86'), 'test', 'file:///tmp/repo.hpkr');
+
+This artificial repository will obtain a file from the local file system's temporary directory. You could, for example, take the test HPKR file supplied in the test resources of the "haikudepotserver-packagefile" module and place this at /tmp/repo.hpkr. Now it should be possible to prompt the system to take-up the repository data by dispatching a URL of this form using a tool such as curl;
+
+ curl "http://localhost:8080/importrepositorydata?code=test"
+
+You should now refresh your browser and it ought to be possible to view the packages that have been imported from the test file.
+
+---
+API
+
+The API for communicating with the server is described in the "haikudepotserver-api1" module. This contains DTO model objects describing the objects to be used in API calls as well as interfaces that describe the API calls that are available. The application server vends the API as JSON-RPC. More information about JSON-RPC can be found here;
+
+ http://www.jsonrpc.org/
+
+This API is intended to be used for the single-page web application as well as a desktop application.
+
+---
+HPKR HANDLING
+
+Haiku packages are described using HPK* files and these are described here;
+
+ http://dev.haiku-os.org/wiki/PackageManagement
+
+The "haikudepotserver-packagefile" module contains handling for HPKR files. Given the requirements of the application server this handling is limited to read-only access.
diff --git a/haikudepotserver-api1/pom.xml b/haikudepotserver-api1/pom.xml
new file mode 100644
index 00000000..2bec96ed
--- /dev/null
+++ b/haikudepotserver-api1/pom.xml
@@ -0,0 +1,25 @@
+
+
+
+ haikudepotserver-parent
+ org.haikuos
+ ../haikudepotserver-parent
+ 1.0.1-SNAPSHOT
+
+
+ 4.0.0
+ org.haikuos
+ haikudepotserver-api1
+ jar
+
+
+
+ com.googlecode
+ jsonrpc4j
+
+
+
+
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/CaptchaApi.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/CaptchaApi.java
new file mode 100644
index 00000000..cc06b917
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/CaptchaApi.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1;
+
+import com.googlecode.jsonrpc4j.JsonRpcService;
+import org.haikuos.haikudepotserver.api1.model.captcha.GenerateCaptchaRequest;
+import org.haikuos.haikudepotserver.api1.model.captcha.GenerateCaptchaResult;
+
+/**
+ *
This API is to do with captchas. A captcha is a small image that is shown to a user in order for the user to
+ * supply some textual response from the image in order to verify that the operator is likely to be human and not a
+ * computer. This helps to prevent machine-hacking of systems. This API is able to provide a captcha and other
+ * APIs require that a 'captcha response' is supplied as part of a request. In general a captcha is valid for a
+ * certain length of time.
This method will return a captcha that can be used in systems where a captcha response (generated by a
+ * human) is required to be supplied with an API request.
+ */
+
+ GenerateCaptchaResult generateCaptcha(GenerateCaptchaRequest generateCaptchaRequest);
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/MiscellaneousApi.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/MiscellaneousApi.java
new file mode 100644
index 00000000..f4c73430
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/MiscellaneousApi.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1;
+
+import com.googlecode.jsonrpc4j.JsonRpcService;
+import org.haikuos.haikudepotserver.api1.model.miscellaneous.GetAllArchitecturesRequest;
+import org.haikuos.haikudepotserver.api1.model.miscellaneous.GetAllArchitecturesResult;
+import org.haikuos.haikudepotserver.api1.model.miscellaneous.GetAllMessagesRequest;
+import org.haikuos.haikudepotserver.api1.model.miscellaneous.GetAllMessagesResult;
+
+@JsonRpcService("/api/v1/miscellaneous")
+public interface MiscellaneousApi {
+
+ /**
+ *
This method will return all of the localization messages that might be able to be displayed
+ * to the user from the result of validation problems and so on.
This method will return a list of all of the possible architectures in the system such as x86 or arm.
+ * Note that this will explicitly exclude the pseudo-architectures of "source" and "any".
+ */
+
+ GetAllArchitecturesResult getAllArchitectures(GetAllArchitecturesRequest getAllArchitecturesRequest);
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/PkgApi.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/PkgApi.java
new file mode 100644
index 00000000..f23b2731
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/PkgApi.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1;
+
+import com.googlecode.jsonrpc4j.JsonRpcService;
+import org.haikuos.haikudepotserver.api1.model.pkg.GetPkgRequest;
+import org.haikuos.haikudepotserver.api1.model.pkg.GetPkgResult;
+import org.haikuos.haikudepotserver.api1.model.pkg.SearchPkgsRequest;
+import org.haikuos.haikudepotserver.api1.model.pkg.SearchPkgsResult;
+import org.haikuos.haikudepotserver.api1.support.ObjectNotFoundException;
+
+/**
+ *
This API is for access to packages and package versions.
This method will return a package and the specified versions. It will throw an
+ * {@link org.haikuos.haikudepotserver.api1.support.ObjectNotFoundException} if the package was not able to be located.
+ */
+
+ GetPkgResult getPkg(GetPkgRequest request) throws ObjectNotFoundException;
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/UserApi.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/UserApi.java
new file mode 100644
index 00000000..9e3e24f8
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/UserApi.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1;
+
+import com.googlecode.jsonrpc4j.JsonRpcService;
+import org.haikuos.haikudepotserver.api1.model.user.*;
+import org.haikuos.haikudepotserver.api1.support.ObjectNotFoundException;
+
+/**
+ *
This interface defines operations that can be undertaken around users.
This method will create a user in the system. It is identified by a username
+ * and authenticated by a password. The password is supplied in the clear.
This method will get the user identified by the nickname in the request object.
+ * If no user was able to be found an instance of {@link org.haikuos.haikudepotserver.api1.support.ObjectNotFoundException}
+ * is thrown.
This method will allow a client to authenticate against the server. If this is
+ * successful then the client will know that it is OK to use the authentication
+ * principal and credentials for further API calls.
+ */
+
+ AuthenticateUserResult authenticateUser(AuthenticateUserRequest authenticateUserRequest);
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/captcha/GenerateCaptchaRequest.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/captcha/GenerateCaptchaRequest.java
new file mode 100644
index 00000000..2ad01969
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/captcha/GenerateCaptchaRequest.java
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.captcha;
+
+public class GenerateCaptchaRequest {
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/captcha/GenerateCaptchaResult.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/captcha/GenerateCaptchaResult.java
new file mode 100644
index 00000000..818037af
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/captcha/GenerateCaptchaResult.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.captcha;
+
+public class GenerateCaptchaResult {
+
+ /**
+ *
This token uniquely identifies the captcha.
+ */
+
+ public String token;
+
+ /**
+ *
This is a base-64 encoded image of the captcha. It could, for example, be used with a data url to render
+ * the image in an "img" tag on a web page.
+ */
+
+ public String pngImageDataBase64;
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetAllArchitecturesRequest.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetAllArchitecturesRequest.java
new file mode 100644
index 00000000..5d62daae
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetAllArchitecturesRequest.java
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.miscellaneous;
+
+public class GetAllArchitecturesRequest {
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetAllArchitecturesResult.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetAllArchitecturesResult.java
new file mode 100644
index 00000000..39a2beeb
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetAllArchitecturesResult.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.miscellaneous;
+
+import java.util.List;
+
+public class GetAllArchitecturesResult {
+
+ public List architectures;
+
+ public static class Architecture {
+ public String code;
+ }
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetAllMessagesRequest.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetAllMessagesRequest.java
new file mode 100644
index 00000000..51729838
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetAllMessagesRequest.java
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.miscellaneous;
+
+public class GetAllMessagesRequest {
+
+ // add locale here?
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetAllMessagesResult.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetAllMessagesResult.java
new file mode 100644
index 00000000..a2c72dbe
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetAllMessagesResult.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.miscellaneous;
+
+import java.util.Map;
+
+public class GetAllMessagesResult {
+
+ /**
+ *
This is a key-value pair map of the localization messages.
+ */
+
+ public Map messages;
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgRequest.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgRequest.java
new file mode 100644
index 00000000..37b82d9e
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgRequest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.pkg;
+
+public class GetPkgRequest {
+
+ /**
+ *
This type defines the versions that should be sent back in the result. If the client were
+ * only interested in the latest version for example, then it should use the "LATEST" value.
This is the name of the package that you wish to obtain.
+ */
+
+ public String name;
+
+ /**
+ *
Only a version of the package for this architecture will be returned. Note that this also
+ * includes the pseudo-architectures "any" and "source".
+ */
+
+ public String architectureCode;
+
+ public VersionType versionType;
+
+ // TODO - natural language
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgResult.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgResult.java
new file mode 100644
index 00000000..33881555
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgResult.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.pkg;
+
+import java.util.List;
+
+/**
+ *
This is the result model that comes back from the get packages API invocation.
+ */
+
+public class GetPkgResult {
+
+ public String name;
+
+ public List versions;
+
+ public static class Version {
+
+ public String major;
+ public String minor;
+ public String micro;
+ public String preRelease;
+ public Integer revision;
+
+ public String architectureCode;
+ public String summary;
+ public String description;
+ public String repositoryCode;
+
+ public List licenses;
+ public List copyrights;
+ public List urls;
+
+ }
+
+ public static class Url {
+
+ public String url;
+ public String urlTypeCode;
+
+ }
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/SearchPkgsRequest.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/SearchPkgsRequest.java
new file mode 100644
index 00000000..79fb4b0a
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/SearchPkgsRequest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.pkg;
+
+/**
+ *
This is the model object that is used to define the request to search for packages in the system.
+ */
+
+public class SearchPkgsRequest {
+
+ public enum ExpressionType {
+ CONTAINS
+ }
+
+ public String expression;
+
+ public String architectureCode;
+
+ public ExpressionType expressionType;
+
+ public Integer offset;
+
+ public Integer limit;
+
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/SearchPkgsResult.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/SearchPkgsResult.java
new file mode 100644
index 00000000..63897ee7
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/SearchPkgsResult.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.pkg;
+
+import java.util.List;
+
+public class SearchPkgsResult {
+
+ public List pkgs;
+
+ public Boolean hasMore;
+
+ public static class Pkg {
+ public String name;
+ public Version version;
+ }
+
+ public static class Version {
+ public String major;
+ public String minor;
+ public String micro;
+ public String preRelease;
+ public Integer revision;
+ }
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/AuthenticateUserRequest.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/AuthenticateUserRequest.java
new file mode 100644
index 00000000..84f73525
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/AuthenticateUserRequest.java
@@ -0,0 +1,13 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.user;
+
+public class AuthenticateUserRequest {
+
+ public String nickname;
+ public String passwordClear;
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/AuthenticateUserResult.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/AuthenticateUserResult.java
new file mode 100644
index 00000000..db6c41ec
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/AuthenticateUserResult.java
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.user;
+
+public class AuthenticateUserResult {
+
+ public Boolean authenticated;
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/CreateUserRequest.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/CreateUserRequest.java
new file mode 100644
index 00000000..105ff72b
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/CreateUserRequest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.user;
+
+public class CreateUserRequest {
+
+ public String nickname;
+ public String passwordClear;
+
+ /**
+ *
The captcha token is obtained from an earlier invocation to the
+ * {@link org.haikuos.haikudepotserver.api1.CaptchaApi} method to get
+ * a captcha. This identifies the captcha for which the captcha response should
+ * correlate.
+ */
+
+ public String captchaToken;
+
+ /**
+ *
This is the human-supplied text string that matches the image that would have been
+ * provided with the captcha that is identified by the cpatchaToken.
+ */
+
+ public String captchaResponse;
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/CreateUserResult.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/CreateUserResult.java
new file mode 100644
index 00000000..aadc1713
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/CreateUserResult.java
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.user;
+
+public class CreateUserResult {
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/GetUserRequest.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/GetUserRequest.java
new file mode 100644
index 00000000..b0078dcd
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/GetUserRequest.java
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.user;
+
+public class GetUserRequest {
+
+ public String nickname;
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/GetUserResult.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/GetUserResult.java
new file mode 100644
index 00000000..d58a5bca
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/GetUserResult.java
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.user;
+
+public class GetUserResult {
+
+ public String nickname;
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/CaptchaBadResponseException.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/CaptchaBadResponseException.java
new file mode 100644
index 00000000..b3515535
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/CaptchaBadResponseException.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.support;
+
+/**
+ *
This exception is thrown in the case where the user has mis-entered a captcha.
+ */
+
+public class CaptchaBadResponseException extends RuntimeException {
+
+ public CaptchaBadResponseException() {
+ super();
+ }
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/Constants.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/Constants.java
new file mode 100644
index 00000000..c1cb574a
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/Constants.java
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.support;
+
+public interface Constants {
+
+ public final static int ERROR_CODE_VALIDATION = -32800;
+ public final static int ERROR_CODE_OBJECTNOTFOUND = -32801;
+ public final static int ERROR_CODE_CAPTCHABADRESPONSE = -32802;
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/ObjectNotFoundException.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/ObjectNotFoundException.java
new file mode 100644
index 00000000..2db3f056
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/ObjectNotFoundException.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.support;
+
+/**
+ *
This exception is thrown when the system is not able to find an object in the system as part of an API
+ * invocation.
+ */
+
+public class ObjectNotFoundException extends Exception {
+
+ public String entityName;
+ public Object identifier;
+
+ public ObjectNotFoundException(String entityName, Object identifier) {
+ super();
+
+ if(null==entityName || 0==entityName.length()) {
+ throw new IllegalStateException("the entity name is required");
+ }
+
+ if(null==identifier) {
+ throw new IllegalStateException("the identifier is required");
+ }
+
+ this.entityName = entityName;
+ this.identifier = identifier;
+ }
+
+ public String getEntityName() {
+ return entityName;
+ }
+
+ public Object getIdentifier() {
+ return identifier;
+ }
+
+ @Override
+ public String getMessage() {
+ return String.format("the entity %s was not able to be found with the identifier %s",getEntityName(),getIdentifier().toString());
+ }
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/ValidationException.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/ValidationException.java
new file mode 100644
index 00000000..0838af33
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/ValidationException.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.support;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ *
This exception is thrown in the situation where a validation exception is thrown outside of the Cayenne
+ * infrastructure.
+ */
+
+public class ValidationException extends RuntimeException {
+
+ protected List validationFailures;
+
+ public ValidationException(ValidationFailure validationFailure) {
+ super();
+
+ if(null==validationFailure) {
+ throw new IllegalStateException();
+ }
+
+ this.validationFailures = Collections.singletonList(validationFailure);
+ }
+
+ public ValidationException(List validationFailures) {
+ super();
+
+ if(null==validationFailures) {
+ throw new IllegalStateException();
+ }
+
+ this.validationFailures = validationFailures;
+ }
+
+ public List getValidationFailures() {
+ return validationFailures;
+ }
+
+ @Override
+ public String getMessage() {
+ return String.format("%d validation failures",validationFailures.size());
+ }
+
+}
diff --git a/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/ValidationFailure.java b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/ValidationFailure.java
new file mode 100644
index 00000000..ab2eae74
--- /dev/null
+++ b/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/ValidationFailure.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.support;
+
+/**
+ *
This object models a validation failure in the system. The property indicates what element of the object in
+ * question has failed validation checks and the message indicates what was wrong with that element.
+ */
+
+public class ValidationFailure {
+
+ private String property;
+ private String message;
+
+ public ValidationFailure(String property, String message) {
+ super();
+
+ if(null==property || 0==property.length()) {
+ throw new IllegalStateException("the property is required for a validation failure");
+ }
+
+ if(null==message || 0==message.length()) {
+ throw new IllegalStateException("the message is required for a validation failure");
+ }
+
+ this.property = property;
+ this.message = message;
+ }
+
+ public String getProperty() {
+ return property;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s; %s",getProperty(),getMessage());
+ }
+
+}
diff --git a/haikudepotserver-packagefile/pom.xml b/haikudepotserver-packagefile/pom.xml
new file mode 100644
index 00000000..fa29e93c
--- /dev/null
+++ b/haikudepotserver-packagefile/pom.xml
@@ -0,0 +1,53 @@
+
+
+
+ haikudepotserver-parent
+ org.haikuos
+ ../haikudepotserver-parent
+ 1.0.1-SNAPSHOT
+
+
+ 4.0.0
+ org.haikuos
+ haikudepotserver-packagefile
+ jar
+
+
+
+
+ args4j
+ args4j
+
+
+
+ com.google.guava
+ guava
+
+
+
+ org.slf4j
+ slf4j-api
+
+
+ ch.qos.logback
+ logback-classic
+
+
+
+ junit
+ junit
+ test
+
+
+
+ org.easytesting
+ fest-assert
+ test
+
+
+
+
+
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/AttributeContext.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/AttributeContext.java
new file mode 100644
index 00000000..1dff0367
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/AttributeContext.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg;
+
+import org.haikuos.pkg.heap.HeapReader;
+
+/**
+ *
This object carries around pointers to other data structures and model objects that are required to
+ * support the processing of attributes.
+ */
+
+public class AttributeContext {
+
+ private StringTable stringTable;
+
+ private HeapReader heapReader;
+
+ public HeapReader getHeapReader() {
+ return heapReader;
+ }
+
+ public void setHeapReader(HeapReader heapReader) {
+ this.heapReader = heapReader;
+ }
+
+ public StringTable getStringTable() {
+ return stringTable;
+ }
+
+ public void setStringTable(StringTable stringTable) {
+ this.stringTable = stringTable;
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/AttributeIterator.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/AttributeIterator.java
new file mode 100644
index 00000000..885b12db
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/AttributeIterator.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Preconditions;
+import com.google.common.io.ByteArrayDataOutput;
+import com.google.common.io.ByteStreams;
+import org.haikuos.pkg.heap.HeapCoordinates;
+import org.haikuos.pkg.model.*;
+
+import java.math.BigInteger;
+import java.util.Iterator;
+
+/**
+ *
This object is able to provide an iterator through all of the attributes at a given offset in the chunks. The
+ * chunk data is supplied through an instance of {@link AttributeContext}. It will work through all of the
+ * attributes serially and will also process all of the child-attributes as well. The iteration process means that
+ * less in-memory data is required to process a relatively long list of attributes.
+ *
+ *
Use the method {@link #hasNext()} to find out if there is another attribute to read and {@link #next()} in
+ * order to obtain the next attribute.
+ *
+ *
Note that this does not actually implement {@link Iterator} because it needs to throw Hpk exceptions
+ * which would mean that it were not compliant with the @{link Iterator} interface.
+ */
+
+public class AttributeIterator {
+
+ private final static int ATTRIBUTE_TYPE_INVALID = 0;
+ private final static int ATTRIBUTE_TYPE_INT = 1;
+ private final static int ATTRIBUTE_TYPE_UINT = 2;
+ private final static int ATTRIBUTE_TYPE_STRING = 3;
+ private final static int ATTRIBUTE_TYPE_RAW = 4;
+
+ private final static int ATTRIBUTE_ENCODING_INT_8_BIT = 0;
+ private final static int ATTRIBUTE_ENCODING_INT_16_BIT = 1;
+ private final static int ATTRIBUTE_ENCODING_INT_32_BIT = 2;
+ private final static int ATTRIBUTE_ENCODING_INT_64_BIT = 3;
+
+ private final static int ATTRIBUTE_ENCODING_STRING_INLINE = 0;
+ private final static int ATTRIBUTE_ENCODING_STRING_TABLE = 1;
+
+ private final static int ATTRIBUTE_ENCODING_RAW_INLINE = 0;
+ private final static int ATTRIBUTE_ENCODING_RAW_HEAP = 1;
+
+ private long offset;
+
+ private AttributeContext context;
+
+ private BigInteger nextTag = null;
+
+ public AttributeIterator(AttributeContext context, long offset) {
+ super();
+
+ Preconditions.checkNotNull(context);
+ Preconditions.checkState(offset >= 0 && offset < Integer.MAX_VALUE);
+
+ this.offset = offset;
+ this.context = context;
+ }
+
+ public AttributeContext getContext() {
+ return context;
+ }
+
+ public long getOffset() {
+ return offset;
+ }
+
+ private int deriveAttributeTagType(BigInteger tag) {
+ return tag.subtract(BigInteger.valueOf(1l)).shiftRight(7).and(BigInteger.valueOf(0x7l)).intValue();
+ }
+
+ private int deriveAttributeTagId(BigInteger tag) {
+ return tag.subtract(BigInteger.valueOf(1l)).and(BigInteger.valueOf(0x7fl)).intValue();
+ }
+
+ private int deriveAttributeTagEncoding(BigInteger tag) {
+ return tag.subtract(BigInteger.valueOf(1l)).shiftRight(11).and(BigInteger.valueOf(3l)).intValue();
+ }
+
+ private boolean deriveAttributeTagHasChildAttributes(BigInteger tag) {
+ return 0!=tag.subtract(BigInteger.valueOf(1l)).shiftRight(10).and(BigInteger.valueOf(1l)).intValue();
+ }
+
+ private BigInteger getNextTag() {
+ if(null==nextTag) {
+ nextTag = readUnsignedLeb128();
+ }
+
+ return nextTag;
+ }
+
+ private BigInteger readUnsignedLeb128() {
+ BigInteger result = BigInteger.valueOf(0l);
+ int shift = 0;
+
+ while(true) {
+ int b = context.getHeapReader().readHeap(offset);
+ offset++;
+
+ result = result.or(BigInteger.valueOf((long) (b & 0x7f)).shiftLeft(shift));
+
+ if(0 == (b & 0x80)) {
+ return result;
+ }
+
+ shift+=7;
+ }
+ }
+
+ private void ensureValidEncodingForInt(int encoding) {
+ switch(encoding) {
+ case ATTRIBUTE_ENCODING_INT_8_BIT:
+ case ATTRIBUTE_ENCODING_INT_16_BIT:
+ case ATTRIBUTE_ENCODING_INT_32_BIT:
+ case ATTRIBUTE_ENCODING_INT_64_BIT:
+ break;
+
+ default:
+ throw new IllegalStateException("unknown encoding on a signed integer");
+ }
+ }
+
+ /**
+ *
This method allows the caller to discover if there is another attribute to get off the iterator.
This method will return the next {@link org.haikuos.pkg.model.Attribute}. If there is not another value to return then
+ * this method will return null. It will throw an instance of @{link HpkException} in any situation in which
+ * it is not able to parse the data or chunks such that it is not able to read the next attribute.
+ */
+
+ public Attribute next() throws HpkException {
+
+ Attribute result = null;
+
+ // first, the LEB128 has to be read in which is the 'tag' defining what sort of attribute this is that
+ // we are dealing with.
+
+ BigInteger tag = getNextTag();
+
+ // if we encounter 0 tag then we know that we have finished the list.
+
+ if(0!=tag.signum()) {
+
+ int encoding = deriveAttributeTagEncoding(tag);
+ int id = deriveAttributeTagId(tag);
+
+ if(id <= 0 || id >= AttributeId.values().length) {
+ throw new HpkException("illegal id; "+Integer.toString(id));
+ }
+ AttributeId attributeId = AttributeId.values()[id];
+
+ switch(deriveAttributeTagType(tag)) {
+
+ case ATTRIBUTE_TYPE_INVALID:
+ throw new HpkException("an invalid attribute tag type has been encountered");
+
+ case ATTRIBUTE_TYPE_INT:
+ {
+ ensureValidEncodingForInt(encoding);
+ byte[] buffer = new byte[encoding+1];
+ context.getHeapReader().readHeap(buffer,0,new HeapCoordinates(offset,encoding+1));
+ offset+=encoding+1;
+ result = new IntAttribute(attributeId, new BigInteger(buffer));
+ }
+ break;
+
+ case ATTRIBUTE_TYPE_UINT:
+ {
+ ensureValidEncodingForInt(encoding);
+ byte[] buffer = new byte[encoding+1];
+ context.getHeapReader().readHeap(buffer,0,new HeapCoordinates(offset,encoding+1));
+ offset+=encoding+1;
+ result = new IntAttribute(attributeId, new BigInteger(1,buffer));
+ }
+ break;
+
+ case ATTRIBUTE_TYPE_STRING:
+ {
+ switch(encoding) {
+
+ case ATTRIBUTE_ENCODING_STRING_INLINE:
+ {
+ ByteArrayDataOutput assembly = ByteStreams.newDataOutput();
+
+ while(null==result) {
+ int b = context.getHeapReader().readHeap(offset);
+ offset++;
+
+ if(0!=b) {
+ assembly.write(b);
+ }
+ else {
+ result = new StringInlineAttribute(
+ attributeId,
+ new String(
+ assembly.toByteArray(),
+ Charsets.UTF_8));
+ }
+ }
+ }
+ break;
+
+ case ATTRIBUTE_ENCODING_STRING_TABLE:
+ {
+ BigInteger index = readUnsignedLeb128();
+
+ if(index.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) > 0) {
+ throw new IllegalStateException("the string table index is preposterously large");
+ }
+
+ result = new StringTableRefAttribute(attributeId,index.intValue());
+ }
+ break;
+
+ default:
+ throw new HpkException("unknown string encoding; "+encoding);
+ }
+ }
+ break;
+
+ case ATTRIBUTE_TYPE_RAW:
+ {
+ switch(encoding) {
+ case ATTRIBUTE_ENCODING_RAW_INLINE:
+ {
+ BigInteger length = readUnsignedLeb128();
+
+ if(length.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) > 0) {
+ throw new HpkException("the length of the inline data is too large");
+ }
+
+ byte[] buffer = new byte[length.intValue()];
+ context.getHeapReader().readHeap(buffer,0,new HeapCoordinates(offset,length.intValue()));
+ offset += length.intValue();
+
+ result = new RawInlineAttribute(attributeId, buffer);
+ }
+ break;
+
+ case ATTRIBUTE_ENCODING_RAW_HEAP:
+ {
+ BigInteger rawLength = readUnsignedLeb128();
+ BigInteger rawOffset = readUnsignedLeb128();
+
+ if(rawLength.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) > 0) {
+ throw new HpkException("the length of the heap data is too large");
+ }
+
+ if(rawOffset.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) > 0) {
+ throw new HpkException("the offset of the heap data is too large");
+ }
+
+ result = new RawHeapAttribute(
+ attributeId,
+ new HeapCoordinates(
+ rawOffset.longValue(),
+ rawLength.longValue()));
+ }
+
+ default:
+ throw new HpkException("unknown raw encoding; "+encoding);
+ }
+ }
+ break;
+
+ default:
+ throw new HpkException("unable to read the tag type; "+deriveAttributeTagType(tag));
+
+ }
+
+ // each attribute id has a type associated with it; now check that the attribute matches
+ // its intended type.
+
+ if(result.getAttributeId().getAttributeType() != result.getAttributeType()) {
+ throw new HpkException(String.format(
+ "mismatch in attribute type for id %s; expecting %s, but got %s",
+ result.getAttributeId().getName(),
+ result.getAttributeId().getAttributeType(),
+ result.getAttributeType()));
+ }
+
+ // possibly there are child attributes after this attribute; if this is the
+ // case then open-up a new iterator to work across those and load them in.
+
+ if(deriveAttributeTagHasChildAttributes(tag)) {
+
+ AttributeIterator childAttributeIterator = new AttributeIterator(context, offset);
+
+ while(childAttributeIterator.hasNext()) {
+ result.addChildAttribute(childAttributeIterator.next());
+ }
+
+ offset = childAttributeIterator.getOffset();
+
+ }
+
+ nextTag = null;
+ }
+
+ return result;
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/FileHelper.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/FileHelper.java
new file mode 100644
index 00000000..6ce1278d
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/FileHelper.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.math.BigInteger;
+
+/**
+ *
This helps out with typical common reads that might be performed as part of
+ * parsing various values in the HPKR file.
+ */
+
+public class FileHelper {
+
+ private final static BigInteger MAX_BIGINTEGER_FILE = new BigInteger(Long.toString(Long.MAX_VALUE));
+
+ private byte[] buffer8 = new byte[8];
+
+ public int readUnsignedShortToInt(RandomAccessFile randomAccessFile) throws IOException, HpkException {
+
+ if(2!=randomAccessFile.read(buffer8,0,2)) {
+ throw new HpkException("not enough bytes read for an unsigned short");
+ }
+
+ int i0 = buffer8[0]&0xff;
+ int i1 = buffer8[1]&0xff;
+
+ return i0 << 8 | i1;
+ }
+
+ public long readUnsignedIntToLong(RandomAccessFile randomAccessFile) throws IOException, HpkException {
+
+ if(4!=randomAccessFile.read(buffer8,0,4)) {
+ throw new HpkException("not enough bytes read for an unsigned int");
+ }
+
+ long l0 = buffer8[0]&0xff;
+ long l1 = buffer8[1]&0xff;
+ long l2 = buffer8[2]&0xff;
+ long l3 = buffer8[3]&0xff;
+
+ return l0 << 24 | l1 << 16 | l2 << 8 | l3;
+ }
+
+ public BigInteger readUnsignedLong(RandomAccessFile randomAccessFile) throws IOException, HpkException {
+
+ if(8!=randomAccessFile.read(buffer8)) {
+ throw new HpkException("not enough bytes read for an unsigned long");
+ }
+
+ return new BigInteger(1, buffer8);
+ }
+
+ public long readUnsignedLongToLong(RandomAccessFile randomAccessFile) throws IOException, HpkException {
+
+ BigInteger result = readUnsignedLong(randomAccessFile);
+
+ if(result.compareTo(MAX_BIGINTEGER_FILE) > 0) {
+ throw new HpkException("the hpkr file contains an unsigned long which is larger than can be represented in a java long");
+ }
+
+ return result.longValue();
+ }
+
+ public char[] readMagic(RandomAccessFile randomAccessFile) throws IOException, HpkException {
+
+ if(4!=randomAccessFile.read(buffer8,0,4)) {
+ throw new HpkException("not enough bytes read for a 4-byte magic");
+ }
+
+ return new char[] {
+ (char) buffer8[0],
+ (char) buffer8[1],
+ (char) buffer8[2],
+ (char) buffer8[3]
+ };
+ }
+
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkException.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkException.java
new file mode 100644
index 00000000..9e380b92
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkException.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg;
+
+/**
+ *
This type of exception is used through the Hpk file processing system to indicate that something has gone wrong
+ * with processing the Hpk data in some way.
+ */
+
+public class HpkException extends Exception {
+
+ public HpkException(String message) {
+ super(message);
+ }
+
+ public HpkException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkStringTable.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkStringTable.java
new file mode 100644
index 00000000..847c0b04
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkStringTable.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Preconditions;
+import org.haikuos.pkg.heap.HeapCoordinates;
+import org.haikuos.pkg.heap.HpkHeapReader;
+
+/**
+ *
The HPK* file format may contain a table of commonly used strings in a table. This object will represent
+ * those strings and will lazily load them from the heap as necessary.
+ */
+
+public class HpkStringTable implements StringTable {
+
+ private HpkHeapReader heapReader;
+
+ private long expectedCount;
+
+ private long heapLength;
+
+ private long heapOffset;
+
+ private String[] values = null;
+
+ public HpkStringTable(
+ HpkHeapReader heapReader,
+ long heapOffset,
+ long heapLength,
+ long expectedCount) {
+
+ super();
+
+ Preconditions.checkNotNull(heapReader);
+ Preconditions.checkState(heapOffset >= 0 && heapOffset < Integer.MAX_VALUE);
+ Preconditions.checkState(heapLength >= 0 && heapLength < Integer.MAX_VALUE);
+ Preconditions.checkState(expectedCount >= 0 && expectedCount < Integer.MAX_VALUE);
+
+ this.heapReader = heapReader;
+ this.expectedCount = expectedCount;
+ this.heapOffset = heapOffset;
+ this.heapLength = heapLength;
+
+ }
+
+ // TODO; could avoid the big read into a buffer by reading the heap byte by byte.
+ private String[] readStrings() throws HpkException {
+ String[] result = new String[(int) expectedCount];
+ byte[] stringsDataBuffer = new byte[(int) heapLength];
+
+ heapReader.readHeap(
+ stringsDataBuffer,
+ 0,
+ new HeapCoordinates(
+ heapOffset,
+ heapLength));
+
+ // now work through the data and load them into the strings.
+
+ int stringIndex = 0;
+ int offset = 0;
+
+ while(offset < stringsDataBuffer.length) {
+
+ if(0==stringsDataBuffer[offset]) {
+ if(stringIndex != result.length) {
+ throw new HpkException(String.format("expected to read %d package strings from the strings table, but actually found %d",expectedCount,stringIndex));
+ }
+
+ return result;
+ }
+
+ int start = offset;
+
+ while(0!=stringsDataBuffer[offset] && offset < stringsDataBuffer.length) {
+ offset++;
+ }
+
+ if(offset < stringsDataBuffer.length) {
+ result[stringIndex] = new String(stringsDataBuffer,start,offset-start, Charsets.UTF_8);
+ stringIndex++;
+ offset++;
+ }
+
+ }
+
+ throw new HpkException("expected to find the null-terminator for the list of strings, but was not able to find one.");
+ }
+
+ private String[] getStrings() throws HpkException {
+ if(null==values) {
+ if(0==heapLength) {
+ values = new String[] {};
+ }
+ else {
+ values = readStrings();
+ }
+ }
+
+ return values;
+ }
+
+ @Override
+ public String getString(int index) throws HpkException {
+ return getStrings()[index];
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkrFileExtractor.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkrFileExtractor.java
new file mode 100644
index 00000000..e0d08465
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkrFileExtractor.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg;
+
+import com.google.common.base.Preconditions;
+import org.haikuos.pkg.heap.HeapCompression;
+import org.haikuos.pkg.heap.HpkHeapReader;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.Arrays;
+
+/**
+ *
This object represents an object that can extract an Hpkr (Haiku Pkg Repository) file. If you are wanting to
+ * read HPKR files then you should instantiate an instance of this class and then make method calls to it in order to
+ * read values such as the attributes of the HPKR file.
+ */
+
+public class HpkrFileExtractor implements Closeable {
+
+ private File file;
+
+ private HpkrHeader header;
+
+ private HpkHeapReader heapReader;
+
+ private HpkStringTable attributesStringTable;
+
+ public HpkrFileExtractor(File file) throws IOException, HpkException {
+
+ super();
+ Preconditions.checkNotNull(file);
+ Preconditions.checkState(file.isFile() && file.exists());
+
+ this.file = file;
+ this.header = readHeader();
+
+ try {
+ heapReader = new HpkHeapReader(
+ file,
+ header.getHeapCompression(),
+ header.getHeaderSize(),
+ header.getHeapChunkSize(), // uncompressed size
+ header.getHeapSizeCompressed(), // including the compressed chunk lengths.
+ header.getHeapSizeUncompressed() // excludes the compressed chunk lengths.
+ );
+
+ attributesStringTable = new HpkStringTable(
+ heapReader,
+ header.getInfoLength(),
+ header.getPackagesStringsLength(),
+ header.getPackagesStringsCount());
+
+ }
+ catch(Exception e) {
+ close();
+ throw new HpkException("unable to setup the hpkr file extractor",e);
+ }
+ catch(Throwable th) {
+ close();
+ throw new RuntimeException("unable to setup the hpkr file extractor",th);
+ }
+ }
+
+ @Override
+ public void close() {
+ if(null!=heapReader) {
+ heapReader.close();
+ }
+ }
+
+ public AttributeContext getAttributeContext() {
+ AttributeContext context = new AttributeContext();
+ context.setHeapReader(heapReader);
+ context.setStringTable(attributesStringTable);
+ return context;
+ }
+
+ public AttributeIterator getPackageAttributesIterator() {
+ long offset = header.getInfoLength() + header.getPackagesStringsLength();
+ return new AttributeIterator(getAttributeContext(),offset);
+ }
+
+ private HpkrHeader readHeader() throws IOException, HpkException {
+ Preconditions.checkNotNull(file);
+
+ RandomAccessFile randomAccessFile = null;
+ FileHelper fileHelper = new FileHelper();
+
+ try {
+ randomAccessFile = new RandomAccessFile(file, "r");
+
+ if(!Arrays.equals(new char[] { 'h','p','k','r' }, fileHelper.readMagic(randomAccessFile))) {
+ throw new HpkException("magic incorrect at the start of the hpkr file");
+ }
+
+ HpkrHeader result = new HpkrHeader();
+
+ result.setHeaderSize(fileHelper.readUnsignedShortToInt(randomAccessFile));
+ result.setVersion(fileHelper.readUnsignedShortToInt(randomAccessFile));
+ result.setTotalSize(fileHelper.readUnsignedLongToLong(randomAccessFile));
+ result.setMinorVersion(fileHelper.readUnsignedShortToInt(randomAccessFile));
+
+ int compression = fileHelper.readUnsignedShortToInt(randomAccessFile);
+
+ // heap information
+ switch(compression) {
+ case 0:
+ result.setHeapCompression(HeapCompression.NONE);
+ break;
+
+ case 1:
+ result.setHeapCompression(HeapCompression.ZLIB);
+ break;
+
+ default:
+ throw new HpkException("unknown compression setting in header; "+compression);
+ }
+
+ result.setHeapChunkSize(fileHelper.readUnsignedIntToLong(randomAccessFile));
+ result.setHeapSizeCompressed(fileHelper.readUnsignedLongToLong(randomAccessFile));
+ result.setHeapSizeUncompressed(fileHelper.readUnsignedLongToLong(randomAccessFile));
+
+ // repository info
+ result.setInfoLength(fileHelper.readUnsignedIntToLong(randomAccessFile));
+ randomAccessFile.skipBytes(4); // reserved
+
+ // package attributes section
+ result.setPackagesLength(fileHelper.readUnsignedLongToLong(randomAccessFile));
+ result.setPackagesStringsLength(fileHelper.readUnsignedLongToLong(randomAccessFile));
+ result.setPackagesStringsCount(fileHelper.readUnsignedLongToLong(randomAccessFile));
+
+ return result;
+ }
+ finally {
+ if(null!=randomAccessFile) {
+ try {
+ randomAccessFile.close();
+ }
+ catch(IOException ioe) {
+ // ignore
+ }
+ }
+ }
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkrHeader.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkrHeader.java
new file mode 100644
index 00000000..3fa53bf4
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkrHeader.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg;
+
+import org.haikuos.pkg.heap.HeapCompression;
+
+public class HpkrHeader {
+
+ private long headerSize;
+ private int version;
+ private long totalSize;
+ private int minorVersion;
+
+ // heap
+ private HeapCompression heapCompression;
+ private long heapChunkSize;
+ private long heapSizeCompressed;
+ private long heapSizeUncompressed;
+
+ // repository info section
+ private long infoLength;
+
+ // package attributes section
+ private long packagesLength;
+ private long packagesStringsLength;
+ private long packagesStringsCount;
+
+ public long getHeaderSize() {
+ return headerSize;
+ }
+
+ public void setHeaderSize(long headerSize) {
+ this.headerSize = headerSize;
+ }
+
+ public int getVersion() {
+ return version;
+ }
+
+ public void setVersion(int version) {
+ this.version = version;
+ }
+
+ public long getTotalSize() {
+ return totalSize;
+ }
+
+ public void setTotalSize(long totalSize) {
+ this.totalSize = totalSize;
+ }
+
+ public int getMinorVersion() {
+ return minorVersion;
+ }
+
+ public void setMinorVersion(int minorVersion) {
+ this.minorVersion = minorVersion;
+ }
+
+ public HeapCompression getHeapCompression() {
+ return heapCompression;
+ }
+
+ public void setHeapCompression(HeapCompression heapCompression) {
+ this.heapCompression = heapCompression;
+ }
+
+ public long getHeapChunkSize() {
+ return heapChunkSize;
+ }
+
+ public void setHeapChunkSize(long heapChunkSize) {
+ this.heapChunkSize = heapChunkSize;
+ }
+
+ public long getHeapSizeCompressed() {
+ return heapSizeCompressed;
+ }
+
+ public void setHeapSizeCompressed(long heapSizeCompressed) {
+ this.heapSizeCompressed = heapSizeCompressed;
+ }
+
+ public long getHeapSizeUncompressed() {
+ return heapSizeUncompressed;
+ }
+
+ public void setHeapSizeUncompressed(long heapSizeUncompressed) {
+ this.heapSizeUncompressed = heapSizeUncompressed;
+ }
+
+ public long getInfoLength() {
+ return infoLength;
+ }
+
+ public void setInfoLength(long infoLength) {
+ this.infoLength = infoLength;
+ }
+
+ public long getPackagesLength() {
+ return packagesLength;
+ }
+
+ public void setPackagesLength(long packagesLength) {
+ this.packagesLength = packagesLength;
+ }
+
+ public long getPackagesStringsLength() {
+ return packagesStringsLength;
+ }
+
+ public void setPackagesStringsLength(long packagesStringsLength) {
+ this.packagesStringsLength = packagesStringsLength;
+ }
+
+ public long getPackagesStringsCount() {
+ return packagesStringsCount;
+ }
+
+ public void setPackagesStringsCount(long packagesStringsCount) {
+ this.packagesStringsCount = packagesStringsCount;
+ }
+
+}
+
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/PkgException.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/PkgException.java
new file mode 100644
index 00000000..aafa5071
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/PkgException.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg;
+
+public class PkgException extends Exception {
+
+ public PkgException(String message) {
+ super(message);
+ }
+
+ public PkgException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/PkgFactory.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/PkgFactory.java
new file mode 100644
index 00000000..6c8be8ae
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/PkgFactory.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import org.haikuos.pkg.model.*;
+
+import java.math.BigInteger;
+
+/**
+ *
This object is algorithm that is able to convert a top level package attribute into a modelled package object
+ * that can more easily represent the package; essentially converting the low-level attributes into a higher-level
+ * package model object.
This object will wrap an attribute iterator to be able to generate a series of {@link Pkg} objects that
+ * model a package in the HaikuOS package management system.
This method will return the next package from the attribute iterator supplied.
+ * @return The return value is the next package from the list of attributes.
+ * @throws PkgException when there is a problem obtaining the next package from the attributes.
+ * @throws HpkException when there is a problem obtaining the next attributes.
+ */
+
+ public Pkg next() throws PkgException, HpkException {
+ Attribute attribute = attributeIterator.next();
+
+ if(null!=attribute) {
+ return pkgFactory.createPackage(attributeIterator.getContext(), attribute);
+ }
+
+ return null;
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/StringTable.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/StringTable.java
new file mode 100644
index 00000000..d878fc6b
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/StringTable.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg;
+
+/**
+ *
The attribute-reading elements of the system need to be able to access a string table. This is interface of
+ * an object which is able to provide those strings.
Given the index supplied, this method should return the corresponding string. It will throw an instance
+ * of {@link HpkException} if there is any problems associated with achieving this.
+ */
+
+ public String getString(int index) throws HpkException;
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/heap/HeapCompression.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/heap/HeapCompression.java
new file mode 100644
index 00000000..4cd727f1
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/heap/HeapCompression.java
@@ -0,0 +1,11 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.heap;
+
+public enum HeapCompression {
+ NONE,
+ ZLIB
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/heap/HeapCoordinates.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/heap/HeapCoordinates.java
new file mode 100644
index 00000000..4a1a9921
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/heap/HeapCoordinates.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.heap;
+
+import com.google.common.base.Preconditions;
+
+/**
+ *
This object provides an offset and length into the heap and this provides a coordinate for a chunk of
+ * data in the heap. Note that the coordinates refer to the uncompressed data across all of the chunks of the heap.
+ *
+ */
+
+public class HeapCoordinates {
+
+ private long offset;
+ private long length;
+
+ public HeapCoordinates(long offset, long length) {
+ super();
+
+ Preconditions.checkState(offset >= 0 && offset < Integer.MAX_VALUE);
+ Preconditions.checkState(length >= 0 && length < Integer.MAX_VALUE);
+
+ this.offset = offset;
+ this.length = length;
+ }
+
+ public long getOffset() {
+ return offset;
+ }
+
+ public long getLength() {
+ return length;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ HeapCoordinates that = (HeapCoordinates) o;
+
+ if (length != that.length) return false;
+ if (offset != that.offset) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = (int) (offset ^ (offset >>> 32));
+ result = 31 * result + (int) (length ^ (length >>> 32));
+ return result;
+ }
+
+ @Override
+
+ public String toString() {
+ return String.format("{%d,%d}",offset,length);
+ }
+
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/heap/HeapReader.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/heap/HeapReader.java
new file mode 100644
index 00000000..721c0f86
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/heap/HeapReader.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.heap;
+
+/**
+ *
This is an interface for classes that are able to provide data from a block of memory referred to as "the heap".
+ * Concrete sub-classes are able to provide specific implementations that can read from different on-disk files to
+ * provide access to a heap.
+ *
+ */
+
+public interface HeapReader {
+
+ /**
+ *
This method reads from the heap (possibly across chunks) the data described in the coordinates attribute. It
+ * writes those bytes into the supplied buffer at the offset supplied.
+ */
+
+ public void readHeap(byte[] buffer, int bufferOffset, HeapCoordinates coordinates);
+
+ /**
+ *
This method reads a single byte of the heap at the given offset.
+ */
+
+ public int readHeap(long offset);
+
+}
\ No newline at end of file
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/heap/HpkHeapReader.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/heap/HpkHeapReader.java
new file mode 100644
index 00000000..db96b150
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/heap/HpkHeapReader.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.heap;
+
+import com.google.common.base.Preconditions;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import org.haikuos.pkg.FileHelper;
+import org.haikuos.pkg.HpkException;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+
+/**
+ *
An instance of this class is able to read the heap's chunks that are in HPK format. Note
+ * that this class will also take responsibility for caching the chunks so that a subsequent
+ * read from the same chunk will not require a re-fault from disk.
+ */
+
+public class HpkHeapReader implements Closeable, HeapReader {
+
+ private File file;
+
+ private HeapCompression compression;
+
+ private long heapOffset;
+
+ private long chunkSize;
+
+ private long compressedSize; // including the shorts for the chunks' compressed sizes
+
+ private long uncompressedSize; // excluding the shorts for the chunks' compressed sizes
+
+ private LoadingCache heapChunkUncompressedCache;
+
+ private int[] heapChunkCompressedLengths = null;
+
+ private RandomAccessFile randomAccessFile;
+
+ private FileHelper fileHelper = new FileHelper();
+
+ public HpkHeapReader(
+ final File file,
+ final HeapCompression compression,
+ final long heapOffset,
+ final long chunkSize,
+ final long compressedSize,
+ final long uncompressedSize) throws HpkException {
+
+ super();
+
+ Preconditions.checkNotNull(file);
+ Preconditions.checkNotNull(compression);
+ Preconditions.checkState(heapOffset > 0 &&heapOffset < Integer.MAX_VALUE);
+ Preconditions.checkState(chunkSize > 0 && chunkSize < Integer.MAX_VALUE);
+ Preconditions.checkState(compressedSize >= 0 && compressedSize < Integer.MAX_VALUE);
+ Preconditions.checkState(uncompressedSize >= 0 && compressedSize < Integer.MAX_VALUE);
+
+ this.file = file;
+ this.compression = compression;
+ this.heapOffset = heapOffset;
+ this.chunkSize = chunkSize;
+ this.compressedSize = compressedSize;
+ this.uncompressedSize = uncompressedSize;
+
+ try {
+ randomAccessFile = new RandomAccessFile(file,"r");
+
+ heapChunkCompressedLengths = new int[getHeapChunkCount()];
+ populateChunkCompressedLengths(heapChunkCompressedLengths);
+
+ heapChunkUncompressedCache = CacheBuilder
+ .newBuilder()
+ .maximumSize(3)
+ .build(new CacheLoader() {
+ @Override
+ public byte[] load(Integer key) throws Exception {
+ Preconditions.checkNotNull(key);
+
+ // TODO: best to avoid continuously allocating new byte buffers
+ byte[] result = new byte[getHeapChunkUncompressedLength(key)];
+ readHeapChunk(key,result);
+ return result;
+ }
+ });
+ }
+ catch(Exception e) {
+ close();
+ throw new HpkException("unable to configure the hpk heap reader",e);
+ }
+ catch(Throwable th) {
+ close();
+ throw new RuntimeException("unable to configure the hkp heap reader",th);
+ }
+
+ }
+
+ @Override
+ public void close() {
+ if(null!=randomAccessFile) {
+ try {
+ randomAccessFile.close();
+ }
+ catch(IOException ioe) {
+ // ignore
+ }
+ }
+ }
+
+ /**
+ *
This gives the quantity of chunks that are in the heap.
After the chunk data is a whole lot of unsigned shorts that define the compressed
+ * size of the chunks in the heap. This method will shift the input stream to the
+ * start of those shorts and read them in.
+ */
+
+ private void populateChunkCompressedLengths(int lengths[]) throws IOException, HpkException {
+ Preconditions.checkNotNull(lengths);
+
+ int count = getHeapChunkCount();
+ long totalCompressedLength = 0;
+ randomAccessFile.seek(heapOffset + compressedSize - (2 * (count-1)));
+
+ for(int i=0;i uncompressedSize) {
+ throw new HpkException(
+ String.format("the chunk at %d is of size %d, but the uncompressed length of the chunks is %d",
+ i,
+ lengths[i],
+ uncompressedSize));
+ }
+
+ totalCompressedLength += lengths[i];
+ }
+
+ // the last one will be missing will need to be derived
+ lengths[count-1] = (int) (compressedSize - ((2*(count-1)) + totalCompressedLength));
+
+ if(lengths[count-1] < 0 || lengths[count-1] > uncompressedSize) {
+ throw new HpkException(
+ String.format(
+ "the derivation of the last chunk size of %d is out of bounds",
+ lengths[count-1]));
+ }
+
+ totalCompressedLength += lengths[count-1];
+ }
+
+ private boolean isHeapChunkCompressed(int index) throws IOException, HpkException {
+ return getHeapChunkCompressedLength(index) < getHeapChunkUncompressedLength(index);
+ }
+
+ private long getHeapChunkAbsoluteFileOffset(int index) throws IOException, HpkException {
+ long result = heapOffset; // heap comes after the header.
+
+ for(int i=0;iThis will read from the current offset into the supplied buffer until the supplied buffer is completely
+ * filledup.
+ */
+
+ private void readFully(byte[] buffer) throws IOException, HpkException {
+ Preconditions.checkNotNull(buffer);
+ int total = 0;
+
+ while(total < buffer.length) {
+ int read = randomAccessFile.read(buffer,total,buffer.length - total);
+
+ if(-1==read) {
+ throw new HpkException("unexpected end of file when reading a chunk");
+ }
+
+ total += read;
+ }
+ }
+
+ /**
+ *
This will read a chunk of the heap into the supplied buffer. It is assumed that the buffer will be
+ * of the correct length for the uncompressed heap chunk size.
+ */
+
+ private void readHeapChunk(int index, byte[] buffer) throws IOException, HpkException {
+
+ randomAccessFile.seek(getHeapChunkAbsoluteFileOffset(index));
+ int chunkUncompressedLength = getHeapChunkUncompressedLength(index);
+
+ if(isHeapChunkCompressed(index) || HeapCompression.NONE == compression) {
+
+ switch(compression) {
+ case NONE:
+ throw new IllegalStateException();
+
+ case ZLIB:
+ {
+ byte[] deflatedBuffer = new byte[(int) getHeapChunkCompressedLength(index)];
+ readFully(deflatedBuffer);
+
+ Inflater inflater = new Inflater();
+ inflater.setInput(deflatedBuffer);
+
+ try {
+ int read;
+
+ if(chunkUncompressedLength != (read = inflater.inflate(buffer))) {
+
+ // the last chunk size uncompressed may be smaller than the chunk size,
+ // so don't throw an exception if this happens.
+
+ if(index < getHeapChunkCount()-1) {
+ String message = String.format("a compressed heap chunk inflated to %d bytes; was expecting %d",read,chunkUncompressedLength);
+
+ if(inflater.needsInput()) {
+ message += "; needs input";
+ }
+
+ if(inflater.needsDictionary()) {
+ message += "; needs dictionary";
+ }
+
+ throw new HpkException(message);
+ }
+ }
+
+ if(!inflater.finished()) {
+ throw new HpkException(String.format("incomplete inflation of input data while reading chunk %d",index));
+ }
+ }
+ catch(DataFormatException dfe) {
+ throw new HpkException("unable to inflate (decompress) heap chunk "+index,dfe);
+ }
+ }
+ break;
+
+ default:
+ throw new IllegalStateException("unsupported compression; "+compression);
+ }
+ }
+ else {
+ int read;
+
+ if(chunkUncompressedLength != (read = randomAccessFile.read(buffer,0,chunkUncompressedLength))) {
+ throw new HpkException(String.format("problem reading chunk %d of heap; only read %d of %d bytes",index,read,buffer.length));
+ }
+ }
+ }
+
+ @Override
+ public int readHeap(long offset) {
+ Preconditions.checkState(offset >= 0);
+ Preconditions.checkState(offset < uncompressedSize);
+
+ int chunkIndex = (int) (offset / chunkSize);
+ int chunkOffset = (int) (offset - (chunkIndex * chunkSize));
+ byte[] chunkData = heapChunkUncompressedCache.getUnchecked(chunkIndex);
+
+ return chunkData[chunkOffset] & 0xff;
+ }
+
+ @Override
+ public void readHeap(byte[] buffer, int bufferOffset, HeapCoordinates coordinates) {
+
+ Preconditions.checkNotNull(buffer);
+ Preconditions.checkState(bufferOffset >= 0);
+ Preconditions.checkState(bufferOffset < buffer.length);
+ Preconditions.checkState(coordinates.getOffset() >= 0);
+ Preconditions.checkState(coordinates.getOffset() < uncompressedSize);
+ Preconditions.checkState(coordinates.getOffset()+coordinates.getLength() < uncompressedSize);
+
+ // first figure out how much to read from this chunk
+
+ int chunkIndex = (int) (coordinates.getOffset() / chunkSize);
+ int chunkOffset = (int) (coordinates.getOffset() - (chunkIndex * chunkSize));
+ int chunkLength;
+ int chunkUncompressedLength = getHeapChunkUncompressedLength(chunkIndex);
+
+ if(chunkOffset + coordinates.getLength() > chunkUncompressedLength) {
+ chunkLength = (int) (chunkUncompressedLength - chunkOffset);
+ }
+ else {
+ chunkLength = (int) coordinates.getLength();
+ }
+
+ // now read it in.
+
+ byte[] chunkData = heapChunkUncompressedCache.getUnchecked(chunkIndex);
+
+ System.arraycopy(chunkData,chunkOffset,buffer,bufferOffset,chunkLength);
+
+ // if we need to get some more data from the next chunk then call again.
+ // TODO - recursive approach may not be too good when more data is involved; probably ok for hpkr though.
+
+ if(chunkLength < coordinates.getLength()) {
+ readHeap(
+ buffer,
+ bufferOffset + chunkLength,
+ new HeapCoordinates(
+ heapOffset + chunkLength,
+ coordinates.getLength() - chunkLength));
+ }
+
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/Attribute.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/Attribute.java
new file mode 100644
index 00000000..7e5aecac
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/Attribute.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.model;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import org.haikuos.pkg.AttributeContext;
+import org.haikuos.pkg.HpkException;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ *
This is the superclass of the different types (data types) of attributes.
These constants define the meaning of an {@link Attribute}. The numerical value is a value that comes up
+ * in the formatted file that maps to these constants. The string value is a name for the attribute and the type
+ * gives the type that is expected to be associated with an {@link Attribute} that has one of these IDs.
+ *
+ *
These constants were obtained from
+ * here and then the
+ * search/replace of B_DEFINE_HPKG_ATTRIBUTE\([ ]*(\d+),[ ]*([A-Z]+),[ \t]*("[a-z:\-.]+"),[ \t]*([A-Z_]+)\)
+ * / $4($1,$3,$2), was applied.
This attribute is an integral numeric value. Note that the format specifies either a signed or unsigned value,
+ * but this concrete subclass of @{link Attribute} serves for both the signed and unsigned cases.
+ */
+
+public class IntAttribute extends Attribute {
+
+ private BigInteger numericValue;
+
+ public IntAttribute(AttributeId attributeId, BigInteger numericValue) {
+ super(attributeId);
+ Preconditions.checkNotNull(numericValue);
+ this.numericValue = numericValue;
+ }
+
+ @Override
+ public BigInteger getValue(AttributeContext context) throws HpkException {
+ return numericValue;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ IntAttribute that = (IntAttribute) o;
+
+ if (!numericValue.equals(that.numericValue)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return numericValue.hashCode();
+ }
+
+ @Override
+ public AttributeType getAttributeType() {
+ return AttributeType.INT;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s : %s",super.toString(),numericValue.toString());
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/Pkg.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/Pkg.java
new file mode 100644
index 00000000..a5ec941b
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/Pkg.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.model;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+
+import java.util.Collections;
+import java.util.List;
+
+public class Pkg {
+
+ private String name;
+ private PkgVersion version;
+ private PkgArchitecture architecture;
+ private String vendor;
+ private List copyrights;
+ private List licenses;
+ private String summary;
+ private String description;
+ private PkgUrl homePageUrl;
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getSummary() {
+ return summary;
+ }
+
+ public void setSummary(String summary) {
+ this.summary = summary;
+ }
+
+ public String getVendor() {
+ return vendor;
+ }
+
+ public void setVendor(String vendor) {
+ this.vendor = vendor;
+ }
+
+ public PkgVersion getVersion() {
+ return version;
+ }
+
+ public void setVersion(PkgVersion version) {
+ this.version = version;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public PkgArchitecture getArchitecture() {
+ return architecture;
+ }
+
+ public void setArchitecture(PkgArchitecture architecture) {
+ this.architecture = architecture;
+ }
+
+ public List getCopyrights() {
+ if(null==copyrights) {
+ return Collections.emptyList();
+ }
+ return copyrights;
+ }
+
+ public void addCopyright(String copyright) {
+ Preconditions.checkNotNull(copyright);
+
+ if(null==copyrights) {
+ copyrights = Lists.newArrayList();
+ }
+
+ copyrights.add(copyright);
+ }
+
+ public List getLicenses() {
+ if(null==licenses) {
+ return Collections.emptyList();
+ }
+ return licenses;
+ }
+
+ public void addLicense(String license) {
+ Preconditions.checkNotNull(license);
+
+ if(null==licenses) {
+ licenses = Lists.newArrayList();
+ }
+
+ licenses.add(license);
+ }
+
+ public PkgUrl getHomePageUrl() {
+ return homePageUrl;
+ }
+
+ public void setHomePageUrl(PkgUrl homePageUrl) {
+ this.homePageUrl = homePageUrl;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append(null==name?"???":name);
+ stringBuilder.append(" : ");
+ stringBuilder.append(null==version?"???":version.toString());
+ stringBuilder.append(" : ");
+ stringBuilder.append(null==architecture?"???":getArchitecture().toString());
+ return stringBuilder.toString();
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/PkgArchitecture.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/PkgArchitecture.java
new file mode 100644
index 00000000..2bebb88f
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/PkgArchitecture.java
@@ -0,0 +1,13 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.model;
+
+public enum PkgArchitecture {
+ ANY,
+ X86,
+ X86_GCC2,
+ SOURCE
+};
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/PkgUrl.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/PkgUrl.java
new file mode 100644
index 00000000..5ad64bfd
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/PkgUrl.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.model;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+
+public class PkgUrl {
+
+ private String url;
+
+ private PkgUrlType urlType;
+
+ public PkgUrl(String url, PkgUrlType urlType) {
+ super();
+ Preconditions.checkNotNull(urlType);
+ Preconditions.checkNotNull(url);
+ Preconditions.checkState(!Strings.isNullOrEmpty(url));
+ this.url = url;
+ this.urlType = urlType;
+ }
+
+ public PkgUrlType getUrlType() {
+ return urlType;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s; %s",urlType.toString(),url);
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/PkgUrlType.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/PkgUrlType.java
new file mode 100644
index 00000000..9d1cee4f
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/PkgUrlType.java
@@ -0,0 +1,10 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.model;
+
+public enum PkgUrlType {
+ HOMEPAGE
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/PkgVersion.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/PkgVersion.java
new file mode 100644
index 00000000..a3731861
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/PkgVersion.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.model;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+
+public class PkgVersion {
+
+ private String major;
+ private String minor;
+ private String micro;
+ private String preRelease;
+ private Integer revision;
+
+ public PkgVersion(String major, String minor, String micro, String preRelease, Integer revision) {
+ Preconditions.checkState(!Strings.isNullOrEmpty(major));
+ this.major = major;
+ this.minor = minor;
+ this.micro = micro;
+ this.preRelease = preRelease;
+ this.revision = revision;
+ }
+
+ public String getMajor() {
+ return major;
+ }
+
+ public String getMinor() {
+ return minor;
+ }
+
+ public String getMicro() {
+ return micro;
+ }
+
+ public String getPreRelease() {
+ return preRelease;
+ }
+
+ public Integer getRevision() {
+ return revision;
+ }
+
+ private void appendDotValue(StringBuilder stringBuilder, String value) {
+ if(null!=value) {
+ if(0!=stringBuilder.length()) {
+ stringBuilder.append('.');
+ }
+
+ stringBuilder.append(value);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder();
+ appendDotValue(result,getMajor());
+ appendDotValue(result,getMinor());
+ appendDotValue(result,getMicro());
+ appendDotValue(result, getPreRelease());
+ appendDotValue(result, null == getRevision() ? null : getRevision().toString());
+ return result.toString();
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/RawAttribute.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/RawAttribute.java
new file mode 100644
index 00000000..82d31e16
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/RawAttribute.java
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.model;
+
+public abstract class RawAttribute extends Attribute {
+
+ public RawAttribute(AttributeId attributeId) {
+ super(attributeId);
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/RawHeapAttribute.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/RawHeapAttribute.java
new file mode 100644
index 00000000..804fca8a
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/RawHeapAttribute.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.model;
+
+import com.google.common.base.Preconditions;
+import org.haikuos.pkg.AttributeContext;
+import org.haikuos.pkg.HpkException;
+import org.haikuos.pkg.heap.HeapCoordinates;
+
+/**
+ *
This type of attribute refers to raw data. It uses coordinates into the heap to provide a source for the
+ * data.
+ */
+
+public class RawHeapAttribute extends RawAttribute {
+
+ private HeapCoordinates heapCoordinates;
+
+ public RawHeapAttribute(AttributeId attributeId, HeapCoordinates heapCoordinates) {
+ super(attributeId);
+ Preconditions.checkNotNull(heapCoordinates);
+ this.heapCoordinates = heapCoordinates;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ RawHeapAttribute that = (RawHeapAttribute) o;
+
+ if (!heapCoordinates.equals(that.heapCoordinates)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return heapCoordinates.hashCode();
+ }
+
+ @Override
+ public byte[] getValue(AttributeContext context) throws HpkException {
+ byte[] buffer = new byte[(int) heapCoordinates.getLength()];
+ context.getHeapReader().readHeap(buffer, 0, heapCoordinates);
+ return buffer;
+ }
+
+ @Override
+ public AttributeType getAttributeType() {
+ return AttributeType.RAW;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s : @%s",super.toString(),heapCoordinates.toString());
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/RawInlineAttribute.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/RawInlineAttribute.java
new file mode 100644
index 00000000..c66c8970
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/RawInlineAttribute.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.model;
+
+import com.google.common.base.Preconditions;
+import org.haikuos.pkg.AttributeContext;
+import org.haikuos.pkg.HpkException;
+
+import java.util.Arrays;
+
+
+public class RawInlineAttribute extends RawAttribute {
+
+ private byte[] rawValue;
+
+ public RawInlineAttribute(AttributeId attributeId, byte[] rawValue) {
+ super(attributeId);
+ Preconditions.checkNotNull(rawValue);
+ this.rawValue = rawValue;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ RawInlineAttribute that = (RawInlineAttribute) o;
+
+ if (!Arrays.equals(rawValue, that.rawValue)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(rawValue);
+ }
+
+ @Override
+ public byte[] getValue(AttributeContext context) throws HpkException {
+ return rawValue;
+ }
+
+ @Override
+ public AttributeType getAttributeType() {
+ return AttributeType.RAW;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s : %d b",super.toString(),rawValue.length);
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/StringAttribute.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/StringAttribute.java
new file mode 100644
index 00000000..6c16336e
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/StringAttribute.java
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.model;
+
+public abstract class StringAttribute extends Attribute {
+
+ public StringAttribute(AttributeId attributeId) {
+ super(attributeId);
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/StringInlineAttribute.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/StringInlineAttribute.java
new file mode 100644
index 00000000..6555ce82
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/StringInlineAttribute.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.model;
+
+import com.google.common.base.Preconditions;
+import org.haikuos.pkg.AttributeContext;
+import org.haikuos.pkg.HpkException;
+
+/**
+ *
This type of attribute is a string. The string is supplied in the stream of attributes so this attribute will
+ * carry the string.
+ */
+
+public class StringInlineAttribute extends StringAttribute {
+
+ private String stringValue;
+
+ public StringInlineAttribute(AttributeId attributeId, String stringValue) {
+ super(attributeId);
+ Preconditions.checkNotNull(stringValue);
+ this.stringValue = stringValue;
+ }
+
+ @Override
+ public String getValue(AttributeContext context) throws HpkException {
+ return stringValue;
+ }
+
+ @Override
+ public AttributeType getAttributeType() {
+ return AttributeType.STRING;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ StringInlineAttribute that = (StringInlineAttribute) o;
+
+ if (!stringValue.equals(that.stringValue)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return stringValue.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s : %s",super.toString(),stringValue.toString());
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/StringTableRefAttribute.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/StringTableRefAttribute.java
new file mode 100644
index 00000000..1fe017ae
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/model/StringTableRefAttribute.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.model;
+
+import com.google.common.base.Preconditions;
+import org.haikuos.pkg.AttributeContext;
+import org.haikuos.pkg.HpkException;
+
+/**
+ *
This type of attribute references a string from an instance of {@link org.haikuos.pkg.HpkStringTable}
+ * which is typically obtained from an instance of {@link org.haikuos.pkg.AttributeContext}.
+ */
+
+public class StringTableRefAttribute extends StringAttribute {
+
+ private int index;
+
+ public StringTableRefAttribute(AttributeId attributeId, int index) {
+ super(attributeId);
+ Preconditions.checkState(index >= 0);
+ this.index = index;
+ }
+
+ @Override
+ public String getValue(AttributeContext context) throws HpkException {
+ return context.getStringTable().getString(index);
+ }
+
+ @Override
+ public AttributeType getAttributeType() {
+ return AttributeType.STRING;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ StringTableRefAttribute that = (StringTableRefAttribute) o;
+
+ if (index != that.index) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return index;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s : @%d",super.toString(),index);
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/output/AttributeWriter.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/output/AttributeWriter.java
new file mode 100644
index 00000000..9cd28928
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/output/AttributeWriter.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.output;
+
+import com.google.common.base.Preconditions;
+import org.haikuos.pkg.AttributeContext;
+import org.haikuos.pkg.AttributeIterator;
+import org.haikuos.pkg.HpkException;
+import org.haikuos.pkg.model.Attribute;
+
+import java.io.FilterWriter;
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ *
This is a writer in the sense that it writes a human-readable dump of the attributes to a {@link Writer}. This
+ * enables debug or diagnostic output to be written to, for example, a file or standard output. It will write the
+ * attributes in an indented tree structure.
+ */
+
+public class AttributeWriter extends FilterWriter {
+
+ public AttributeWriter(Writer writer) {
+ super(writer);
+ }
+
+ private void write(int indent, AttributeContext context, Attribute attribute) throws IOException {
+ Preconditions.checkNotNull(context);
+ Preconditions.checkNotNull(attribute);
+ Preconditions.checkState(indent >= 0);
+
+ for(int i=0;iThis is a writer in the sense of being able to produce a human-readable output of a package.
+ */
+
+public class PkgWriter extends FilterWriter {
+
+ public PkgWriter(Writer writer) {
+ super(writer);
+ }
+
+ private void write(Pkg pkg) throws IOException {
+ Preconditions.checkNotNull(pkg);
+ write(pkg.toString());
+ }
+
+ public void write(PkgIterator pkgIterator) throws IOException, HpkException {
+ Preconditions.checkNotNull(pkgIterator);
+
+ try {
+ while(pkgIterator.hasNext()) {
+ write(pkgIterator.next());
+ write('\n');
+ }
+ }
+ catch(PkgException pe) {
+ throw new IOException("unable to write a package owing to an exception obtaining the package",pe);
+ }
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/package-info.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/package-info.java
new file mode 100644
index 00000000..5aa0b698
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/package-info.java
@@ -0,0 +1,11 @@
+/**
+ *
This package contains controller, model and helper objects for reading package files for the
+ * HaikuOS project. Pkg files come in two types. HPKR is a file
+ * format for providing a kind of catalogue of what is in a repository. HPKG format is a file that describes
+ * a particular package. At the time of writing, this library only supports HPKR although there is enough
+ * supporting material to easily provide a reader for HPKG.
+ *
+ *
Note that this library (currently) only supports (signed) 32bit addressing in the HPKR files.
+ */
+
+package org.haikuos.pkg;
\ No newline at end of file
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/tool/AttributeDumpTool.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/tool/AttributeDumpTool.java
new file mode 100644
index 00000000..810dc1de
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/tool/AttributeDumpTool.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.tool;
+
+import org.haikuos.pkg.HpkException;
+import org.haikuos.pkg.HpkrFileExtractor;
+import org.haikuos.pkg.output.AttributeWriter;
+import org.kohsuke.args4j.CmdLineException;
+import org.kohsuke.args4j.CmdLineParser;
+import org.kohsuke.args4j.Option;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+
+/**
+ *
Given an HPKR file, this small program will dump all of the attributes of the HPKR file. This is handy for
+ * diagnostic purposes.
+ */
+
+public class AttributeDumpTool implements Runnable {
+
+ protected static Logger logger = LoggerFactory.getLogger(AttributeDumpTool.class);
+
+ @Option(name = "-f", required = true, usage = "the HPKR file is required")
+ private File hpkrFile;
+
+ public static void main(String[] args) throws HpkException, IOException {
+ AttributeDumpTool main = new AttributeDumpTool();
+ CmdLineParser parser = new CmdLineParser(main);
+
+ try {
+ parser.parseArgument(args);
+ main.run();
+ }
+ catch(CmdLineException cle) {
+ throw new IllegalStateException("unable to parse arguments",cle);
+ }
+ }
+
+ public void run() {
+ CmdLineParser parser = new CmdLineParser(this);
+
+ HpkrFileExtractor hpkrFileExtractor = null;
+
+ try {
+ hpkrFileExtractor = new HpkrFileExtractor(hpkrFile);
+
+ OutputStreamWriter streamWriter = new OutputStreamWriter(System.out);
+ AttributeWriter attributeWriter = new AttributeWriter(streamWriter);
+ attributeWriter.write(hpkrFileExtractor.getPackageAttributesIterator());
+ attributeWriter.flush();
+ }
+ catch(Throwable th) {
+ logger.error("unable to dump attributes",th);
+ }
+ finally {
+ if(null!=hpkrFileExtractor) {
+ hpkrFileExtractor.close();
+ }
+ }
+
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/tool/PkgDumpTool.java b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/tool/PkgDumpTool.java
new file mode 100644
index 00000000..3d9d2e45
--- /dev/null
+++ b/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/tool/PkgDumpTool.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg.tool;
+
+import org.haikuos.pkg.HpkException;
+import org.haikuos.pkg.HpkrFileExtractor;
+import org.haikuos.pkg.PkgIterator;
+import org.haikuos.pkg.output.PkgWriter;
+import org.kohsuke.args4j.CmdLineException;
+import org.kohsuke.args4j.CmdLineParser;
+import org.kohsuke.args4j.Option;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+
+/**
+ *
This small tool will take an HPKR file and parse it first into attributes and then into packages. The packages
+ * are dumped out to the standard output. This is useful as means of debugging.
+ */
+
+public class PkgDumpTool {
+
+ protected static Logger logger = LoggerFactory.getLogger(AttributeDumpTool.class);
+
+ @Option(name = "-f", required = true, usage = "the HPKR file is required")
+ private File hpkrFile;
+
+ public static void main(String[] args) throws HpkException, IOException {
+ PkgDumpTool main = new PkgDumpTool();
+ CmdLineParser parser = new CmdLineParser(main);
+
+ try {
+ parser.parseArgument(args);
+ main.run();
+ }
+ catch(CmdLineException cle) {
+ throw new IllegalStateException("unable to parse arguments",cle);
+ }
+ }
+
+ public void run() {
+ CmdLineParser parser = new CmdLineParser(this);
+
+ HpkrFileExtractor hpkrFileExtractor = null;
+
+ try {
+ hpkrFileExtractor = new HpkrFileExtractor(hpkrFile);
+
+ OutputStreamWriter streamWriter = new OutputStreamWriter(System.out);
+ PkgWriter pkgWriter = new PkgWriter(streamWriter);
+ pkgWriter.write(new PkgIterator(hpkrFileExtractor.getPackageAttributesIterator()));
+ pkgWriter.flush();
+ }
+ catch(Throwable th) {
+ logger.error("unable to dump packages",th);
+ }
+ finally {
+ if(null!=hpkrFileExtractor) {
+ hpkrFileExtractor.close();
+ }
+ }
+
+ }
+
+
+
+}
diff --git a/haikudepotserver-packagefile/src/test/java/org/haikuos/pkg/AbstractHpkrTest.java b/haikudepotserver-packagefile/src/test/java/org/haikuos/pkg/AbstractHpkrTest.java
new file mode 100644
index 00000000..0ea37f35
--- /dev/null
+++ b/haikudepotserver-packagefile/src/test/java/org/haikuos/pkg/AbstractHpkrTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg;
+
+import com.google.common.io.ByteStreams;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public abstract class AbstractHpkrTest {
+
+ /**
+ *
This will copy the supplied test classpath resource into a temporary file to work with during the test. It
+ * is the responsibility of the caller to clean up the temporary file afterwards.
+ */
+
+ protected File prepareTestFile() throws IOException {
+ InputStream inputStream = getClass().getResourceAsStream("/repo.hpkr");
+
+ if(null==inputStream) {
+ throw new IllegalStateException("unable to find the test hpkr resource");
+ }
+
+ File temporaryFile = File.createTempFile("repo-test-","hpkr");
+ FileOutputStream fileOutputStream = null;
+
+ try {
+ fileOutputStream = new FileOutputStream(temporaryFile);
+ ByteStreams.copy(inputStream, fileOutputStream);
+ }
+ catch(IOException ioe) {
+ temporaryFile.delete();
+ throw new IOException("unable to copy the test hpkr resource to a temporary file; "+temporaryFile.getAbsolutePath());
+ }
+ finally {
+ if(null!=fileOutputStream) {
+ try {
+ fileOutputStream.close();
+ }
+ catch(IOException ioe) {
+ // ignore
+ }
+ }
+ }
+
+ return temporaryFile;
+ }
+
+}
diff --git a/haikudepotserver-packagefile/src/test/java/org/haikuos/pkg/HpkrFileExtractorAttributeTest.java b/haikudepotserver-packagefile/src/test/java/org/haikuos/pkg/HpkrFileExtractorAttributeTest.java
new file mode 100644
index 00000000..ff2a89cd
--- /dev/null
+++ b/haikudepotserver-packagefile/src/test/java/org/haikuos/pkg/HpkrFileExtractorAttributeTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.pkg;
+
+import org.haikuos.pkg.model.Attribute;
+import org.haikuos.pkg.model.AttributeId;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.OutputStreamWriter;
+import java.math.BigInteger;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+/**
+ *
This is a simplistic test that is just going to stream out some attributes from a known HPKR file and will
+ * then look for certain packages and make sure that an artificially created attribute set matches. It's basically
+ * a smoke test.
See {@link PkgApi} for details on the methods this API affords.
+ */
+
+@Component
+public class PkgApiImpl implements PkgApi {
+
+ @Resource
+ ServerRuntime serverRuntime;
+
+ @Resource
+ SearchPkgsService searchPkgsService;
+
+ @Override
+ public SearchPkgsResult searchPkgs(SearchPkgsRequest request) {
+ Preconditions.checkNotNull(request);
+ Preconditions.checkState(!Strings.isNullOrEmpty(request.architectureCode));
+
+ final ObjectContext context = serverRuntime.getContext();
+
+ SearchPkgsSpecification specification = new SearchPkgsSpecification();
+
+ String exp = request.expression;
+
+ if(null!=exp) {
+ exp = Strings.emptyToNull(exp.trim().toLowerCase());
+ }
+
+ specification.setExpression(exp);
+
+ if(null!=request.expressionType) {
+ specification.setExpressionType(
+ SearchPkgsSpecification.ExpressionType.valueOf(request.expressionType.name()));
+ }
+
+ final Optional architectureOptional = Architecture.getByCode(context,request.architectureCode);
+
+ if(!architectureOptional.isPresent()) {
+ throw new IllegalStateException("the architecture specified is not able to be found; "+request.architectureCode);
+ }
+
+ specification.setArchitectures(Sets.newHashSet(
+ architectureOptional.get()
+ ,
+ Architecture.getByCode(context,Architecture.CODE_ANY).get()
+// ,
+// Architecture.getByCode(context,Architecture.CODE_SOURCE).get()
+ ));
+
+ specification.setLimit(request.limit+1); // get +1 to see if there are any more.
+ specification.setOffset(request.offset);
+
+ SearchPkgsResult result = new SearchPkgsResult();
+ List searchedPkgs = searchPkgsService.search(context,specification);
+
+ // if there are more than we asked for then there must be more available.
+
+ result.hasMore = new Boolean(searchedPkgs.size() > request.limit);
+
+ if(result.hasMore) {
+ searchedPkgs = searchedPkgs.subList(0,request.limit);
+ }
+
+ result.pkgs = Lists.newArrayList(Iterables.transform(
+ searchedPkgs,
+ new Function() {
+ @Override
+ public SearchPkgsResult.Pkg apply(org.haikuos.haikudepotserver.model.Pkg input) {
+ SearchPkgsResult.Pkg resultPkg = new SearchPkgsResult.Pkg();
+ resultPkg.name = input.getName();
+
+ Optional pkgVersionOptional = PkgVersion.getLatestForPkg(
+ context,
+ input,
+ Lists.newArrayList(
+ architectureOptional.get(),
+ Architecture.getByCode(context, Architecture.CODE_ANY).get(),
+ Architecture.getByCode(context, Architecture.CODE_SOURCE).get()));
+
+ if(pkgVersionOptional.isPresent()) {
+ SearchPkgsResult.Version resultVersion = new SearchPkgsResult.Version();
+ resultVersion.major = pkgVersionOptional.get().getMajor();
+ resultVersion.minor = pkgVersionOptional.get().getMinor();
+ resultVersion.micro = pkgVersionOptional.get().getMicro();
+ resultVersion.preRelease = pkgVersionOptional.get().getPreRelease();
+ resultVersion.revision = pkgVersionOptional.get().getRevision();
+ resultPkg.version = resultVersion;
+ }
+
+ return resultPkg;
+ }
+ }
+ ));
+
+
+ return result;
+ }
+
+ /**
+ *
Given the persistence model object, this method will construct the DTO to be sent back over the wire.
+ */
+
+ private GetPkgResult.Version createVersion(PkgVersion pkgVersion) {
+ GetPkgResult.Version version = new GetPkgResult.Version();
+
+ version.major = pkgVersion.getMajor();
+ version.minor = pkgVersion.getMinor();
+ version.micro = pkgVersion.getMicro();
+ version.revision = pkgVersion.getRevision();
+ version.preRelease = pkgVersion.getPreRelease();
+
+ version.architectureCode = pkgVersion.getArchitecture().getCode();
+ version.copyrights = Lists.transform(
+ pkgVersion.getPkgVersionCopyrights(),
+ new Function() {
+ @Override
+ public String apply(org.haikuos.haikudepotserver.model.PkgVersionCopyright input) {
+ return input.getBody();
+ }
+ });
+
+ version.licenses = Lists.transform(
+ pkgVersion.getPkgVersionLicenses(),
+ new Function() {
+ @Override
+ public String apply(org.haikuos.haikudepotserver.model.PkgVersionLicense input) {
+ return input.getBody();
+ }
+ });
+
+ // TODO - languages
+ if(!pkgVersion.getPkgVersionLocalizations().isEmpty()) {
+ PkgVersionLocalization pkgVersionLocalization = pkgVersion.getPkgVersionLocalizations().get(0);
+ version.description = pkgVersionLocalization.getDescription();
+ version.summary = pkgVersionLocalization.getSummary();
+ }
+
+ version.urls = Lists.transform(
+ pkgVersion.getPkgVersionUrls(),
+ new Function() {
+ @Override
+ public GetPkgResult.Url apply(org.haikuos.haikudepotserver.model.PkgVersionUrl input) {
+ GetPkgResult.Url url = new GetPkgResult.Url();
+ url.url = input.getUrl();
+ url.urlTypeCode = input.getPkgUrlType().getCode();
+ return url;
+ }
+ });
+
+ return version;
+ }
+
+ @Override
+ public GetPkgResult getPkg(GetPkgRequest request) throws ObjectNotFoundException {
+
+ Preconditions.checkNotNull(request);
+ Preconditions.checkState(!Strings.isNullOrEmpty(request.name));
+ Preconditions.checkState(!Strings.isNullOrEmpty(request.architectureCode));
+ Preconditions.checkNotNull(request.versionType);
+
+ final ObjectContext context = serverRuntime.getContext();
+
+ Optional architectureOptional = Architecture.getByCode(context, request.architectureCode);
+
+ if(!architectureOptional.isPresent()) {
+ throw new IllegalStateException("the specified architecture was not able to be found; "+request.architectureCode);
+ }
+
+ GetPkgResult result = new GetPkgResult();
+ Optional pkgOptional = Pkg.getByName(context, request.name);
+
+ if(!pkgOptional.isPresent()) {
+ throw new ObjectNotFoundException(Pkg.class.getSimpleName(), request.name);
+ }
+
+ result.name = pkgOptional.get().getName();
+
+ switch(request.versionType) {
+ case LATEST:
+ Optional pkgVersionOptional = PkgVersion.getLatestForPkg(
+ context,
+ pkgOptional.get(),
+ Lists.newArrayList(
+ architectureOptional.get(),
+ Architecture.getByCode(context, Architecture.CODE_ANY).get(),
+ Architecture.getByCode(context, Architecture.CODE_SOURCE).get()));
+
+ if(!pkgVersionOptional.isPresent()) {
+ throw new ObjectNotFoundException(PkgVersion.class.getSimpleName(), request.name);
+ }
+
+ result.versions = Collections.singletonList(createVersion(pkgVersionOptional.get()));
+ break;
+
+ default:
+ throw new IllegalStateException("unhandled version type in request");
+ }
+
+ return result;
+ }
+
+}
diff --git a/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/UserApiImpl.java b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/UserApiImpl.java
new file mode 100644
index 00000000..cb2e613f
--- /dev/null
+++ b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/UserApiImpl.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.hash.Hashing;
+import com.google.common.util.concurrent.Uninterruptibles;
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.configuration.server.ServerRuntime;
+import org.haikuos.haikudepotserver.api1.model.user.*;
+import org.haikuos.haikudepotserver.api1.support.CaptchaBadResponseException;
+import org.haikuos.haikudepotserver.api1.support.ObjectNotFoundException;
+import org.haikuos.haikudepotserver.captcha.CaptchaService;
+import org.haikuos.haikudepotserver.model.User;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+@Component
+public class UserApiImpl implements UserApi {
+
+ protected static Logger logger = LoggerFactory.getLogger(UserApiImpl.class);
+
+ @Resource
+ ServerRuntime serverRuntime;
+
+ @Resource
+ CaptchaService captchaService;
+
+ @Override
+ public CreateUserResult createUser(CreateUserRequest createUserRequest) {
+
+ Preconditions.checkNotNull(createUserRequest);
+ Preconditions.checkNotNull(createUserRequest.captchaToken);
+ Preconditions.checkNotNull(createUserRequest.captchaResponse);
+
+ // check the supplied catcha matches the token.
+
+ if(!captchaService.verify(createUserRequest.captchaToken, createUserRequest.captchaResponse)) {
+ throw new CaptchaBadResponseException();
+ }
+
+ // we need to check the nickname even before we create the user because we have to
+ // check for uniqueness of the nickname across all of the users.
+
+ if(Strings.isNullOrEmpty(createUserRequest.nickname)) {
+ throw new org.haikuos.haikudepotserver.api1.support.ValidationException(
+ new org.haikuos.haikudepotserver.api1.support.ValidationFailure(
+ User.NICKNAME_PROPERTY,"required")
+ );
+ }
+
+ final ObjectContext context = serverRuntime.getContext();
+
+ //need to check that the nickname is not already in use.
+
+ if(User.getByNickname(context,createUserRequest.nickname).isPresent()) {
+ throw new org.haikuos.haikudepotserver.api1.support.ValidationException(
+ new org.haikuos.haikudepotserver.api1.support.ValidationFailure(
+ User.NICKNAME_PROPERTY,"notunique")
+ );
+ }
+
+ User user = context.newObject(User.class);
+ user.setNickname(createUserRequest.nickname);
+ user.setPasswordSalt(); // random
+ user.setPasswordHash(Hashing.sha256().hashString(user.getPasswordSalt() + createUserRequest.passwordClear).toString());
+ context.commitChanges();
+
+ logger.info("data create user; {}",user.getNickname());
+
+ return new CreateUserResult();
+ }
+
+ @Override
+ public GetUserResult getUser(GetUserRequest getUserRequest) throws ObjectNotFoundException {
+ Preconditions.checkNotNull(getUserRequest);
+ Preconditions.checkState(!Strings.isNullOrEmpty(getUserRequest.nickname));
+
+ final ObjectContext context = serverRuntime.getContext();
+
+ Optional user = User.getByNickname(context, getUserRequest.nickname);
+
+ if(!user.isPresent()) {
+ throw new ObjectNotFoundException(User.class.getSimpleName(), User.NICKNAME_PROPERTY);
+ }
+
+ GetUserResult result = new GetUserResult();
+ result.nickname = user.get().getNickname();
+ return result;
+ }
+
+ // TODO; some sort of brute-force checking here; too many authentication requests in a short period; go into lock-down?
+
+ @Override
+ public AuthenticateUserResult authenticateUser(AuthenticateUserRequest authenticateUserRequest) {
+ Preconditions.checkNotNull(authenticateUserRequest);
+ AuthenticateUserResult authenticateUserResult = new AuthenticateUserResult();
+ authenticateUserResult.authenticated = false;
+
+ if(null!=authenticateUserRequest.nickname) {
+ authenticateUserRequest.nickname = authenticateUserRequest.nickname.trim();
+ }
+
+ if(null!=authenticateUserRequest.passwordClear) {
+ authenticateUserRequest.passwordClear = authenticateUserRequest.passwordClear.trim();
+ }
+
+ if(
+ !Strings.isNullOrEmpty(authenticateUserRequest.nickname)
+ && !Strings.isNullOrEmpty(authenticateUserRequest.passwordClear)) {
+
+ final ObjectContext context = serverRuntime.getContext();
+
+ Optional userOptional = User.getByNickname(context, authenticateUserRequest.nickname);
+
+ if(userOptional.isPresent()) {
+ String saltAndPasswordClear = userOptional.get().getPasswordSalt() + authenticateUserRequest.passwordClear;
+ String inboundHash = Hashing.sha256().hashString(saltAndPasswordClear).toString();
+ authenticateUserResult.authenticated = inboundHash.equals(userOptional.get().getPasswordHash());
+ }
+ }
+
+ if(!authenticateUserResult.authenticated) {
+ Uninterruptibles.sleepUninterruptibly(5,TimeUnit.SECONDS);
+ }
+
+ return authenticateUserResult;
+ }
+
+
+}
diff --git a/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/support/ErrorResolverImpl.java b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/support/ErrorResolverImpl.java
new file mode 100644
index 00000000..5ac301ea
--- /dev/null
+++ b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/support/ErrorResolverImpl.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.support;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.googlecode.jsonrpc4j.DefaultErrorResolver;
+import com.googlecode.jsonrpc4j.ErrorResolver;
+import org.apache.cayenne.validation.BeanValidationFailure;
+import org.apache.cayenne.validation.SimpleValidationFailure;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Map;
+
+/**
+ *
This class is able to take exceptions and throwables and turn them into valid JSON-RPC errors that can be
+ * returned to the client with specific codes and with specific data.
+ */
+
+public class ErrorResolverImpl implements ErrorResolver {
+
+ @Override
+ public JsonError resolveError(Throwable t, Method method, List arguments) {
+
+ // special output for a bad captcha
+
+ if(CaptchaBadResponseException.class.isAssignableFrom(t.getClass())) {
+ return new JsonError(
+ Constants.ERROR_CODE_CAPTCHABADRESPONSE,
+ "captchabadresponse",
+ null);
+ }
+
+ // special output for the object not found exceptions
+
+ if(ObjectNotFoundException.class.isAssignableFrom(t.getClass())) {
+ ObjectNotFoundException objectNotFoundException = (ObjectNotFoundException) t;
+
+ return new JsonError(
+ Constants.ERROR_CODE_OBJECTNOTFOUND,
+ "objectnotfound",
+ ImmutableMap.of(
+ "entityname", objectNotFoundException.getEntityName(),
+ "identifier", objectNotFoundException.getIdentifier()
+ )
+ );
+ }
+
+ // special output for the validation exceptions
+
+ if(ValidationException.class.isAssignableFrom(t.getClass())) {
+ ValidationException validationException = (ValidationException) t;
+
+ return new JsonError(
+ Constants.ERROR_CODE_VALIDATION,
+ "validationerror",
+ ImmutableMap.of(
+ "validationfailures",
+ Lists.transform(
+ validationException.getValidationFailures(),
+ new Function() {
+ @Override
+ public Map apply(org.haikuos.haikudepotserver.api1.support.ValidationFailure input) {
+ return ImmutableMap.of(
+ "property",input.getProperty(),
+ "message",input.getMessage()
+ );
+ }
+ }
+ )
+ )
+ );
+ }
+
+ // special output for cayenne validation exceptions
+
+ if(org.apache.cayenne.validation.ValidationException.class.isAssignableFrom(t.getClass())) {
+ org.apache.cayenne.validation.ValidationException validationException = (org.apache.cayenne.validation.ValidationException) t;
+
+ return new JsonError(
+ Constants.ERROR_CODE_VALIDATION,
+ "validationerror",
+ ImmutableMap.of(
+ "validationfailures",
+ Lists.transform(
+ validationException.getValidationResult().getFailures(),
+ new Function() {
+ @Override
+ public Map apply(org.apache.cayenne.validation.ValidationFailure input) {
+ if(BeanValidationFailure.class.isAssignableFrom(input.getClass())) {
+ BeanValidationFailure beanValidationFailure = (BeanValidationFailure) input;
+ return ImmutableMap.of(
+ "property", beanValidationFailure.getProperty(),
+ "message", beanValidationFailure.toString());
+ }
+
+ if(SimpleValidationFailure.class.isAssignableFrom(input.getClass())) {
+ SimpleValidationFailure simpleValidationFailure = (SimpleValidationFailure) input;
+ return ImmutableMap.of(
+ "property", "",
+ "message", simpleValidationFailure.getDescription());
+ }
+
+ throw new IllegalStateException("unable to establish data portion of validation exception owing to unknown cayenne validation failure; "+input.getClass().getSimpleName());
+ }
+ }
+ )
+ )
+ );
+ }
+
+ return DefaultErrorResolver.INSTANCE.resolveError(t,method,arguments);
+ }
+}
diff --git a/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/support/ObjectMapperFactory.java b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/support/ObjectMapperFactory.java
new file mode 100644
index 00000000..5874647a
--- /dev/null
+++ b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/support/ObjectMapperFactory.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.support;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.FactoryBean;
+
+/**
+ *
The JSON handling class needs some special configuration in order for it to, for example, ignore
+ * missing fields on objects. This factory is able to create those
+ * {@link ObjectMapper} objects correctly configured ready for use.
+ */
+
+public class ObjectMapperFactory implements FactoryBean {
+
+ @Override
+ public ObjectMapper getObject() throws Exception {
+ ObjectMapper objectMapper = new ObjectMapper();
+ objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
+ return objectMapper;
+ }
+
+ @Override
+ public Class> getObjectType() {
+ return ObjectMapper.class;
+ }
+
+ @Override
+ public boolean isSingleton() {
+ return false;
+ }
+}
diff --git a/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/captcha/CaptchaService.java b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/captcha/CaptchaService.java
new file mode 100644
index 00000000..900cc34b
--- /dev/null
+++ b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/captcha/CaptchaService.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.captcha;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import org.haikuos.haikudepotserver.captcha.model.Captcha;
+import org.haikuos.haikudepotserver.captcha.model.CaptchaAlgorithm;
+import org.haikuos.haikudepotserver.captcha.model.CaptchaRepository;
+
+/**
+ *
This service is able to provide interfacing to the captcha system including verification and generation of
+ * captchas.
This method will generate a captcha, returning all of the details of the captcha. Note that the captcha is
+ * stored so that it can be validated within some time-frame.
This will check that the captcha identified by the supplied token has an expected response that matches the
+ * response that is supplied to this method. It will return true if this is the case. Note that this method will
+ * also delete the captcha such that it is not able to be verified again or re-used.
Implementation of {@link org.haikuos.haikudepotserver.captcha.model.CaptchaAlgorithm} that presents the user with
+ * a fairly trivial maths problem to solve.
+ */
+public class SimpleMathProblemCaptchaAlgorithm implements CaptchaAlgorithm {
+
+ private static final int PADDING_TEXT = 4;
+
+ private static final int WIDTH = 128;
+ private static final int HEIGHT = 32;
+
+ private Font font = new Font(Font.SANS_SERIF, Font.PLAIN, 14);
+
+ private BufferedImage bufferedImage;
+ private Graphics bufferedImageGraphics;
+ private FontMetrics fontMetrics;
+ private Random random = new Random(System.currentTimeMillis());
+
+ public SimpleMathProblemCaptchaAlgorithm() {
+ super();
+
+ bufferedImage = new BufferedImage(
+ WIDTH,
+ HEIGHT,
+ BufferedImage.TYPE_INT_RGB);
+
+ bufferedImageGraphics = bufferedImage.getGraphics();
+ Font font = new Font(Font.SANS_SERIF, Font.PLAIN, 14);
+ bufferedImageGraphics.setFont(font);
+ fontMetrics = bufferedImageGraphics.getFontMetrics(font);
+ }
+
+ @Override
+ public synchronized Captcha generate() {
+
+ int addend1 = Math.abs(random.nextInt() % 25);
+ int addend2 = Math.abs(random.nextInt() % 25);
+ String problem = String.format("%d + %d",addend1,addend2);
+ String response = Integer.toString(addend1 + addend2);
+ byte[] pngImageData;
+
+ synchronized(this) {
+
+ // reset the image.
+
+ bufferedImageGraphics.setColor(Color.DARK_GRAY);
+ bufferedImageGraphics.fillRect(0,0,bufferedImage.getWidth(),bufferedImage.getHeight());
+
+ // now render a small image using java 2d with this text in it.
+
+ bufferedImageGraphics.setColor(Color.WHITE);
+ bufferedImageGraphics.drawString(
+ problem,
+ (bufferedImage.getWidth() - fontMetrics.stringWidth(problem)) / 2,
+ (bufferedImage.getHeight() + fontMetrics.getAscent()) / 2);
+
+ // now generate a PNG of it.
+
+ try {
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ ImageIO.write(bufferedImage, "png", byteArrayOutputStream);
+ pngImageData = byteArrayOutputStream.toByteArray();
+ }
+ catch(IOException ioe) {
+ throw new IllegalStateException("unable to write png data in memory",ioe);
+ }
+
+ }
+
+ Captcha captcha = new Captcha();
+ captcha.setToken(UUID.randomUUID().toString());
+ captcha.setResponse(response);
+ captcha.setPngImageData(pngImageData);
+ return captcha;
+
+ }
+}
diff --git a/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/captcha/model/Captcha.java b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/captcha/model/Captcha.java
new file mode 100644
index 00000000..88541751
--- /dev/null
+++ b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/captcha/model/Captcha.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.captcha.model;
+
+/**
+ *
This object models a captcha; which is an image that contains instructions for a human to convert into text to
+ * confirm that they are more likely to be a human.
+ */
+
+public class Captcha {
+
+ /**
+ *
This string uniquely identifies the captcha. This is generally the mechanism by which the captcha is
+ * referenced in other API and services.
+ */
+
+ private String token;
+
+ /**
+ *
This is the expected response that a human operator should be expected to provide.
+ */
+
+ private String response;
+
+ /**
+ *
This is a PNG image that contains the instruction for the user to convert into some text in their
+ * response.
+ */
+
+ private byte[] pngImageData;
+
+ public String getToken() {
+ return token;
+ }
+
+ public void setToken(String token) {
+ this.token = token;
+ }
+
+ public String getResponse() {
+ return response;
+ }
+
+ public void setResponse(String response) {
+ this.response = response;
+ }
+
+ public byte[] getPngImageData() {
+ return pngImageData;
+ }
+
+ public void setPngImageData(byte[] pngImageData) {
+ this.pngImageData = pngImageData;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder();
+ result.append(null!=token ? token.toString() : "???");
+ result.append(" --> ");
+ result.append(getResponse());
+ result.append(" (");
+ result.append(Integer.toString(pngImageData.length));
+ result.append("b)");
+ return result.toString();
+ }
+
+}
diff --git a/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/captcha/model/CaptchaAlgorithm.java b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/captcha/model/CaptchaAlgorithm.java
new file mode 100644
index 00000000..d514240a
--- /dev/null
+++ b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/captcha/model/CaptchaAlgorithm.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.captcha.model;
+
+/**
+ *
This interface defines an object that is able to generate a new captcha.
+ */
+
+public interface CaptchaAlgorithm {
+
+ Captcha generate();
+
+}
diff --git a/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/captcha/model/CaptchaRepository.java b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/captcha/model/CaptchaRepository.java
new file mode 100644
index 00000000..6e8bb2ad
--- /dev/null
+++ b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/captcha/model/CaptchaRepository.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.captcha.model;
+
+/**
+ *
This interface defines a class that is able to store expected responses against captcha tokens.
This method will obtain the response for the captcha identified by the UUID.
+ */
+
+ public String get(String token);
+
+ /**
+ *
This method will store a new token in the database.
+ */
+
+ public void store(String token, String response);
+
+}
diff --git a/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/controller/EntryPointController.java b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/controller/EntryPointController.java
new file mode 100644
index 00000000..148730ec
--- /dev/null
+++ b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/controller/EntryPointController.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.controller;
+
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.configuration.server.ServerRuntime;
+import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.query.SelectQuery;
+import org.haikuos.haikudepotserver.model.Architecture;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.ModelMap;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ *
This controller renders the default HTML entry point into the application. As this is generally a
+ * single page application, this controller will just render that single page.
+ */
+
+@Controller
+@RequestMapping("/")
+public class EntryPointController {
+
+ @RequestMapping(method = RequestMethod.GET)
+ public String entryPoint() {
+ return "entryPoint";
+ }
+}
diff --git a/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/controller/ImportRepositoryDataController.java b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/controller/ImportRepositoryDataController.java
new file mode 100644
index 00000000..d93e326d
--- /dev/null
+++ b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/controller/ImportRepositoryDataController.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.controller;
+
+import com.google.common.base.Strings;
+import com.google.common.net.HttpHeaders;
+import com.google.common.net.MediaType;
+import org.haikuos.haikudepotserver.services.ImportRepositoryDataService;
+import org.haikuos.haikudepotserver.services.model.ImportRepositoryDataJob;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ *
This is the HTTP endpoint from which external systems are able to trigger a repository to be scanned for
+ * new packages by fetching the HPKR file and processing it. The actual logistics in this controller do not use
+ * typical Spring MVC error handling and so on; this is because fine control is required and this seems to be
+ * an easy way to achieve that; basically done manually.
+ */
+
+@Controller
+@RequestMapping("/importrepositorydata")
+public class ImportRepositoryDataController {
+
+ protected static Logger logger = LoggerFactory.getLogger(ImportRepositoryDataController.class);
+
+ public final static String KEY_CODE = "code";
+
+ @Resource
+ ImportRepositoryDataService importRepositoryDataService;
+
+ @RequestMapping(method = RequestMethod.GET)
+ public void fetch(
+ HttpServletResponse response,
+ @RequestParam(value = KEY_CODE, required = false) String repositoryCode) {
+
+ try {
+ if(Strings.isNullOrEmpty(repositoryCode)) {
+ logger.warn("attempt to import repository data service with no repository code supplied");
+ response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+ response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.PLAIN_TEXT_UTF_8.toString());
+ response.getWriter().print(String.format("expected '%s' to have been a query argument to this resource\n",KEY_CODE));
+ }
+ else {
+ importRepositoryDataService.submit(new ImportRepositoryDataJob(repositoryCode));
+ response.setStatus(HttpServletResponse.SC_OK);
+ response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.PLAIN_TEXT_UTF_8.toString());
+ response.getWriter().print(String.format("accepted import repository job for repository %s\n",repositoryCode));
+ }
+ }
+ catch(Throwable th) {
+ logger.error("failed to accept import repository job",th);
+
+ response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.PLAIN_TEXT_UTF_8.toString());
+
+ try {
+ response.getWriter().print(String.format("failed to accept import repository job for repository %s\n",repositoryCode));
+ }
+ catch(IOException ioe) {
+ /* ignore */
+ }
+ }
+ }
+
+}
diff --git a/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/controller/WebResourceGroupController.java b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/controller/WebResourceGroupController.java
new file mode 100644
index 00000000..9cc7f9f7
--- /dev/null
+++ b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/controller/WebResourceGroupController.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.controller;
+
+import com.google.common.base.Strings;
+import com.google.common.io.ByteStreams;
+import com.google.common.net.HttpHeaders;
+import com.google.common.net.MediaType;
+import org.haikuos.haikudepotserver.support.web.model.WebResourceGroup;
+import org.haikuos.haikudepotserver.support.Closeables;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.core.io.Resource;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ *
Resources such as java-script files and a CSS files can be defined as part of the spring context and then
+ * this controller is able to take those beans and then render them into a single HTTP download. This avoids the
+ * page from having to download dozens of small javascript files; it can just request them as one HTTP request.
+ */
+
+ public void setPasswordSalt() {
+ setPasswordSalt(Hashing.sha256().hashString(UUID.randomUUID().toString()).toString());
+ }
+
+}
diff --git a/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_Architecture.java b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_Architecture.java
new file mode 100644
index 00000000..295655b0
--- /dev/null
+++ b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_Architecture.java
@@ -0,0 +1,21 @@
+package org.haikuos.haikudepotserver.model.auto;
+
+import org.haikuos.haikudepotserver.model.support.AbstractDataObject;
+
+/**
+ * Class _Architecture was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _Architecture extends AbstractDataObject {
+
+ public static final String CODE_PROPERTY = "code";
+
+ public static final String ID_PK_COLUMN = "id";
+
+ public String getCode() {
+ return (String)readProperty(CODE_PROPERTY);
+ }
+
+}
diff --git a/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_HaikuDepot.java b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_HaikuDepot.java
new file mode 100644
index 00000000..ab91c690
--- /dev/null
+++ b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_HaikuDepot.java
@@ -0,0 +1,12 @@
+package org.haikuos.haikudepotserver.model.auto;
+
+
+
+/**
+ * This class was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public class _HaikuDepot {
+}
\ No newline at end of file
diff --git a/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_Pkg.java b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_Pkg.java
new file mode 100644
index 00000000..8fcabeac
--- /dev/null
+++ b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_Pkg.java
@@ -0,0 +1,61 @@
+package org.haikuos.haikudepotserver.model.auto;
+
+import java.util.Date;
+
+import org.haikuos.haikudepotserver.model.Publisher;
+import org.haikuos.haikudepotserver.model.support.AbstractDataObject;
+
+/**
+ * Class _Pkg was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _Pkg extends AbstractDataObject {
+
+ public static final String ACTIVE_PROPERTY = "active";
+ public static final String CREATE_TIMESTAMP_PROPERTY = "createTimestamp";
+ public static final String MODIFY_TIMESTAMP_PROPERTY = "modifyTimestamp";
+ public static final String NAME_PROPERTY = "name";
+ public static final String PUBLISHER_PROPERTY = "publisher";
+
+ public static final String ID_PK_COLUMN = "id";
+
+ public void setActive(Boolean active) {
+ writeProperty(ACTIVE_PROPERTY, active);
+ }
+ public Boolean getActive() {
+ return (Boolean)readProperty(ACTIVE_PROPERTY);
+ }
+
+ public void setCreateTimestamp(Date createTimestamp) {
+ writeProperty(CREATE_TIMESTAMP_PROPERTY, createTimestamp);
+ }
+ public Date getCreateTimestamp() {
+ return (Date)readProperty(CREATE_TIMESTAMP_PROPERTY);
+ }
+
+ public void setModifyTimestamp(Date modifyTimestamp) {
+ writeProperty(MODIFY_TIMESTAMP_PROPERTY, modifyTimestamp);
+ }
+ public Date getModifyTimestamp() {
+ return (Date)readProperty(MODIFY_TIMESTAMP_PROPERTY);
+ }
+
+ public void setName(String name) {
+ writeProperty(NAME_PROPERTY, name);
+ }
+ public String getName() {
+ return (String)readProperty(NAME_PROPERTY);
+ }
+
+ public void setPublisher(Publisher publisher) {
+ setToOneTarget(PUBLISHER_PROPERTY, publisher, true);
+ }
+
+ public Publisher getPublisher() {
+ return (Publisher)readProperty(PUBLISHER_PROPERTY);
+ }
+
+
+}
diff --git a/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_PkgUrlType.java b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_PkgUrlType.java
new file mode 100644
index 00000000..0f8be9ff
--- /dev/null
+++ b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_PkgUrlType.java
@@ -0,0 +1,21 @@
+package org.haikuos.haikudepotserver.model.auto;
+
+import org.haikuos.haikudepotserver.model.support.AbstractDataObject;
+
+/**
+ * Class _PkgUrlType was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _PkgUrlType extends AbstractDataObject {
+
+ public static final String CODE_PROPERTY = "code";
+
+ public static final String ID_PK_COLUMN = "id";
+
+ public String getCode() {
+ return (String)readProperty(CODE_PROPERTY);
+ }
+
+}
diff --git a/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_PkgVersion.java b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_PkgVersion.java
new file mode 100644
index 00000000..56af8b2e
--- /dev/null
+++ b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_PkgVersion.java
@@ -0,0 +1,172 @@
+package org.haikuos.haikudepotserver.model.auto;
+
+import java.util.Date;
+import java.util.List;
+
+import org.haikuos.haikudepotserver.model.Architecture;
+import org.haikuos.haikudepotserver.model.Pkg;
+import org.haikuos.haikudepotserver.model.PkgVersionCopyright;
+import org.haikuos.haikudepotserver.model.PkgVersionLicense;
+import org.haikuos.haikudepotserver.model.PkgVersionLocalization;
+import org.haikuos.haikudepotserver.model.PkgVersionUrl;
+import org.haikuos.haikudepotserver.model.Repository;
+import org.haikuos.haikudepotserver.model.support.AbstractDataObject;
+
+/**
+ * Class _PkgVersion was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _PkgVersion extends AbstractDataObject {
+
+ public static final String ACTIVE_PROPERTY = "active";
+ public static final String CREATE_TIMESTAMP_PROPERTY = "createTimestamp";
+ public static final String MAJOR_PROPERTY = "major";
+ public static final String MICRO_PROPERTY = "micro";
+ public static final String MINOR_PROPERTY = "minor";
+ public static final String MODIFY_TIMESTAMP_PROPERTY = "modifyTimestamp";
+ public static final String PRE_RELEASE_PROPERTY = "preRelease";
+ public static final String REVISION_PROPERTY = "revision";
+ public static final String ARCHITECTURE_PROPERTY = "architecture";
+ public static final String PKG_PROPERTY = "pkg";
+ public static final String PKG_VERSION_COPYRIGHTS_PROPERTY = "pkgVersionCopyrights";
+ public static final String PKG_VERSION_LICENSES_PROPERTY = "pkgVersionLicenses";
+ public static final String PKG_VERSION_LOCALIZATIONS_PROPERTY = "pkgVersionLocalizations";
+ public static final String PKG_VERSION_URLS_PROPERTY = "pkgVersionUrls";
+ public static final String REPOSITORY_PROPERTY = "repository";
+
+ public static final String ID_PK_COLUMN = "id";
+
+ public void setActive(Boolean active) {
+ writeProperty(ACTIVE_PROPERTY, active);
+ }
+ public Boolean getActive() {
+ return (Boolean)readProperty(ACTIVE_PROPERTY);
+ }
+
+ public void setCreateTimestamp(Date createTimestamp) {
+ writeProperty(CREATE_TIMESTAMP_PROPERTY, createTimestamp);
+ }
+ public Date getCreateTimestamp() {
+ return (Date)readProperty(CREATE_TIMESTAMP_PROPERTY);
+ }
+
+ public void setMajor(String major) {
+ writeProperty(MAJOR_PROPERTY, major);
+ }
+ public String getMajor() {
+ return (String)readProperty(MAJOR_PROPERTY);
+ }
+
+ public void setMicro(String micro) {
+ writeProperty(MICRO_PROPERTY, micro);
+ }
+ public String getMicro() {
+ return (String)readProperty(MICRO_PROPERTY);
+ }
+
+ public void setMinor(String minor) {
+ writeProperty(MINOR_PROPERTY, minor);
+ }
+ public String getMinor() {
+ return (String)readProperty(MINOR_PROPERTY);
+ }
+
+ public void setModifyTimestamp(Date modifyTimestamp) {
+ writeProperty(MODIFY_TIMESTAMP_PROPERTY, modifyTimestamp);
+ }
+ public Date getModifyTimestamp() {
+ return (Date)readProperty(MODIFY_TIMESTAMP_PROPERTY);
+ }
+
+ public void setPreRelease(String preRelease) {
+ writeProperty(PRE_RELEASE_PROPERTY, preRelease);
+ }
+ public String getPreRelease() {
+ return (String)readProperty(PRE_RELEASE_PROPERTY);
+ }
+
+ public void setRevision(Integer revision) {
+ writeProperty(REVISION_PROPERTY, revision);
+ }
+ public Integer getRevision() {
+ return (Integer)readProperty(REVISION_PROPERTY);
+ }
+
+ public void setArchitecture(Architecture architecture) {
+ setToOneTarget(ARCHITECTURE_PROPERTY, architecture, true);
+ }
+
+ public Architecture getArchitecture() {
+ return (Architecture)readProperty(ARCHITECTURE_PROPERTY);
+ }
+
+
+ public void setPkg(Pkg pkg) {
+ setToOneTarget(PKG_PROPERTY, pkg, true);
+ }
+
+ public Pkg getPkg() {
+ return (Pkg)readProperty(PKG_PROPERTY);
+ }
+
+
+ public void addToPkgVersionCopyrights(PkgVersionCopyright obj) {
+ addToManyTarget(PKG_VERSION_COPYRIGHTS_PROPERTY, obj, true);
+ }
+ public void removeFromPkgVersionCopyrights(PkgVersionCopyright obj) {
+ removeToManyTarget(PKG_VERSION_COPYRIGHTS_PROPERTY, obj, true);
+ }
+ @SuppressWarnings("unchecked")
+ public List getPkgVersionCopyrights() {
+ return (List)readProperty(PKG_VERSION_COPYRIGHTS_PROPERTY);
+ }
+
+
+ public void addToPkgVersionLicenses(PkgVersionLicense obj) {
+ addToManyTarget(PKG_VERSION_LICENSES_PROPERTY, obj, true);
+ }
+ public void removeFromPkgVersionLicenses(PkgVersionLicense obj) {
+ removeToManyTarget(PKG_VERSION_LICENSES_PROPERTY, obj, true);
+ }
+ @SuppressWarnings("unchecked")
+ public List getPkgVersionLicenses() {
+ return (List)readProperty(PKG_VERSION_LICENSES_PROPERTY);
+ }
+
+
+ public void addToPkgVersionLocalizations(PkgVersionLocalization obj) {
+ addToManyTarget(PKG_VERSION_LOCALIZATIONS_PROPERTY, obj, true);
+ }
+ public void removeFromPkgVersionLocalizations(PkgVersionLocalization obj) {
+ removeToManyTarget(PKG_VERSION_LOCALIZATIONS_PROPERTY, obj, true);
+ }
+ @SuppressWarnings("unchecked")
+ public List getPkgVersionLocalizations() {
+ return (List)readProperty(PKG_VERSION_LOCALIZATIONS_PROPERTY);
+ }
+
+
+ public void addToPkgVersionUrls(PkgVersionUrl obj) {
+ addToManyTarget(PKG_VERSION_URLS_PROPERTY, obj, true);
+ }
+ public void removeFromPkgVersionUrls(PkgVersionUrl obj) {
+ removeToManyTarget(PKG_VERSION_URLS_PROPERTY, obj, true);
+ }
+ @SuppressWarnings("unchecked")
+ public List getPkgVersionUrls() {
+ return (List)readProperty(PKG_VERSION_URLS_PROPERTY);
+ }
+
+
+ public void setRepository(Repository repository) {
+ setToOneTarget(REPOSITORY_PROPERTY, repository, true);
+ }
+
+ public Repository getRepository() {
+ return (Repository)readProperty(REPOSITORY_PROPERTY);
+ }
+
+
+}
diff --git a/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_PkgVersionCopyright.java b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_PkgVersionCopyright.java
new file mode 100644
index 00000000..b91cfd10
--- /dev/null
+++ b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_PkgVersionCopyright.java
@@ -0,0 +1,35 @@
+package org.haikuos.haikudepotserver.model.auto;
+
+import org.haikuos.haikudepotserver.model.PkgVersion;
+import org.haikuos.haikudepotserver.model.support.AbstractDataObject;
+
+/**
+ * Class _PkgVersionCopyright was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _PkgVersionCopyright extends AbstractDataObject {
+
+ public static final String BODY_PROPERTY = "body";
+ public static final String PKG_VERSION_PROPERTY = "pkgVersion";
+
+ public static final String ID_PK_COLUMN = "id";
+
+ public void setBody(String body) {
+ writeProperty(BODY_PROPERTY, body);
+ }
+ public String getBody() {
+ return (String)readProperty(BODY_PROPERTY);
+ }
+
+ public void setPkgVersion(PkgVersion pkgVersion) {
+ setToOneTarget(PKG_VERSION_PROPERTY, pkgVersion, true);
+ }
+
+ public PkgVersion getPkgVersion() {
+ return (PkgVersion)readProperty(PKG_VERSION_PROPERTY);
+ }
+
+
+}
diff --git a/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_PkgVersionLicense.java b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_PkgVersionLicense.java
new file mode 100644
index 00000000..94283eeb
--- /dev/null
+++ b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_PkgVersionLicense.java
@@ -0,0 +1,35 @@
+package org.haikuos.haikudepotserver.model.auto;
+
+import org.haikuos.haikudepotserver.model.PkgVersion;
+import org.haikuos.haikudepotserver.model.support.AbstractDataObject;
+
+/**
+ * Class _PkgVersionLicense was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _PkgVersionLicense extends AbstractDataObject {
+
+ public static final String BODY_PROPERTY = "body";
+ public static final String PKG_VERSION_PROPERTY = "pkgVersion";
+
+ public static final String ID_PK_COLUMN = "id";
+
+ public void setBody(String body) {
+ writeProperty(BODY_PROPERTY, body);
+ }
+ public String getBody() {
+ return (String)readProperty(BODY_PROPERTY);
+ }
+
+ public void setPkgVersion(PkgVersion pkgVersion) {
+ setToOneTarget(PKG_VERSION_PROPERTY, pkgVersion, true);
+ }
+
+ public PkgVersion getPkgVersion() {
+ return (PkgVersion)readProperty(PKG_VERSION_PROPERTY);
+ }
+
+
+}
diff --git a/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_PkgVersionLocalization.java b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_PkgVersionLocalization.java
new file mode 100644
index 00000000..80d63dc7
--- /dev/null
+++ b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_PkgVersionLocalization.java
@@ -0,0 +1,43 @@
+package org.haikuos.haikudepotserver.model.auto;
+
+import org.haikuos.haikudepotserver.model.PkgVersion;
+import org.haikuos.haikudepotserver.model.support.AbstractDataObject;
+
+/**
+ * Class _PkgVersionLocalization was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _PkgVersionLocalization extends AbstractDataObject {
+
+ public static final String DESCRIPTION_PROPERTY = "description";
+ public static final String SUMMARY_PROPERTY = "summary";
+ public static final String PKG_VERSION_PROPERTY = "pkgVersion";
+
+ public static final String ID_PK_COLUMN = "id";
+
+ public void setDescription(String description) {
+ writeProperty(DESCRIPTION_PROPERTY, description);
+ }
+ public String getDescription() {
+ return (String)readProperty(DESCRIPTION_PROPERTY);
+ }
+
+ public void setSummary(String summary) {
+ writeProperty(SUMMARY_PROPERTY, summary);
+ }
+ public String getSummary() {
+ return (String)readProperty(SUMMARY_PROPERTY);
+ }
+
+ public void setPkgVersion(PkgVersion pkgVersion) {
+ setToOneTarget(PKG_VERSION_PROPERTY, pkgVersion, true);
+ }
+
+ public PkgVersion getPkgVersion() {
+ return (PkgVersion)readProperty(PKG_VERSION_PROPERTY);
+ }
+
+
+}
diff --git a/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_PkgVersionUrl.java b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_PkgVersionUrl.java
new file mode 100644
index 00000000..6be8b3ab
--- /dev/null
+++ b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_PkgVersionUrl.java
@@ -0,0 +1,46 @@
+package org.haikuos.haikudepotserver.model.auto;
+
+import org.haikuos.haikudepotserver.model.PkgUrlType;
+import org.haikuos.haikudepotserver.model.PkgVersion;
+import org.haikuos.haikudepotserver.model.support.AbstractDataObject;
+
+/**
+ * Class _PkgVersionUrl was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _PkgVersionUrl extends AbstractDataObject {
+
+ public static final String URL_PROPERTY = "url";
+ public static final String PKG_URL_TYPE_PROPERTY = "pkgUrlType";
+ public static final String PKG_VERSION_PROPERTY = "pkgVersion";
+
+ public static final String ID_PK_COLUMN = "id";
+
+ public void setUrl(String url) {
+ writeProperty(URL_PROPERTY, url);
+ }
+ public String getUrl() {
+ return (String)readProperty(URL_PROPERTY);
+ }
+
+ public void setPkgUrlType(PkgUrlType pkgUrlType) {
+ setToOneTarget(PKG_URL_TYPE_PROPERTY, pkgUrlType, true);
+ }
+
+ public PkgUrlType getPkgUrlType() {
+ return (PkgUrlType)readProperty(PKG_URL_TYPE_PROPERTY);
+ }
+
+
+ public void setPkgVersion(PkgVersion pkgVersion) {
+ setToOneTarget(PKG_VERSION_PROPERTY, pkgVersion, true);
+ }
+
+ public PkgVersion getPkgVersion() {
+ return (PkgVersion)readProperty(PKG_VERSION_PROPERTY);
+ }
+
+
+}
diff --git a/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_Publisher.java b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_Publisher.java
new file mode 100644
index 00000000..c1dff0d1
--- /dev/null
+++ b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_Publisher.java
@@ -0,0 +1,89 @@
+package org.haikuos.haikudepotserver.model.auto;
+
+import java.util.Date;
+import java.util.List;
+
+import org.haikuos.haikudepotserver.model.Pkg;
+import org.haikuos.haikudepotserver.model.support.AbstractDataObject;
+
+/**
+ * Class _Publisher was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _Publisher extends AbstractDataObject {
+
+ public static final String ACTIVE_PROPERTY = "active";
+ public static final String CODE_PROPERTY = "code";
+ public static final String CREATE_TIMESTAMP_PROPERTY = "createTimestamp";
+ public static final String EMAIL_PROPERTY = "email";
+ public static final String MODIFY_TIMESTAMP_PROPERTY = "modifyTimestamp";
+ public static final String NAME_PROPERTY = "name";
+ public static final String SITE_URL_PROPERTY = "siteUrl";
+ public static final String PKGS_PROPERTY = "pkgs";
+
+ public static final String ID_PK_COLUMN = "id";
+
+ public void setActive(Boolean active) {
+ writeProperty(ACTIVE_PROPERTY, active);
+ }
+ public Boolean getActive() {
+ return (Boolean)readProperty(ACTIVE_PROPERTY);
+ }
+
+ public void setCode(String code) {
+ writeProperty(CODE_PROPERTY, code);
+ }
+ public String getCode() {
+ return (String)readProperty(CODE_PROPERTY);
+ }
+
+ public void setCreateTimestamp(Date createTimestamp) {
+ writeProperty(CREATE_TIMESTAMP_PROPERTY, createTimestamp);
+ }
+ public Date getCreateTimestamp() {
+ return (Date)readProperty(CREATE_TIMESTAMP_PROPERTY);
+ }
+
+ public void setEmail(String email) {
+ writeProperty(EMAIL_PROPERTY, email);
+ }
+ public String getEmail() {
+ return (String)readProperty(EMAIL_PROPERTY);
+ }
+
+ public void setModifyTimestamp(Date modifyTimestamp) {
+ writeProperty(MODIFY_TIMESTAMP_PROPERTY, modifyTimestamp);
+ }
+ public Date getModifyTimestamp() {
+ return (Date)readProperty(MODIFY_TIMESTAMP_PROPERTY);
+ }
+
+ public void setName(String name) {
+ writeProperty(NAME_PROPERTY, name);
+ }
+ public String getName() {
+ return (String)readProperty(NAME_PROPERTY);
+ }
+
+ public void setSiteUrl(String siteUrl) {
+ writeProperty(SITE_URL_PROPERTY, siteUrl);
+ }
+ public String getSiteUrl() {
+ return (String)readProperty(SITE_URL_PROPERTY);
+ }
+
+ public void addToPkgs(Pkg obj) {
+ addToManyTarget(PKGS_PROPERTY, obj, true);
+ }
+ public void removeFromPkgs(Pkg obj) {
+ removeToManyTarget(PKGS_PROPERTY, obj, true);
+ }
+ @SuppressWarnings("unchecked")
+ public List getPkgs() {
+ return (List)readProperty(PKGS_PROPERTY);
+ }
+
+
+}
diff --git a/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_Repository.java b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_Repository.java
new file mode 100644
index 00000000..a66f23ea
--- /dev/null
+++ b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_Repository.java
@@ -0,0 +1,69 @@
+package org.haikuos.haikudepotserver.model.auto;
+
+import java.util.Date;
+
+import org.haikuos.haikudepotserver.model.Architecture;
+import org.haikuos.haikudepotserver.model.support.AbstractDataObject;
+
+/**
+ * Class _Repository was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _Repository extends AbstractDataObject {
+
+ public static final String ACTIVE_PROPERTY = "active";
+ public static final String CODE_PROPERTY = "code";
+ public static final String CREATE_TIMESTAMP_PROPERTY = "createTimestamp";
+ public static final String MODIFY_TIMESTAMP_PROPERTY = "modifyTimestamp";
+ public static final String URL_PROPERTY = "url";
+ public static final String ARCHITECTURE_PROPERTY = "architecture";
+
+ public static final String ID_PK_COLUMN = "id";
+
+ public void setActive(Boolean active) {
+ writeProperty(ACTIVE_PROPERTY, active);
+ }
+ public Boolean getActive() {
+ return (Boolean)readProperty(ACTIVE_PROPERTY);
+ }
+
+ public void setCode(String code) {
+ writeProperty(CODE_PROPERTY, code);
+ }
+ public String getCode() {
+ return (String)readProperty(CODE_PROPERTY);
+ }
+
+ public void setCreateTimestamp(Date createTimestamp) {
+ writeProperty(CREATE_TIMESTAMP_PROPERTY, createTimestamp);
+ }
+ public Date getCreateTimestamp() {
+ return (Date)readProperty(CREATE_TIMESTAMP_PROPERTY);
+ }
+
+ public void setModifyTimestamp(Date modifyTimestamp) {
+ writeProperty(MODIFY_TIMESTAMP_PROPERTY, modifyTimestamp);
+ }
+ public Date getModifyTimestamp() {
+ return (Date)readProperty(MODIFY_TIMESTAMP_PROPERTY);
+ }
+
+ public void setUrl(String url) {
+ writeProperty(URL_PROPERTY, url);
+ }
+ public String getUrl() {
+ return (String)readProperty(URL_PROPERTY);
+ }
+
+ public void setArchitecture(Architecture architecture) {
+ setToOneTarget(ARCHITECTURE_PROPERTY, architecture, true);
+ }
+
+ public Architecture getArchitecture() {
+ return (Architecture)readProperty(ARCHITECTURE_PROPERTY);
+ }
+
+
+}
diff --git a/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_User.java b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_User.java
new file mode 100644
index 00000000..d1e13148
--- /dev/null
+++ b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/auto/_User.java
@@ -0,0 +1,64 @@
+package org.haikuos.haikudepotserver.model.auto;
+
+import org.haikuos.haikudepotserver.model.support.AbstractDataObject;
+
+/**
+ * Class _User was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _User extends AbstractDataObject {
+
+ public static final String ACTIVE_PROPERTY = "active";
+ public static final String CAN_MANAGE_USERS_PROPERTY = "canManageUsers";
+ public static final String IS_ROOT_PROPERTY = "isRoot";
+ public static final String NICKNAME_PROPERTY = "nickname";
+ public static final String PASSWORD_HASH_PROPERTY = "passwordHash";
+ public static final String PASSWORD_SALT_PROPERTY = "passwordSalt";
+
+ public static final String ID_PK_COLUMN = "id";
+
+ public void setActive(Boolean active) {
+ writeProperty(ACTIVE_PROPERTY, active);
+ }
+ public Boolean getActive() {
+ return (Boolean)readProperty(ACTIVE_PROPERTY);
+ }
+
+ public void setCanManageUsers(Boolean canManageUsers) {
+ writeProperty(CAN_MANAGE_USERS_PROPERTY, canManageUsers);
+ }
+ public Boolean getCanManageUsers() {
+ return (Boolean)readProperty(CAN_MANAGE_USERS_PROPERTY);
+ }
+
+ public void setIsRoot(Boolean isRoot) {
+ writeProperty(IS_ROOT_PROPERTY, isRoot);
+ }
+ public Boolean getIsRoot() {
+ return (Boolean)readProperty(IS_ROOT_PROPERTY);
+ }
+
+ public void setNickname(String nickname) {
+ writeProperty(NICKNAME_PROPERTY, nickname);
+ }
+ public String getNickname() {
+ return (String)readProperty(NICKNAME_PROPERTY);
+ }
+
+ public void setPasswordHash(String passwordHash) {
+ writeProperty(PASSWORD_HASH_PROPERTY, passwordHash);
+ }
+ public String getPasswordHash() {
+ return (String)readProperty(PASSWORD_HASH_PROPERTY);
+ }
+
+ public void setPasswordSalt(String passwordSalt) {
+ writeProperty(PASSWORD_SALT_PROPERTY, passwordSalt);
+ }
+ public String getPasswordSalt() {
+ return (String)readProperty(PASSWORD_SALT_PROPERTY);
+ }
+
+}
diff --git a/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/package-info.java b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/package-info.java
new file mode 100644
index 00000000..92d08d6d
--- /dev/null
+++ b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/package-info.java
@@ -0,0 +1,8 @@
+/**
+ *
These model objects are initially created by the Cayenne modeller application using the 'gap pattern'. The
+ * objects in this package are generally able to be edited to augment them with validation and so on. The "auto"
+ * sub-package contains automatically re-generated files that should not be altered because the modeller may be used
+ * to re-generate those base classes.
+ */
+
+package org.haikuos.haikudepotserver.model;
\ No newline at end of file
diff --git a/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/support/AbstractDataObject.java b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/support/AbstractDataObject.java
new file mode 100644
index 00000000..66f50761
--- /dev/null
+++ b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/support/AbstractDataObject.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.model.support;
+
+import org.apache.cayenne.CayenneDataObject;
+import org.apache.cayenne.validation.SimpleValidationFailure;
+import org.apache.cayenne.validation.ValidationResult;
+
+import java.util.regex.Pattern;
+
+/**
+ *
This is the superclass of the Cayanne Data Objects in this project. This contains some common handling for
+ * all data objects.
+ */
+
+public abstract class AbstractDataObject extends CayenneDataObject {
+
+ public final static Pattern CODE_PATTERN = Pattern.compile("^[a-f0-9]{2,16}$");
+
+ @Override
+ protected void validateForSave(ValidationResult validationResult) {
+ super.validateForSave(validationResult);
+
+ // If we implement the Coded interface then we can validate the code on this
+ // object to check it is valid.
+
+ if(Coded.class.isAssignableFrom(this.getClass())) {
+
+ Coded coded = (Coded) this;
+
+ if(null != coded.getCode()) {
+ if(!CODE_PATTERN.matcher(coded.getCode()).matches()) {
+ validationResult.addFailure(new SimpleValidationFailure(this,"code.malformed"));
+ }
+ }
+ }
+
+ }
+
+}
diff --git a/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/support/Coded.java b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/support/Coded.java
new file mode 100644
index 00000000..2fe157f9
--- /dev/null
+++ b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/support/Coded.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.model.support;
+
+/**
+ *
This interface defines a method to get a code. This is used mostly on reference data such as
+ * {@link org.haikuos.haikudepotserver.model.PkgUrlType} for example where the code provides a
+ * machine-reference (not human readable) identification mechanism for an instance of an object.
+ */
+
+public interface Coded {
+
+ String getCode();
+
+}
diff --git a/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/support/CreateAndModifyTimestamped.java b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/support/CreateAndModifyTimestamped.java
new file mode 100644
index 00000000..d2c2cd86
--- /dev/null
+++ b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/model/support/CreateAndModifyTimestamped.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.model.support;
+
+import java.util.Date;
+
+/**
+ *
This is an interface for objects that are capable of storing and providing a create and modify timestamp. A
+ * listener is then able to operate on such objects observing changes and thereby updating the modify timestamp on
+ * the instance of the object in question. This avoids the need to manually maintain these modify timestamps.
+ */
+
+public interface CreateAndModifyTimestamped {
+
+ public void setCreateTimestamp(Date createTimestamp);
+ public Date getCreateTimestamp();
+ public void setModifyTimestamp(Date modifyTimestamp);
+ public Date getModifyTimestamp();
+
+}
diff --git a/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/services/ImportPackageService.java b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/services/ImportPackageService.java
new file mode 100644
index 00000000..4353b6b8
--- /dev/null
+++ b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/services/ImportPackageService.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.services;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.ObjectId;
+import org.apache.cayenne.configuration.server.ServerRuntime;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.query.SelectQuery;
+import org.haikuos.haikudepotserver.model.*;
+import org.haikuos.pkg.model.Pkg;
+import org.haikuos.pkg.model.PkgVersion;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ *
This class will import a package into the system's database; merging the data, or disabling entries as
+ * necessary.
This method will import the pkg described. The repository is also provided as a Cayenne object id in order
+ * to provide reference to the repository from which this pkg was obtained. Note that this method will execute
+ * as one 'transaction' (in the Cayenne sense).
+ */
+
+ public void run(
+ ObjectId repositoryObjectId,
+ Pkg pkg) {
+
+ Preconditions.checkNotNull(pkg);
+ Preconditions.checkNotNull(repositoryObjectId);
+
+ ObjectContext objectContext = serverRuntime.getContext();
+ Repository repository = Repository.get(objectContext, repositoryObjectId);
+
+ // first, check to see if the package is there or not.
+
+ Optional persistedPkgOptional = org.haikuos.haikudepotserver.model.Pkg.getByName(objectContext, pkg.getName());
+ org.haikuos.haikudepotserver.model.Pkg persistedPkg;
+ org.haikuos.haikudepotserver.model.PkgVersion persistedPkgVersion = null;
+
+ if(!persistedPkgOptional.isPresent()) {
+ persistedPkg = objectContext.newObject(org.haikuos.haikudepotserver.model.Pkg.class);
+ persistedPkg.setName(pkg.getName());
+ persistedPkg.setActive(Boolean.TRUE);
+ logger.info("the package {} did not exist; will create",pkg.getName());
+ }
+ else {
+ persistedPkg = persistedPkgOptional.get();
+
+ // if we know that the package exists then we should look for the version.
+
+ SelectQuery selectQuery = new SelectQuery(
+ org.haikuos.haikudepotserver.model.PkgVersion.class,
+ ExpressionFactory.matchExp(
+ org.haikuos.haikudepotserver.model.PkgVersion.PKG_PROPERTY,
+ persistedPkg)
+ .andExp(toExpression(pkg.getVersion())));
+
+ persistedPkgVersion = Iterables.getOnlyElement(
+ (List) objectContext.performQuery(selectQuery),
+ null);
+ }
+
+ if(null==persistedPkgVersion) {
+
+ persistedPkgVersion = objectContext.newObject(org.haikuos.haikudepotserver.model.PkgVersion.class);
+ persistedPkgVersion.setActive(Boolean.TRUE);
+ persistedPkgVersion.setMajor(pkg.getVersion().getMajor());
+ persistedPkgVersion.setMinor(pkg.getVersion().getMinor());
+ persistedPkgVersion.setMicro(pkg.getVersion().getMicro());
+ persistedPkgVersion.setPreRelease(pkg.getVersion().getPreRelease());
+ persistedPkgVersion.setRevision(pkg.getVersion().getRevision());
+ persistedPkgVersion.setRepository(repository);
+ persistedPkgVersion.setArchitecture(Architecture.getByCode(
+ objectContext,
+ pkg.getArchitecture().name().toLowerCase()).get());
+ persistedPkgVersion.setPkg(persistedPkg);
+
+ // now add the copyrights
+ for(String copyright : pkg.getCopyrights()) {
+ PkgVersionCopyright persistedPkgVersionCopyright = objectContext.newObject(PkgVersionCopyright.class);
+ persistedPkgVersionCopyright.setBody(copyright);
+ persistedPkgVersionCopyright.setPkgVersion(persistedPkgVersion);
+ }
+
+ // now add the licenses
+ for(String license : pkg.getLicenses()) {
+ PkgVersionLicense persistedPkgVersionLicense = objectContext.newObject(PkgVersionLicense.class);
+ persistedPkgVersionLicense.setBody(license);
+ persistedPkgVersionLicense.setPkgVersion(persistedPkgVersion);
+ }
+
+ if(null!=pkg.getHomePageUrl()) {
+ PkgVersionUrl persistedPkgVersionUrl = objectContext.newObject(PkgVersionUrl.class);
+ persistedPkgVersionUrl.setUrl(pkg.getHomePageUrl().getUrl());
+ persistedPkgVersionUrl.setPkgUrlType(PkgUrlType.getByCode(
+ objectContext,
+ pkg.getHomePageUrl().getUrlType().name().toLowerCase()).get());
+ persistedPkgVersionUrl.setPkgVersion(persistedPkgVersion);
+ }
+
+ if(!Strings.isNullOrEmpty(pkg.getSummary()) || !Strings.isNullOrEmpty(pkg.getDescription())) {
+ PkgVersionLocalization persistedPkgVersionLocalization = objectContext.newObject(PkgVersionLocalization.class);
+ persistedPkgVersionLocalization.setDescription(pkg.getDescription());
+ persistedPkgVersionLocalization.setSummary(pkg.getSummary());
+ persistedPkgVersionLocalization.setPkgVersion(persistedPkgVersion);
+ }
+
+ logger.info("the version {} of package {} did not exist; will create", pkg.getVersion().toString(), pkg.getName());
+ }
+
+ objectContext.commitChanges();
+
+ logger.info("have processed package {}",pkg.toString());
+
+ }
+
+}
diff --git a/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/services/ImportRepositoryDataService.java b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/services/ImportRepositoryDataService.java
new file mode 100644
index 00000000..9112f019
--- /dev/null
+++ b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/services/ImportRepositoryDataService.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.services;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Queues;
+import com.google.common.io.InputSupplier;
+import org.apache.cayenne.configuration.server.ServerRuntime;
+import org.haikuos.haikudepotserver.model.Repository;
+import org.haikuos.haikudepotserver.services.model.ImportRepositoryDataJob;
+import org.haikuos.haikudepotserver.support.Closeables;
+import org.haikuos.pkg.PkgIterator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ *
This object is responsible for migrating a HPKR file from a remote repository into the Haiku Depot Server
+ * database. It will copy the data into a local file and then work through it there. Note that this does
+ * not run the entire process in a single transaction; it will execute one transaction per package. So if the
+ * process fails, a repository update is likely to be partially imported.
+ *
+ *
The system works by the caller lodging a request to update from a remote repository. The request may be
+ * later superceeded by another request for the same repository. When the import process has capacity then it
+ * will undertake the import process.
+ */
+
+@Service
+public class ImportRepositoryDataService {
+
+ protected static Logger logger = LoggerFactory.getLogger(ImportRepositoryDataService.class);
+
+ public final static int SIZE_QUEUE = 10;
+
+ @Resource
+ ServerRuntime serverRuntime;
+
+ @Resource
+ ImportPackageService importPackageService;
+
+ private ThreadPoolExecutor executor = null;
+
+ private ArrayBlockingQueue runnables = Queues.newArrayBlockingQueue(SIZE_QUEUE);
+
+ private ThreadPoolExecutor getExecutor() {
+ if(null==executor) {
+ executor = new ThreadPoolExecutor(
+ 0, // core pool size
+ 1, // max pool size
+ 1l, // time to shutdown threads
+ TimeUnit.MINUTES,
+ runnables,
+ new ThreadPoolExecutor.AbortPolicy());
+ }
+
+ return executor;
+ }
+
+ /**
+ *
This method will check that there is not already a job in the queue for this repository and then will
+ * add it to the queue so that it is run at some time in the future.
+ * @param job
+ */
+
+ public void submit(final ImportRepositoryDataJob job) {
+ Preconditions.checkNotNull(job);
+
+ // first thing to do is to validate the request; does the repository exist and what is it's URL?
+ Optional repositoryOptional = Repository.getByCode(serverRuntime.getContext(), job.getCode());
+
+ if(!repositoryOptional.isPresent()) {
+ throw new RuntimeException("unable to import repository data because repository was not able to be found for code; "+job.getCode());
+ }
+
+ if(!Iterables.tryFind(runnables, new Predicate() {
+ @Override
+ public boolean apply(java.lang.Runnable input) {
+ ImportRepositoryDataJobRunnable importRepositoryDataJobRunnable = (ImportRepositoryDataJobRunnable) input;
+ return importRepositoryDataJobRunnable.equals(job);
+ }
+ }).isPresent()) {
+ getExecutor().submit(new ImportRepositoryDataJobRunnable(this,job));
+ logger.info("have submitted job to import repository data; {}", job.toString());
+ }
+ else {
+ logger.info("ignoring job to import repository data as there is already one waiting; {}", job.toString());
+
+ }
+ }
+
+ protected void run(ImportRepositoryDataJob job) {
+ Preconditions.checkNotNull(job);
+
+ Repository repository = Repository.getByCode(serverRuntime.getContext(), job.getCode()).get();
+ URL url;
+
+ try {
+ url = new URL(repository.getUrl());
+ }
+ catch(MalformedURLException mue) {
+ throw new IllegalStateException("the repository "+job.getCode()+" has a malformed url; "+repository.getUrl(),mue);
+ }
+
+ // now shift the URL's data into a temporary file and then process it.
+
+ File temporaryFile = null;
+ InputStream urlInputStream = null;
+
+ try {
+
+ urlInputStream = url.openStream();
+ temporaryFile = File.createTempFile(job.getCode()+"__import",".hpkr");
+ final InputStream finalUrlInputStream = urlInputStream;
+
+ com.google.common.io.Files.copy(
+ new InputSupplier() {
+ @Override
+ public InputStream getInput() throws IOException {
+ return finalUrlInputStream;
+ }
+ },
+ temporaryFile);
+
+ logger.info("did copy data for repository {} ({}) to temporary file",job.getCode(),url.toString());
+
+ org.haikuos.pkg.HpkrFileExtractor fileExtractor = new org.haikuos.pkg.HpkrFileExtractor(temporaryFile);
+ PkgIterator pkgIterator = new PkgIterator(fileExtractor.getPackageAttributesIterator());
+
+ long startTimeMs = System.currentTimeMillis();
+ logger.info("will process data for repository {}",job.getCode());
+
+ while(pkgIterator.hasNext()) {
+ importPackageService.run(repository.getObjectId(), pkgIterator.next());
+ }
+
+ logger.info("did process data for repository {} in {}ms",job.getCode(),System.currentTimeMillis()-startTimeMs);
+
+ }
+ catch(Throwable th) {
+ logger.error("a problem has arisen processing a repository file for repository "+job.getCode()+" from url '"+url.toString()+"'",th);
+ }
+ finally {
+ Closeables.closeQuietly(urlInputStream);
+
+ if(null!=temporaryFile && temporaryFile.exists()) {
+ temporaryFile.delete();
+ }
+ }
+
+ }
+
+ /**
+ *
This is the object that gets enqueued to actually do the work.
+ */
+
+ public static class ImportRepositoryDataJobRunnable implements Runnable {
+
+ private ImportRepositoryDataJob job;
+
+ private ImportRepositoryDataService service;
+
+ public ImportRepositoryDataJobRunnable(
+ ImportRepositoryDataService service,
+ ImportRepositoryDataJob job) {
+ Preconditions.checkNotNull(service);
+ Preconditions.checkNotNull(job);
+ this.service = service;
+ this.job = job;
+ }
+
+ @Override
+ public void run() {
+ service.run(job);
+ }
+
+ }
+
+}
diff --git a/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/services/SearchPkgsService.java b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/services/SearchPkgsService.java
new file mode 100644
index 00000000..bea68745
--- /dev/null
+++ b/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/services/SearchPkgsService.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2013, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.services;
+
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.ejbql.parser.EJBQL;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.query.EJBQLQuery;
+import org.apache.cayenne.query.SelectQuery;
+import org.apache.cayenne.query.SortOrder;
+import org.haikuos.haikudepotserver.model.Architecture;
+import org.haikuos.haikudepotserver.model.Pkg;
+import org.haikuos.haikudepotserver.model.PkgVersion;
+import org.haikuos.haikudepotserver.services.model.SearchPkgsSpecification;
+import org.haikuos.haikudepotserver.services.model.SearchPkgsSpecification;
+import org.haikuos.haikudepotserver.support.cayenne.LikeHelper;
+import org.springframework.stereotype.Service;
+
+import java.util.Collections;
+import java.util.List;
+
+@Service
+public class SearchPkgsService {
+
+ public List search(ObjectContext context, SearchPkgsSpecification search) {
+ Preconditions.checkNotNull(search);
+ Preconditions.checkNotNull(context);
+ Preconditions.checkState(search.getOffset() >= 0);
+ Preconditions.checkState(search.getLimit() > 0);
+ Preconditions.checkNotNull(search.getArchitectures());
+ Preconditions.checkState(!search.getArchitectures().isEmpty());
+
+ List pkgNames;
+
+ // using jpql because of need to get out raw rows for the pkg name.
+
+ {
+ StringBuilder queryBuilder = new StringBuilder();
+ List