├── run.bat ├── .gitignore ├── lib ├── log4j-1.2.jar ├── httpcore-4.0.1.jar ├── httpmime-4.0.3.jar ├── commons-logging.jar ├── httpclient-4.0.3.jar ├── apache-mime4j-0.6.jar ├── commons-codec-1.4.jar ├── httpcore-nio-4.0.1.jar ├── jackson-core-lgpl-1.5.6.jar └── jackson-mapper-lgpl-1.5.6.jar ├── authentication.properties ├── src ├── authentication.properties ├── log4j.properties └── cl │ └── continuum │ └── harvest │ ├── console │ ├── Option.java │ └── Main.java │ ├── Timer.java │ ├── RESTParameterizedType.java │ ├── Request.java │ ├── DayEntry.java │ ├── Client.java │ ├── util │ └── DebugServer.java │ ├── Daily.java │ ├── TaskAssignment.java │ ├── Task.java │ ├── HarvestCore.java │ ├── AbstractSerializer.java │ └── Project.java ├── .project ├── log4j.properties ├── README.txt └── .classpath /run.bat: -------------------------------------------------------------------------------- 1 | java -jar harvest-plugin.jar -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | data 3 | .idea 4 | log 5 | out 6 | Harvest-client.iml 7 | -------------------------------------------------------------------------------- /lib/log4j-1.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phase2/harvest-client/master/lib/log4j-1.2.jar -------------------------------------------------------------------------------- /lib/httpcore-4.0.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phase2/harvest-client/master/lib/httpcore-4.0.1.jar -------------------------------------------------------------------------------- /lib/httpmime-4.0.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phase2/harvest-client/master/lib/httpmime-4.0.3.jar -------------------------------------------------------------------------------- /lib/commons-logging.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phase2/harvest-client/master/lib/commons-logging.jar -------------------------------------------------------------------------------- /lib/httpclient-4.0.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phase2/harvest-client/master/lib/httpclient-4.0.3.jar -------------------------------------------------------------------------------- /lib/apache-mime4j-0.6.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phase2/harvest-client/master/lib/apache-mime4j-0.6.jar -------------------------------------------------------------------------------- /lib/commons-codec-1.4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phase2/harvest-client/master/lib/commons-codec-1.4.jar -------------------------------------------------------------------------------- /lib/httpcore-nio-4.0.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phase2/harvest-client/master/lib/httpcore-nio-4.0.1.jar -------------------------------------------------------------------------------- /authentication.properties: -------------------------------------------------------------------------------- 1 | username=mauricio@radiochela.com 2 | password=asdfasdf 3 | host=radiochela.harvestapp.com -------------------------------------------------------------------------------- /src/authentication.properties: -------------------------------------------------------------------------------- 1 | username=mauricio@radiochela.com 2 | password=asdfasdf 3 | host=radiochela.harvestapp.com -------------------------------------------------------------------------------- /lib/jackson-core-lgpl-1.5.6.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phase2/harvest-client/master/lib/jackson-core-lgpl-1.5.6.jar -------------------------------------------------------------------------------- /lib/jackson-mapper-lgpl-1.5.6.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phase2/harvest-client/master/lib/jackson-mapper-lgpl-1.5.6.jar -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | harvest-client 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | -------------------------------------------------------------------------------- /log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=DEBUG,FILEAPPENDER 2 | 3 | log4j.appender.FILEAPPENDER=org.apache.log4j.DailyRollingFileAppender 4 | log4j.appender.FILEAPPENDER.File=./log/harvest-console.log 5 | log4j.appender.FILEAPPENDER.layout=org.apache.log4j.PatternLayout 6 | log4j.appender.FILEAPPENDER.append=true 7 | log4j.appender.FILEAPPENDER.layout.ConversionPattern=%d{ABSOLUTE} | %-5p [%c{1}:%L] - %m%n 8 | log4j.appender.FILEAPPENDER.DatePattern='.'yyyy-MM-dd'.log' 9 | log4j.appender.FILEAPPENDER.Threshold=ALL 10 | 11 | log4j.logger.httpclient.wire.header=DEBUG 12 | log4j.logger.org.apache.commons.httpclient=DEBUG -------------------------------------------------------------------------------- /src/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=DEBUG,FILEAPPENDER 2 | 3 | log4j.appender.FILEAPPENDER=org.apache.log4j.DailyRollingFileAppender 4 | log4j.appender.FILEAPPENDER.File=./log/harvest-console.log 5 | log4j.appender.FILEAPPENDER.layout=org.apache.log4j.PatternLayout 6 | log4j.appender.FILEAPPENDER.append=true 7 | log4j.appender.FILEAPPENDER.layout.ConversionPattern=%d{ABSOLUTE} | %-5p [%c{1}:%L] - %m%n 8 | log4j.appender.FILEAPPENDER.DatePattern='.'yyyy-MM-dd'.log' 9 | log4j.appender.FILEAPPENDER.Threshold=ALL 10 | 11 | log4j.logger.httpclient.wire.header=DEBUG 12 | log4j.logger.org.apache.commons.httpclient=DEBUG -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | Harvest Plugin 2 | 3 | This is an open source project, built in Santiago of Chile by Mauricio Offermann to Continnuum Chile, 4 | http://www.continuum.cl. 5 | 6 | The initial mainidea was to build a Eclipse plugin, but for many reasons the final project is only a 7 | harvest console client. The target is assign shortcuts in Eclipse IDE to admin Daily entries. 8 | 9 | If you want continues this project feel free to do. 10 | 11 | Instalation 12 | 13 | 1º Open file authentication.properties, set your account information and save it. 14 | 2º Run the file run.bat 15 | 16 | if you have problems to run, probably you need to install Java and set the JAVA_HOME enviroment variable. -------------------------------------------------------------------------------- /src/cl/continuum/harvest/console/Option.java: -------------------------------------------------------------------------------- 1 | package cl.continuum.harvest.console; 2 | 3 | import java.lang.reflect.Method; 4 | 5 | public class Option { 6 | 7 | private char codigo; 8 | private String descripcion; 9 | private String metodo; 10 | public Option(char codigo, String descripcion, String metodo) { 11 | super(); 12 | this.codigo = codigo; 13 | this.descripcion = descripcion; 14 | this.metodo = metodo; 15 | } 16 | 17 | public void execute() throws Exception { 18 | Method method = Main.class.getMethod(metodo); 19 | method.invoke(null); 20 | } 21 | 22 | public String toString() { 23 | return "(" + codigo + ") " + descripcion; 24 | } 25 | 26 | 27 | } 28 | -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/cl/continuum/harvest/Timer.java: -------------------------------------------------------------------------------- 1 | package cl.continuum.harvest; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | 6 | import org.codehaus.jackson.JsonParseException; 7 | import org.codehaus.jackson.map.JsonMappingException; 8 | 9 | public class Timer extends AbstractSerializer { 10 | 11 | private DayEntry day_entry; 12 | private float hours_for_previously_running_timer; 13 | public DayEntry getDay_entry() { 14 | return day_entry; 15 | } 16 | public void setDay_entry(DayEntry dayEntry) { 17 | day_entry = dayEntry; 18 | } 19 | public float getHours_for_previously_running_timer() { 20 | return hours_for_previously_running_timer; 21 | } 22 | public void setHours_for_previously_running_timer( 23 | float hoursForPreviouslyRunningTimer) { 24 | hours_for_previously_running_timer = hoursForPreviouslyRunningTimer; 25 | } 26 | 27 | public static Timer get(int day_entry_id) throws JsonParseException, JsonMappingException, IOException { 28 | String uri = "/daily/show/" + day_entry_id; 29 | InputStream content = HarvestCore.get(uri); 30 | return HarvestCore.mapper.readValue(content, Timer.class); 31 | } 32 | 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/cl/continuum/harvest/RESTParameterizedType.java: -------------------------------------------------------------------------------- 1 | package cl.continuum.harvest; 2 | 3 | 4 | import java.lang.reflect.ParameterizedType; 5 | import java.lang.reflect.Type; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | 10 | 11 | public class RESTParameterizedType implements ParameterizedType { 12 | 13 | private Type rawType; 14 | private Type[] actualTypeArguments; 15 | 16 | private RESTParameterizedType(Type rawType, Type[] actualTypeArguments) { 17 | super(); 18 | this.rawType = rawType; 19 | this.actualTypeArguments = actualTypeArguments; 20 | } 21 | 22 | public RESTParameterizedType(Type targetType, boolean isList) { 23 | if (isList) { 24 | this.rawType = List.class; 25 | this.actualTypeArguments = new Type[] {new RESTParameterizedType(Map.class, new Type[] {String.class, targetType})}; 26 | } else { 27 | this.rawType = Map.class; 28 | this.actualTypeArguments = new Type[] {String.class, targetType}; 29 | } 30 | 31 | } 32 | 33 | 34 | public boolean isList() { 35 | return List.class.isAssignableFrom((Class)rawType); 36 | } 37 | 38 | @Override 39 | public Type[] getActualTypeArguments() { 40 | return this.actualTypeArguments; 41 | } 42 | 43 | @Override 44 | public Type getOwnerType() { 45 | return null; 46 | } 47 | 48 | @Override 49 | public Type getRawType() { 50 | return this.rawType; 51 | } 52 | 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/cl/continuum/harvest/Request.java: -------------------------------------------------------------------------------- 1 | package cl.continuum.harvest; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | 6 | import org.apache.http.client.ClientProtocolException; 7 | 8 | public class Request extends AbstractSerializer { 9 | 10 | private String notes; 11 | private float hours; 12 | private int project_id; 13 | private int task_id; 14 | private String spent_at; 15 | 16 | public String getNotes() { 17 | return notes; 18 | } 19 | public void setNotes(String notes) { 20 | this.notes = notes; 21 | } 22 | public float getHours() { 23 | return hours; 24 | } 25 | public void setHours(float hours) { 26 | this.hours = hours; 27 | } 28 | public int getProject_id() { 29 | return project_id; 30 | } 31 | public void setProject_id(int projectId) { 32 | project_id = projectId; 33 | } 34 | public int getTask_id() { 35 | return task_id; 36 | } 37 | public void setTask_id(int taskId) { 38 | task_id = taskId; 39 | } 40 | public String getSpent_at() { 41 | return spent_at; 42 | } 43 | public void setSpent_at(String spentAt) { 44 | spent_at = spentAt; 45 | } 46 | 47 | public DayEntry add() throws ClientProtocolException, IOException { 48 | String uri = "/daily/add"; 49 | InputStream content = HarvestCore.post(uri, toJson(false, this)); 50 | if (content != null) 51 | return HarvestCore.mapper.readValue(content, DayEntry.class); 52 | System.out.println("Problems to save "); 53 | return null; 54 | } 55 | 56 | public Request update(int day_entry_id) throws ClientProtocolException, IOException { 57 | return (Request) add("/daily/update/" + day_entry_id); 58 | } 59 | 60 | public static void main(String[] args) throws ClientProtocolException, IOException { 61 | Request request = new Request(); 62 | request.setProject_id(876359); 63 | request.setTask_id(655857); 64 | request.setNotes("Esta es la primera nota asignada"); 65 | request.setSpent_at("Mon, 18 Oct 2010"); 66 | request.setHours(0); 67 | request.add(); 68 | } 69 | 70 | 71 | 72 | 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/cl/continuum/harvest/DayEntry.java: -------------------------------------------------------------------------------- 1 | package cl.continuum.harvest; 2 | 3 | import java.io.InputStream; 4 | import java.io.Serializable; 5 | 6 | public class DayEntry extends AbstractSerializer implements Serializable { 7 | 8 | /** 9 | * 10 | */ 11 | private static final long serialVersionUID = 7511143115137074613L; 12 | private String client; 13 | private int project_id; 14 | private String project; 15 | private int task_id; 16 | private String task; 17 | private float hours; 18 | private String notes; 19 | private String timer_started_at; 20 | private float hours_for_previously_running_timer; 21 | 22 | 23 | public String getClient() { 24 | return client; 25 | } 26 | public float getHours_for_previously_running_timer() { 27 | return hours_for_previously_running_timer; 28 | } 29 | public void setHours_for_previously_running_timer( 30 | float hoursForPreviouslyRunningTimer) { 31 | hours_for_previously_running_timer = hoursForPreviouslyRunningTimer; 32 | } 33 | public void setClient(String client) { 34 | this.client = client; 35 | } 36 | public String getProject() { 37 | return project; 38 | } 39 | public void setProject(String project) { 40 | this.project = project; 41 | } 42 | public String getTask() { 43 | return task; 44 | } 45 | public void setTask(String task) { 46 | this.task = task; 47 | } 48 | public float getHours() { 49 | return hours; 50 | } 51 | public void setHours(float hours) { 52 | this.hours = hours; 53 | } 54 | public String getNotes() { 55 | return notes; 56 | } 57 | public void setNotes(String notes) { 58 | this.notes = notes; 59 | } 60 | public String getTimer_started_at() { 61 | return timer_started_at; 62 | } 63 | public void setTimer_started_at(String timerStartedAt) { 64 | timer_started_at = timerStartedAt; 65 | } 66 | public int getProject_id() { 67 | return project_id; 68 | } 69 | public void setProject_id(int projectId) { 70 | project_id = projectId; 71 | } 72 | public int getTask_id() { 73 | return task_id; 74 | } 75 | public void setTask_id(int taskId) { 76 | task_id = taskId; 77 | } 78 | 79 | 80 | public DayEntry togleTimer() throws Exception { 81 | if (getId() == 0) 82 | return null; 83 | String uri ="/daily/timer/" + getId(); 84 | InputStream content = HarvestCore.get(uri); 85 | return HarvestCore.mapper.readValue(content, DayEntry.class); 86 | } 87 | 88 | 89 | 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/cl/continuum/harvest/Client.java: -------------------------------------------------------------------------------- 1 | package cl.continuum.harvest; 2 | 3 | import org.apache.http.client.ClientProtocolException; 4 | import org.codehaus.jackson.JsonParseException; 5 | import org.codehaus.jackson.map.JsonMappingException; 6 | 7 | import java.io.IOException; 8 | import java.io.Serializable; 9 | import java.util.List; 10 | import java.util.Scanner; 11 | import java.util.regex.Pattern; 12 | 13 | /** 14 | * 15 | */ 16 | public class Client extends AbstractSerializer implements Serializable { 17 | 18 | private static final long serialVersionUID = 8926028143190627264L; 19 | 20 | private String name; 21 | 22 | public String getName() { 23 | return name; 24 | } 25 | 26 | public void setName(String name) { 27 | this.name = name; 28 | } 29 | 30 | public static List list() throws ClientProtocolException, IOException, ClassNotFoundException { 31 | return list(Client.class); 32 | } 33 | 34 | public static Client get(int id) throws JsonParseException, JsonMappingException, IOException, ClassNotFoundException { 35 | return get(id, Client.class); 36 | } 37 | 38 | public static Client select() throws IOException, ClassNotFoundException { 39 | List clients = list(); 40 | return select(clients); 41 | 42 | } 43 | 44 | public static Client select(List clients) throws IOException, ClassNotFoundException { 45 | System.out.println("Select client"); 46 | System.out.println("======================="); 47 | System.out.println("\tq: Quit"); 48 | System.out.println("\t0: None"); 49 | for (int i=0; i < clients.size(); i++) { 50 | Client client = clients.get(i); 51 | System.out.println("\t" + (i+1) + ": " + client.getName()); 52 | } 53 | Scanner scan = new Scanner(System.in); 54 | String option = ""; 55 | while (true) { 56 | System.out.print("\nChoose project number:"); 57 | option = scan.next(); 58 | if (option.equalsIgnoreCase("q")) { 59 | System.out.println("Good bye!"); 60 | System.exit(0); 61 | } 62 | if (!Pattern.matches("[0-9]+", option)) { 63 | System.out.println("You must enter a number"); 64 | continue; 65 | } 66 | int index = Integer.parseInt(option); 67 | if (index < 0 || index > clients.size()) { 68 | System.out.println("Option out of range"); 69 | continue; 70 | } 71 | if (index == 0) 72 | return null; 73 | return clients.get(index - 1); 74 | } 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/cl/continuum/harvest/util/DebugServer.java: -------------------------------------------------------------------------------- 1 | package cl.continuum.harvest.util; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.io.ByteArrayOutputStream; 5 | import java.io.File; 6 | import java.io.FileOutputStream; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.OutputStream; 10 | import java.net.ServerSocket; 11 | import java.net.Socket; 12 | import java.util.regex.Matcher; 13 | import java.util.regex.Pattern; 14 | 15 | public class DebugServer { 16 | 17 | public static final String SHUTDOWN_TOKEN = "/shutdown"; 18 | public static final String SHUTDOWN_REGEX = "GET (.*) HTTP"; 19 | 20 | 21 | public static void copy(InputStream in, OutputStream out, boolean closeAll) throws IOException { 22 | try { 23 | // String endMark = new String(new byte[] {13,10,13,10, 0}); 24 | String endMark = new String(new byte[] {0, 0, 0}); 25 | Pattern p = Pattern.compile(endMark); 26 | while (true) { 27 | byte[] buffer = new byte[1024]; 28 | 29 | int amountRead = in.read(buffer); 30 | out.write(buffer, 0, amountRead); 31 | Matcher m = p.matcher(new String(buffer)); 32 | if (m.find()) 33 | break; 34 | } 35 | } catch (Exception e) { 36 | throw new IOException("Problemas al copiar los datos", e); 37 | } finally { 38 | if (closeAll) { 39 | in.close(); 40 | out.close(); 41 | } 42 | } 43 | } 44 | 45 | 46 | public static void start(int port, File outputFile) throws IOException { 47 | System.out.println("Iniciando server ..."); 48 | System.out.println("PUERTO: " + port); 49 | System.out.println("SHUTDOWN: http://localhost:" + port + SHUTDOWN_TOKEN); 50 | Pattern pattern = Pattern.compile(SHUTDOWN_REGEX); 51 | ServerSocket server = new ServerSocket(port); 52 | while (true) { 53 | Socket socket = server.accept(); 54 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); 55 | System.out.println("DATOS RECIBIDOS!"); 56 | copy(socket.getInputStream(), bos, false); 57 | socket.getOutputStream().write("HTTP/1.1 404 Not Found\n\r\n\r".getBytes()); 58 | socket.close(); 59 | 60 | byte[] outputBytes = bos.toByteArray(); 61 | bos.close(); 62 | 63 | ByteArrayInputStream bis = new ByteArrayInputStream(outputBytes); 64 | copy(bis, new FileOutputStream(outputFile), true); 65 | 66 | String outputString = new String(outputBytes); 67 | System.out.println("RECIBIENDO:\n" + outputString); 68 | Matcher matcher = pattern.matcher(outputString); 69 | if (matcher.find() && SHUTDOWN_TOKEN.equalsIgnoreCase(matcher.group(1))) 70 | break; 71 | } 72 | System.out.println("Cerrando server ..."); 73 | server.close(); 74 | } 75 | 76 | public static void main(String[] args) throws IOException { 77 | start(80, new File("d:/output.dat")); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/cl/continuum/harvest/Daily.java: -------------------------------------------------------------------------------- 1 | package cl.continuum.harvest; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.util.List; 6 | import java.util.Scanner; 7 | import java.util.regex.Pattern; 8 | 9 | import org.apache.http.client.ClientProtocolException; 10 | import org.codehaus.jackson.map.DeserializationConfig.Feature; 11 | 12 | public class Daily extends AbstractSerializer { 13 | 14 | private String for_day; 15 | private List day_entries; 16 | private List projects; 17 | public String getFor_day() { 18 | return for_day; 19 | } 20 | public void setFor_day(String forDay) { 21 | for_day = forDay; 22 | } 23 | public List getDay_entries() { 24 | return day_entries; 25 | } 26 | public void setDay_entries(List dayEntries) { 27 | day_entries = dayEntries; 28 | } 29 | public List getProjects() { 30 | return projects; 31 | } 32 | public void setProjects(List projects) { 33 | this.projects = projects; 34 | } 35 | 36 | 37 | public static Daily get() throws ClientProtocolException, IOException { 38 | String uri = "/daily"; 39 | InputStream content = HarvestCore.get(uri); 40 | HarvestCore.mapper.configure(Feature.FAIL_ON_UNKNOWN_PROPERTIES, false); 41 | return HarvestCore.mapper.readValue(content, Daily.class); 42 | 43 | } 44 | 45 | public DayEntry select() throws IOException, ClassNotFoundException { 46 | System.out.println("Select an entry:"); 47 | System.out.println("======================="); 48 | System.out.println("\tq: Quit"); 49 | System.out.println("\t0: None"); 50 | for (int i=0; i < day_entries.size(); i++) { 51 | DayEntry day_entry = day_entries.get(i); 52 | System.out.println("\t" + (i+1) + ": " + day_entry.getNotes()); 53 | } 54 | Scanner scan = new Scanner(System.in); 55 | String option = ""; 56 | while (true) { 57 | System.out.print("\nChoose an entry:"); 58 | option = scan.next(); 59 | if (option.equalsIgnoreCase("q")) { 60 | System.out.println("Good bye!"); 61 | System.exit(0); 62 | } 63 | if (!Pattern.matches("[0-9]+", option)) { 64 | System.out.println("You must to enter a number"); 65 | continue; 66 | } 67 | int index = Integer.parseInt(option); 68 | if (index < 0 || index > day_entries.size()) { 69 | System.out.println("Option out of range"); 70 | continue; 71 | } 72 | if (index == 0) 73 | return null; 74 | return day_entries.get(index - 1); 75 | } 76 | } 77 | 78 | 79 | 80 | public static void main(String[] args) throws ClientProtocolException, IOException { 81 | 82 | Daily daily = get(); 83 | for (Project project : daily.getProjects()) { 84 | System.out.println(project.getName() + "\t" + project.getId()); 85 | System.out.println("==========="); 86 | for (Task task : project.getTasks()) 87 | System.out.println("\t" + task.getName() + "\t" + task.getId()); 88 | } 89 | 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/cl/continuum/harvest/TaskAssignment.java: -------------------------------------------------------------------------------- 1 | package cl.continuum.harvest; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.util.List; 6 | 7 | import org.apache.http.client.ClientProtocolException; 8 | import org.codehaus.jackson.JsonGenerationException; 9 | import org.codehaus.jackson.JsonParseException; 10 | import org.codehaus.jackson.map.JsonMappingException; 11 | 12 | public class TaskAssignment extends AbstractSerializer { 13 | 14 | private int project_id; 15 | private int task_id; 16 | private boolean billable; 17 | private boolean deactivated; 18 | private int budget; 19 | private int hourly_rate; 20 | public int getProject_id() { 21 | return project_id; 22 | } 23 | public void setProject_id(int projectId) { 24 | project_id = projectId; 25 | } 26 | public int getTask_id() { 27 | return task_id; 28 | } 29 | public void setTask_id(int taskId) { 30 | task_id = taskId; 31 | } 32 | public boolean isBillable() { 33 | return billable; 34 | } 35 | public void setBillable(boolean billable) { 36 | this.billable = billable; 37 | } 38 | public boolean isDeactivated() { 39 | return deactivated; 40 | } 41 | public void setDeactivated(boolean deactivated) { 42 | this.deactivated = deactivated; 43 | } 44 | public int getBudget() { 45 | return budget; 46 | } 47 | public void setBudget(int budget) { 48 | this.budget = budget; 49 | } 50 | public int getHourly_rate() { 51 | return hourly_rate; 52 | } 53 | public void setHourly_rate(int hourlyRate) { 54 | hourly_rate = hourlyRate; 55 | } 56 | 57 | 58 | 59 | public AbstractSerializer add() throws ClientProtocolException, IOException { 60 | String plural = getClass().getSimpleName().toLowerCase() + "s"; 61 | InputStream content = HarvestCore.post("/" + plural, toJson()); 62 | return fromJson(content, getClass()); 63 | } 64 | 65 | public static List list(int project_id) throws ClientProtocolException, IOException, ClassNotFoundException { 66 | String uri = "/projects/" + project_id + "/task_assignments"; 67 | InputStream content = HarvestCore.get(uri); 68 | return fromJsonList(content, TaskAssignment.class); 69 | } 70 | 71 | public static TaskAssignment get(int project_id, long id) throws JsonParseException, JsonMappingException, IOException, ClassNotFoundException { 72 | String uri = "/projects/" + project_id + "/task_assignments/" + id; 73 | InputStream content = HarvestCore.get(uri); 74 | return fromJson(content, TaskAssignment.class); 75 | } 76 | 77 | public void delete() throws ClientProtocolException, IOException { 78 | String uri = "/projects/" + project_id + "/task_assignments/" + getId(); 79 | HarvestCore.delete(uri); 80 | } 81 | 82 | public TaskAssignment update() throws JsonGenerationException, JsonMappingException, ClientProtocolException, IOException { 83 | String uri = "/projects/" + project_id + "/task_assignments/" + getId(); 84 | InputStream content = HarvestCore.put(uri, toJson()); 85 | return fromJson(content, TaskAssignment.class); 86 | } 87 | 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/cl/continuum/harvest/Task.java: -------------------------------------------------------------------------------- 1 | package cl.continuum.harvest; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.Serializable; 6 | import java.util.List; 7 | import java.util.Scanner; 8 | import java.util.regex.Pattern; 9 | 10 | import org.apache.commons.logging.Log; 11 | import org.apache.commons.logging.LogFactory; 12 | import org.apache.http.client.ClientProtocolException; 13 | import org.codehaus.jackson.JsonGenerationException; 14 | import org.codehaus.jackson.JsonParseException; 15 | import org.codehaus.jackson.map.JsonMappingException; 16 | 17 | /** 18 | * Project clas to admin projects requests 19 | * 20 | * @author Mauricio Offermann Palma 21 | * 22 | */ 23 | public class Task extends AbstractSerializer implements Serializable { 24 | 25 | 26 | /** 27 | * 28 | */ 29 | private static final long serialVersionUID = 4381249959658450952L; 30 | private boolean billable_by_default; 31 | private float default_hourly_rate; 32 | private boolean is_default; 33 | private String name; 34 | private boolean deactivated; 35 | 36 | 37 | private static final Log log = LogFactory.getLog(Task.class); 38 | 39 | 40 | public boolean isBillable_by_default() { 41 | return billable_by_default; 42 | } 43 | 44 | public void setBillable_by_default(boolean billableByDefault) { 45 | billable_by_default = billableByDefault; 46 | } 47 | 48 | public float getDefault_hourly_rate() { 49 | return default_hourly_rate; 50 | } 51 | 52 | public void setDefault_hourly_rate(float defaultHourlyRate) { 53 | default_hourly_rate = defaultHourlyRate; 54 | } 55 | 56 | public boolean isIs_default() { 57 | return is_default; 58 | } 59 | 60 | public void setIs_default(boolean isDefault) { 61 | is_default = isDefault; 62 | } 63 | 64 | public String getName() { 65 | return name; 66 | } 67 | 68 | public void setName(String name) { 69 | this.name = name; 70 | } 71 | 72 | 73 | 74 | 75 | public static Log getLog() { 76 | return log; 77 | } 78 | 79 | 80 | 81 | public boolean isDeactivated() { 82 | return deactivated; 83 | } 84 | 85 | 86 | public void setDeactivated(boolean deactivated) { 87 | this.deactivated = deactivated; 88 | } 89 | 90 | public static List list() throws ClientProtocolException, IOException, ClassNotFoundException { 91 | return list(Task.class); 92 | } 93 | 94 | public static Task get(int id) throws JsonParseException, JsonMappingException, IOException, ClassNotFoundException { 95 | return get(id, Task.class); 96 | } 97 | 98 | public TaskAssignment assign(int project_id) throws JsonGenerationException, JsonMappingException, ClientProtocolException, IOException { 99 | String uri = "/projects/" + project_id + "/task_assignments/add_with_create_new_task"; 100 | InputStream content = HarvestCore.post(uri, toJson()); 101 | if (content == null) 102 | return null; 103 | return fromJson(content, TaskAssignment.class); 104 | } 105 | 106 | public static Task select() throws IOException, ClassNotFoundException { 107 | List tasks = list(); 108 | return select(tasks); 109 | } 110 | 111 | 112 | public static Task select(List tasks) throws IOException, ClassNotFoundException { 113 | System.out.println("Task selection"); 114 | System.out.println("======================="); 115 | System.out.println("\tq: Quit"); 116 | System.out.println("\t0: None"); 117 | for (int i=0; i < tasks.size(); i++) { 118 | Task task = tasks.get(i); 119 | System.out.println("\t" + (i+1) + ": " + task.getName()); 120 | } 121 | Scanner scan = new Scanner(System.in); 122 | String option = ""; 123 | while (true) { 124 | System.out.print("\nChoose task number:"); 125 | option = scan.next(); 126 | if (option.equalsIgnoreCase("q")) { 127 | System.out.println("Good bye!"); 128 | System.exit(0); 129 | } 130 | if (!Pattern.matches("[0-9]+", option)) { 131 | System.out.println("You must enter a number"); 132 | continue; 133 | } 134 | int index = Integer.parseInt(option); 135 | if (index < 0 || index > tasks.size()) { 136 | System.out.println("Opcion fuera de rango"); 137 | continue; 138 | } 139 | if (index == 0) 140 | return null; 141 | return tasks.get(index - 1); 142 | } 143 | } 144 | 145 | 146 | 147 | public static void main(String[] args) throws ClientProtocolException, IOException, ClassNotFoundException { 148 | Task task = select(); 149 | System.out.println(task.getName()); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/cl/continuum/harvest/HarvestCore.java: -------------------------------------------------------------------------------- 1 | package cl.continuum.harvest; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.io.ByteArrayOutputStream; 5 | import java.io.FileReader; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.OutputStream; 9 | import java.lang.reflect.Constructor; 10 | import java.util.Properties; 11 | 12 | import org.apache.commons.codec.binary.Base64; 13 | import org.apache.http.Header; 14 | import org.apache.http.HttpEntity; 15 | import org.apache.http.HttpResponse; 16 | import org.apache.http.auth.AuthScope; 17 | import org.apache.http.auth.UsernamePasswordCredentials; 18 | import org.apache.http.client.ClientProtocolException; 19 | import org.apache.http.client.HttpClient; 20 | import org.apache.http.client.methods.HttpDelete; 21 | import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; 22 | import org.apache.http.client.methods.HttpGet; 23 | import org.apache.http.client.methods.HttpPost; 24 | import org.apache.http.client.methods.HttpPut; 25 | import org.apache.http.client.methods.HttpUriRequest; 26 | import org.apache.http.entity.StringEntity; 27 | import org.apache.http.impl.client.DefaultHttpClient; 28 | import org.apache.http.params.CoreProtocolPNames; 29 | import org.codehaus.jackson.map.DeserializationConfig; 30 | import org.codehaus.jackson.map.ObjectMapper; 31 | 32 | /** 33 | * This class provides methods to send request to harvest. 34 | * TODO: Probably is necesary to control more error codes. 35 | * @author Mauricio Offermann 36 | * 37 | */ 38 | public class HarvestCore { 39 | 40 | private static String USERNAME; 41 | private static String PASSWORD; 42 | private static String HOST; 43 | public static final ObjectMapper mapper = new ObjectMapper(); 44 | public static String credentials; 45 | 46 | static { 47 | try{ 48 | Properties property = new Properties(); 49 | property.load(new FileReader("authentication.properties")); 50 | USERNAME = property.getProperty("username"); 51 | PASSWORD = property.getProperty("password"); 52 | HOST = property.getProperty("host"); 53 | credentials = USERNAME + ":" + PASSWORD; 54 | } catch (IOException e) { 55 | e.printStackTrace(); 56 | System.out.println("Can't find authentication.properties"); 57 | } 58 | 59 | mapper.getDeserializationConfig().set(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false); 60 | } 61 | 62 | 63 | public static void copy(InputStream in, OutputStream out) throws IOException { 64 | byte[] buffer = new byte[1024]; 65 | while (true) { 66 | int amountRead = in.read(buffer); 67 | if (amountRead == -1) 68 | break; 69 | out.write(buffer, 0, amountRead); 70 | } 71 | } 72 | 73 | public static HttpClient openConnection() { 74 | DefaultHttpClient httpclient = new DefaultHttpClient(); 75 | httpclient.getCredentialsProvider().setCredentials( 76 | new AuthScope(HOST, 443), 77 | new UsernamePasswordCredentials(USERNAME, PASSWORD)); 78 | 79 | httpclient.getParams().setBooleanParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, false); 80 | httpclient.getParams().setParameter(CoreProtocolPNames.USER_AGENT, "Java"); 81 | return httpclient; 82 | } 83 | 84 | 85 | public static InputStream send(HttpClient httpclient, HttpUriRequest request, String parameters) throws ClientProtocolException, IOException { 86 | request.setHeader("Accept", "application/json"); 87 | request.setHeader("Content-Type", "application/json"); 88 | request.setHeader("Authorization", "Basic " + Base64.encodeBase64String(credentials.getBytes()).trim()); 89 | if (parameters != null) { 90 | StringEntity reqentity = new StringEntity(parameters); 91 | if (request instanceof HttpEntityEnclosingRequestBase) 92 | ((HttpEntityEnclosingRequestBase)request).setEntity(reqentity); 93 | else 94 | throw new ClientProtocolException("Method not allowed. Don't send parameters or use POST or PUT"); 95 | 96 | } 97 | HttpResponse response = httpclient.execute(request); 98 | System.out.println(response.getStatusLine()); 99 | if (response.getStatusLine().getStatusCode() == 404) { 100 | System.out.println("Service not found"); 101 | return null; 102 | } 103 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); 104 | HttpEntity entity = response.getEntity(); 105 | if (entity != null) 106 | entity.writeTo(bos); 107 | Header[] locations = response.getHeaders("Location"); 108 | if (locations == null || locations.length == 0) 109 | return new ByteArrayInputStream(bos.toByteArray()); 110 | String uri = "https://" + HOST; 111 | uri += locations[0].getValue(); 112 | HttpGet get = new HttpGet(uri); 113 | return send(httpclient, get, null); 114 | } 115 | 116 | public static InputStream send(Class methodClass, String uri, String parameters) throws ClientProtocolException, IOException { 117 | try { 118 | uri = "https://" + HOST + uri; 119 | Constructor constructor = methodClass.getConstructor(String.class); 120 | HttpUriRequest request = constructor.newInstance(uri); 121 | HttpClient httpclient = openConnection(); 122 | InputStream content = send(httpclient, request, parameters); 123 | httpclient.getConnectionManager().shutdown(); 124 | return content; 125 | } catch (Exception e) { 126 | throw new ClientProtocolException("Problems to send", e); 127 | } 128 | } 129 | 130 | public static InputStream send(Class methodClass, String uri) throws ClientProtocolException, IOException { 131 | return send(methodClass, uri, null); 132 | } 133 | 134 | public static InputStream get(String uri) throws ClientProtocolException, IOException { 135 | return send(HttpGet.class, uri); 136 | } 137 | 138 | public static InputStream post(String uri, final String parameters) throws ClientProtocolException, IOException { 139 | return send(HttpPost.class, uri, parameters); 140 | } 141 | 142 | public static InputStream delete(String uri) throws ClientProtocolException, IOException { 143 | return send(HttpDelete.class, uri); 144 | } 145 | 146 | public static InputStream put(String uri, final String parameters) throws ClientProtocolException, IOException { 147 | return send(HttpPut.class, uri, parameters); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/cl/continuum/harvest/console/Main.java: -------------------------------------------------------------------------------- 1 | package cl.continuum.harvest.console; 2 | 3 | import java.io.Closeable; 4 | import java.io.File; 5 | import java.io.FileInputStream; 6 | import java.io.FileOutputStream; 7 | import java.io.IOException; 8 | import java.io.ObjectInputStream; 9 | import java.io.ObjectOutputStream; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | import java.util.Scanner; 13 | import java.util.regex.Pattern; 14 | 15 | import org.apache.http.client.ClientProtocolException; 16 | 17 | import cl.continuum.harvest.Client; 18 | import cl.continuum.harvest.Daily; 19 | import cl.continuum.harvest.DayEntry; 20 | import cl.continuum.harvest.Project; 21 | import cl.continuum.harvest.Request; 22 | import cl.continuum.harvest.Task; 23 | 24 | /** 25 | * Main console testing 26 | * You can execute this class without arguments and follow the menu options. 27 | * Also you can use classic flags as arguments, example: 28 | * 29 | * java -jar harvest-plugin.jar -a 30 | * To assign selected task to actual project. 31 | * 32 | * The flags used are the same options than menu. 33 | * 34 | * @author Mauricio Offermann 35 | * 36 | */ 37 | public class Main { 38 | 39 | private static final Map options = new HashMap(); 40 | 41 | private static Client client = null; 42 | private static Project project = null; 43 | private static Task task = null; 44 | private static DayEntry dayEntry = null; 45 | 46 | 47 | static { 48 | Main.client = readObject(Client.class); 49 | Main.project = readObject(Project.class); 50 | Main.task = readObject(Task.class); 51 | Main.dayEntry = readObject(DayEntry.class); 52 | 53 | options.put("a", new Option('a', "Assign task to project", "assignTask")); 54 | options.put("c", new Option('c', "Select client", "selectClient")); 55 | options.put("p", new Option('p', "Select project", "selectProject")); 56 | options.put("t", new Option('t', "Select task from project", "selectTask")); 57 | options.put("k", new Option('k', "List all tasks", "listTasks")); 58 | options.put("s", new Option('s', "Stop/Start timer from actual dayentry", "toggleTimer")); 59 | options.put("l", new Option('l', "List day entries and selected project", "show")); 60 | options.put("n", new Option('n', "Add day entry", "addDayEntry")); 61 | options.put("q", new Option('q', "Quit", "exit")); 62 | } 63 | 64 | public static void exit() { 65 | System.out.println("Good bye"); 66 | System.exit(0); 67 | } 68 | 69 | public static void selectProject() throws IOException, ClassNotFoundException { 70 | Main.project = Project.select(); 71 | writeObject(Main.project); 72 | } 73 | 74 | public static void selectClient() throws IOException, ClassNotFoundException { 75 | Main.client = Client.select(); 76 | writeObject(Main.client); 77 | } 78 | 79 | public static void listTasks() throws IOException, ClassNotFoundException { 80 | Main.task = Task.select(); 81 | writeObject(Main.task); 82 | } 83 | 84 | public static void assignTask() throws IOException, ClassNotFoundException { 85 | if (Main.project == null) 86 | selectProject(); 87 | if (Main.task == null) 88 | listTasks(); 89 | Main.task.assign((int) Main.project.getId()); 90 | } 91 | 92 | public static void show() throws ClientProtocolException, IOException, ClassNotFoundException { 93 | Main.dayEntry = Daily.get().select(); 94 | writeObject(Main.dayEntry); 95 | } 96 | 97 | public static void addDayEntry() throws IOException, ClassNotFoundException { 98 | if (Main.project == null) 99 | selectProject(); 100 | if (Main.task == null) 101 | listTasks(); 102 | Scanner scan = new Scanner(System.in); 103 | System.out.print("Notes: "); 104 | String notes = scan.nextLine(); 105 | Request request = new Request(); 106 | request.setNotes(notes); 107 | request.setProject_id((int) Main.project.getId()); 108 | request.setTask_id((int) Main.task.getId()); 109 | Main.dayEntry = request.add(); 110 | writeObject(Main.dayEntry); 111 | } 112 | 113 | public static void toggleTimer() throws Exception { 114 | if (Main.project == null) 115 | selectProject(); 116 | if (Main.task == null) 117 | listTasks(); 118 | if (Main.dayEntry == null) 119 | Main.dayEntry = Daily.get().select(); 120 | Main.dayEntry = Main.dayEntry.togleTimer(); 121 | writeObject(Main.dayEntry); 122 | } 123 | 124 | public static void selectTask() throws IOException, ClassNotFoundException { 125 | Daily daily = Daily.get(); 126 | if (Main.project == null) 127 | selectProject(); 128 | if (Main.project == null) 129 | return; 130 | for (Project project : daily.getProjects()) { 131 | if (Main.project.getId() == project.getId()) { 132 | Main.task = Task.select(project.getTasks()); 133 | writeObject(Main.task); 134 | break; 135 | } 136 | 137 | } 138 | 139 | } 140 | 141 | 142 | private static void printMenu() { 143 | for (Option option : options.values()) 144 | System.out.println("\t" + option); 145 | System.out.println(""); 146 | if (Main.project != null) 147 | System.out.println("\nACTUAL PROJECT: " + Main.project.getName()); 148 | if (Main.task != null) 149 | System.out.println("\nACTUAL TASK: " + Main.task.getName()); 150 | if (Main.dayEntry != null) 151 | System.out.println("\nACTUAL DAY ENTRY: " + Main.dayEntry.getNotes()); 152 | System.out.print("\nChoose an option:"); 153 | } 154 | 155 | 156 | private static void menu() throws Exception { 157 | System.out.println("------ HARVEST CONSOLE ------\n"); 158 | Scanner scan = new Scanner(System.in); 159 | while (true) { 160 | printMenu(); 161 | String o =scan.next().toLowerCase(); 162 | if (!options.containsKey(o)) { 163 | System.out.println("Unknown option"); 164 | continue; 165 | } 166 | Option option = options.get(o); 167 | option.execute(); 168 | } 169 | } 170 | 171 | private static void close(Closeable c) { 172 | try { 173 | if (c != null) 174 | c.close(); 175 | } catch (IOException e) { 176 | //e.printStackTrace(); 177 | } 178 | } 179 | 180 | public static void writeObject(Object object) { 181 | if (object == null) 182 | return; 183 | ObjectOutputStream os = null; 184 | try { 185 | File dir = new File("data"); 186 | dir.mkdirs(); 187 | File file = new File(dir, object.getClass().getSimpleName()); 188 | os = new ObjectOutputStream(new FileOutputStream(file)); 189 | os.writeObject(object); 190 | } catch (Exception e) { 191 | e.printStackTrace(); 192 | } finally { 193 | close(os); 194 | } 195 | 196 | } 197 | 198 | public static T readObject(Class objectClass) { 199 | ObjectInputStream is = null; 200 | try { 201 | File file = new File("data", objectClass.getSimpleName()); 202 | if (!file.exists()) 203 | return null; 204 | is = new ObjectInputStream(new FileInputStream(file)); 205 | return objectClass.cast(is.readObject()); 206 | } catch (Exception e) { 207 | e.printStackTrace(); 208 | } finally { 209 | close(is); 210 | } 211 | return null; 212 | } 213 | 214 | 215 | public static void main(String[] args) throws Exception { 216 | if (args.length > 0){ 217 | for (String argument : args) { 218 | if (!Pattern.compile("^-[a-z]$").matcher(argument).matches()) 219 | throw new Exception("The argument '" + argument + "' is invalid."); 220 | String option = argument.substring(1); 221 | if (!options.containsKey(option)) 222 | throw new Exception("The argument '" + argument + "' is invalid."); 223 | options.get(option).execute(); 224 | } 225 | }else{ 226 | menu(); 227 | } 228 | } 229 | 230 | } 231 | ; -------------------------------------------------------------------------------- /src/cl/continuum/harvest/AbstractSerializer.java: -------------------------------------------------------------------------------- 1 | package cl.continuum.harvest; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.util.ArrayList; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | import org.apache.http.client.ClientProtocolException; 11 | import org.codehaus.jackson.JsonGenerationException; 12 | import org.codehaus.jackson.JsonParseException; 13 | import org.codehaus.jackson.map.JsonMappingException; 14 | import org.codehaus.jackson.map.type.TypeFactory; 15 | /** 16 | * Base entries class thats provides funtions to serialize to JSON an unserialize 17 | * to java objects. 18 | * Also contains methods to make requests easy from childrens. 19 | * IMPORTANT: Not all methods as add,delete, update, get and list have sense from inherit classes 20 | * due to some entities don't use classic plural to make requests. In this cases you must 21 | * override this methods passing the uri to methods that can accept it. 22 | * 23 | * @author Mauricio Offermann Palma 24 | * 25 | */ 26 | public abstract class AbstractSerializer { 27 | 28 | private long id; 29 | private int company_id; 30 | private String created_at; 31 | private String updated_at; 32 | private long cache_version; 33 | 34 | 35 | 36 | public long getId() { 37 | return id; 38 | } 39 | 40 | 41 | 42 | public void setId(long id) { 43 | this.id = id; 44 | } 45 | 46 | 47 | 48 | public int getCompany_id() { 49 | return company_id; 50 | } 51 | 52 | 53 | 54 | public void setCompany_id(int companyId) { 55 | company_id = companyId; 56 | } 57 | 58 | 59 | 60 | public String getCreated_at() { 61 | return created_at; 62 | } 63 | 64 | 65 | 66 | public void setCreated_at(String createdAt) { 67 | created_at = createdAt; 68 | } 69 | 70 | 71 | 72 | public String getUpdated_at() { 73 | return updated_at; 74 | } 75 | 76 | 77 | 78 | public void setUpdated_at(String updatedAt) { 79 | updated_at = updatedAt; 80 | } 81 | 82 | 83 | 84 | public long getCache_version() { 85 | return cache_version; 86 | } 87 | 88 | 89 | 90 | public void setCache_version(long cacheVersion) { 91 | cache_version = cacheVersion; 92 | } 93 | 94 | public static String getObjectName(Class objectClass) { 95 | return objectClass.getSimpleName().replaceAll("([A-Z])", "_$1").substring(1).toLowerCase(); 96 | 97 | } 98 | 99 | 100 | 101 | public static String toJson(boolean mapWrapped, Object target) throws JsonGenerationException, JsonMappingException, IOException { 102 | if (mapWrapped) { 103 | Map map = new HashMap(); 104 | map.put(target.getClass().getSimpleName().toLowerCase(), target); 105 | return HarvestCore.mapper.writeValueAsString(map); 106 | } 107 | return HarvestCore.mapper.writeValueAsString(target); 108 | } 109 | public String toJson() throws JsonGenerationException, JsonMappingException, IOException { 110 | return toJson(true, this); 111 | } 112 | 113 | public static T fromJson(String source, Class objectClass) throws JsonParseException, JsonMappingException, IOException { 114 | Map map = HarvestCore.mapper.readValue(source, TypeFactory.fromType(new RESTParameterizedType(objectClass, false))); 115 | return map.get(getObjectName(objectClass)); 116 | } 117 | 118 | public static T fromJson(InputStream source, Class objectClass) throws JsonParseException, JsonMappingException, IOException { 119 | if (source == null) 120 | throw new IOException("Source is null"); 121 | Map map = HarvestCore.mapper.readValue(source, TypeFactory.fromType(new RESTParameterizedType(objectClass, false))); 122 | return map.get(getObjectName(objectClass)); 123 | } 124 | 125 | public static List fromJsonList(String source, Class objectClass) throws JsonParseException, JsonMappingException, IOException { 126 | List> list = HarvestCore.mapper.readValue(source, TypeFactory.fromType(new RESTParameterizedType(objectClass, true))); 127 | List result = new ArrayList(); 128 | for (Map map : list) { 129 | result.add(map.get(getObjectName(objectClass))); 130 | } 131 | return result; 132 | } 133 | 134 | public static List fromJsonList(InputStream source, Class objectClass) throws JsonParseException, JsonMappingException, IOException { 135 | List> list = HarvestCore.mapper.readValue(source, TypeFactory.fromType(new RESTParameterizedType(objectClass, true))); 136 | List result = new ArrayList(); 137 | for (Map map : list) { 138 | result.add(map.get(getObjectName(objectClass))); 139 | } 140 | return result; 141 | } 142 | 143 | 144 | /** 145 | * Make a post request serializing this class to json and sending data to specified uri 146 | * @param uri 147 | * @return 148 | * @throws ClientProtocolException 149 | * @throws IOException 150 | */ 151 | public AbstractSerializer add(String uri) throws ClientProtocolException, IOException { 152 | InputStream content = HarvestCore.post(uri, toJson()); 153 | return fromJson(content, getClass()); 154 | } 155 | 156 | /** 157 | * Make a POST request sending this class as JSON to classic plural uri. For example: 158 | * In Projects class project.add() will be send data to /projects. 159 | * @return 160 | * @throws ClientProtocolException 161 | * @throws IOException 162 | */ 163 | public AbstractSerializer add() throws ClientProtocolException, IOException { 164 | String uri = "/" + getClass().getSimpleName().toLowerCase() + "s"; 165 | return add(uri); 166 | } 167 | 168 | /** 169 | * Make a GET request getting a list of specified Class Objects to specified uri 170 | * @param 171 | * @param uri 172 | * @param objectClass 173 | * @return 174 | * @throws ClientProtocolException 175 | * @throws IOException 176 | * @throws ClassNotFoundException 177 | */ 178 | public static List list(String uri, Class objectClass) throws ClientProtocolException, IOException, ClassNotFoundException { 179 | InputStream content = HarvestCore.get(uri); 180 | return fromJsonList(content, objectClass); 181 | } 182 | 183 | /** 184 | * Make a GET request to classic plural uri based in the name of specified class object. 185 | * @param 186 | * @param objectClass 187 | * @return 188 | * @throws ClientProtocolException 189 | * @throws IOException 190 | * @throws ClassNotFoundException 191 | */ 192 | public static List list(Class objectClass) throws ClientProtocolException, IOException, ClassNotFoundException { 193 | String uri = "/" + objectClass.getSimpleName().toLowerCase() + "s"; 194 | return list(uri, objectClass); 195 | } 196 | 197 | /** 198 | * Make a GET request to specified uri 199 | * 200 | * @param 201 | * @param uri 202 | * @param id 203 | * @param childClass 204 | * @return 205 | * @throws JsonParseException 206 | * @throws JsonMappingException 207 | * @throws IOException 208 | * @throws ClassNotFoundException 209 | */ 210 | public static T get(String uri, long id, Class childClass) throws JsonParseException, JsonMappingException, IOException, ClassNotFoundException { 211 | InputStream content = HarvestCore.get(uri); 212 | return fromJson(content, childClass); 213 | } 214 | 215 | /** 216 | * 217 | *Makes a GET request an 218 | * @param 219 | * @param id 220 | * @param childClass 221 | * @return 222 | * @throws JsonParseException 223 | * @throws JsonMappingException 224 | * @throws IOException 225 | * @throws ClassNotFoundException 226 | */ 227 | public static T get(long id, Class childClass) throws JsonParseException, JsonMappingException, IOException, ClassNotFoundException { 228 | String uri = "/" + childClass.getSimpleName().toLowerCase() + "s/" +id; 229 | return get(uri, id, childClass); 230 | } 231 | 232 | public void delete(String uri) throws ClientProtocolException, IOException { 233 | HarvestCore.delete(uri); 234 | } 235 | 236 | public void delete() throws ClientProtocolException, IOException { 237 | String uri = "/" + getClass().getSimpleName().toLowerCase() + "s/" + getId(); 238 | delete(uri); 239 | } 240 | 241 | public AbstractSerializer update(String uri) throws JsonGenerationException, JsonMappingException, ClientProtocolException, IOException { 242 | InputStream content = HarvestCore.put(uri, toJson()); 243 | return fromJson(content, getClass()); 244 | } 245 | 246 | public AbstractSerializer update() throws JsonGenerationException, JsonMappingException, ClientProtocolException, IOException { 247 | String uri = "/" + getClass().getSimpleName().toLowerCase() + "s/" + getId(); 248 | return update(uri); 249 | } 250 | 251 | 252 | 253 | } 254 | -------------------------------------------------------------------------------- /src/cl/continuum/harvest/Project.java: -------------------------------------------------------------------------------- 1 | package cl.continuum.harvest; 2 | 3 | import java.io.IOException; 4 | import java.io.Serializable; 5 | import java.util.List; 6 | import java.util.Scanner; 7 | import java.util.regex.Pattern; 8 | 9 | import org.apache.http.client.ClientProtocolException; 10 | import org.codehaus.jackson.JsonParseException; 11 | import org.codehaus.jackson.map.JsonMappingException; 12 | 13 | public class Project extends AbstractSerializer implements Serializable { 14 | 15 | /** 16 | * 17 | */ 18 | private static final long serialVersionUID = 8750681525704516781L; 19 | private String over_budget_notified_at; 20 | private String name; 21 | private boolean show_budget_to_all; 22 | private String earliest_record_at; 23 | private boolean billable; 24 | private String code; 25 | private boolean notify_when_over_budget; 26 | private String cost_budget; 27 | private String latest_record_at; 28 | private String highrise_deal_id; 29 | private String fees; 30 | private boolean cost_budget_include_expenses; 31 | private String hourly_rate; 32 | private String client_id; 33 | private String client; 34 | private String bill_by; 35 | private int active_user_assignments_count; 36 | private float over_budget_notification_percentage; 37 | private String budget; 38 | private String budget_by; 39 | private String estimate_by; 40 | private String basecamp_id; 41 | private int active_task_assignments_count; 42 | private String hint_latest_record_at; 43 | private boolean active; 44 | private String notes; 45 | private List tasks; 46 | 47 | 48 | public String getOver_budget_notified_at() { 49 | return over_budget_notified_at; 50 | } 51 | 52 | 53 | public void setOver_budget_notified_at(String overBudgetNotifiedAt) { 54 | over_budget_notified_at = overBudgetNotifiedAt; 55 | } 56 | 57 | 58 | public String getName() { 59 | return name; 60 | } 61 | 62 | 63 | public void setName(String name) { 64 | this.name = name; 65 | } 66 | 67 | 68 | public boolean isShow_budget_to_all() { 69 | return show_budget_to_all; 70 | } 71 | 72 | 73 | public void setShow_budget_to_all(boolean showBudgetToAll) { 74 | show_budget_to_all = showBudgetToAll; 75 | } 76 | 77 | 78 | public String getEarliest_record_at() { 79 | return earliest_record_at; 80 | } 81 | 82 | 83 | public void setEarliest_record_at(String earliestRecordAt) { 84 | earliest_record_at = earliestRecordAt; 85 | } 86 | 87 | 88 | public boolean isBillable() { 89 | return billable; 90 | } 91 | 92 | 93 | public void setBillable(boolean billable) { 94 | this.billable = billable; 95 | } 96 | 97 | 98 | public String getCode() { 99 | return code; 100 | } 101 | 102 | 103 | public void setCode(String code) { 104 | this.code = code; 105 | } 106 | 107 | 108 | public boolean isNotify_when_over_budget() { 109 | return notify_when_over_budget; 110 | } 111 | 112 | 113 | public void setNotify_when_over_budget(boolean notifyWhenOverBudget) { 114 | notify_when_over_budget = notifyWhenOverBudget; 115 | } 116 | 117 | 118 | public String getCost_budget() { 119 | return cost_budget; 120 | } 121 | 122 | 123 | public void setCost_budget(String costBudget) { 124 | cost_budget = costBudget; 125 | } 126 | 127 | 128 | public String getLatest_record_at() { 129 | return latest_record_at; 130 | } 131 | 132 | 133 | public void setLatest_record_at(String latestRecordAt) { 134 | latest_record_at = latestRecordAt; 135 | } 136 | 137 | 138 | public String getHighrise_deal_id() { 139 | return highrise_deal_id; 140 | } 141 | 142 | 143 | public void setHighrise_deal_id(String highriseDealId) { 144 | highrise_deal_id = highriseDealId; 145 | } 146 | 147 | 148 | public String getFees() { 149 | return fees; 150 | } 151 | 152 | 153 | public void setFees(String fees) { 154 | this.fees = fees; 155 | } 156 | 157 | 158 | public boolean isCost_budget_include_expenses() { 159 | return cost_budget_include_expenses; 160 | } 161 | 162 | 163 | public void setCost_budget_include_expenses(boolean costBudgetIncludeExpenses) { 164 | cost_budget_include_expenses = costBudgetIncludeExpenses; 165 | } 166 | 167 | 168 | public String getHourly_rate() { 169 | return hourly_rate; 170 | } 171 | 172 | 173 | public void setHourly_rate(String hourlyRate) { 174 | hourly_rate = hourlyRate; 175 | } 176 | 177 | 178 | public String getClient_id() { 179 | return client_id; 180 | } 181 | 182 | 183 | public void setClient_id(String clientId) { 184 | client_id = clientId; 185 | } 186 | 187 | 188 | public String getBill_by() { 189 | return bill_by; 190 | } 191 | 192 | 193 | public void setBill_by(String billBy) { 194 | bill_by = billBy; 195 | } 196 | 197 | 198 | public int getActive_user_assignments_count() { 199 | return active_user_assignments_count; 200 | } 201 | 202 | 203 | public void setActive_user_assignments_count(int activeUserAssignmentsCount) { 204 | active_user_assignments_count = activeUserAssignmentsCount; 205 | } 206 | 207 | 208 | public float getOver_budget_notification_percentage() { 209 | return over_budget_notification_percentage; 210 | } 211 | 212 | 213 | public void setOver_budget_notification_percentage( 214 | float overBudgetNotificationPercentage) { 215 | over_budget_notification_percentage = overBudgetNotificationPercentage; 216 | } 217 | 218 | 219 | public String getBudget() { 220 | return budget; 221 | } 222 | 223 | 224 | public void setBudget(String budget) { 225 | this.budget = budget; 226 | } 227 | 228 | 229 | public String getBudget_by() { 230 | return budget_by; 231 | } 232 | 233 | 234 | public void setBudget_by(String budgetBy) { 235 | budget_by = budgetBy; 236 | } 237 | 238 | 239 | public String getEstimate_by() { 240 | return estimate_by; 241 | } 242 | 243 | 244 | public void setEstimate_by(String estimate_by) { 245 | this.estimate_by = estimate_by; 246 | } 247 | 248 | 249 | public String getBasecamp_id() { 250 | return basecamp_id; 251 | } 252 | 253 | 254 | public void setBasecamp_id(String basecampId) { 255 | basecamp_id = basecampId; 256 | } 257 | 258 | 259 | public int getActive_task_assignments_count() { 260 | return active_task_assignments_count; 261 | } 262 | 263 | 264 | public void setActive_task_assignments_count(int activeTaskAssignmentsCount) { 265 | active_task_assignments_count = activeTaskAssignmentsCount; 266 | } 267 | 268 | 269 | public String getHint_latest_record_at() { 270 | return hint_latest_record_at; 271 | } 272 | 273 | 274 | public void setHint_latest_record_at(String hint_latest_record_at) { 275 | this.hint_latest_record_at = hint_latest_record_at; 276 | } 277 | 278 | 279 | public boolean isActive() { 280 | return active; 281 | } 282 | 283 | 284 | public void setActive(boolean active) { 285 | this.active = active; 286 | } 287 | 288 | 289 | public String getNotes() { 290 | return notes; 291 | } 292 | 293 | 294 | public void setNotes(String notes) { 295 | this.notes = notes; 296 | } 297 | 298 | public String toString() { 299 | return "[PROJECT] " + name + " ID: " + getId(); 300 | } 301 | 302 | 303 | public static List list() throws ClientProtocolException, IOException, ClassNotFoundException { 304 | return list(Project.class); 305 | } 306 | 307 | public static Project get(int id) throws JsonParseException, JsonMappingException, IOException, ClassNotFoundException { 308 | return get(id, Project.class); 309 | } 310 | 311 | 312 | 313 | public List getTasks() { 314 | return tasks; 315 | } 316 | 317 | 318 | public void setTasks(List tasks) { 319 | this.tasks = tasks; 320 | } 321 | 322 | 323 | public String getClient() { 324 | return client; 325 | } 326 | 327 | 328 | public void setClient(String client) { 329 | this.client = client; 330 | } 331 | 332 | public static Project select() throws IOException, ClassNotFoundException { 333 | List projects = list(); 334 | return select(projects); 335 | 336 | } 337 | 338 | 339 | public static Project select(List projects) throws IOException, ClassNotFoundException { 340 | System.out.println("Select project"); 341 | System.out.println("======================="); 342 | System.out.println("\tq: Quit"); 343 | System.out.println("\t0: None"); 344 | for (int i=0; i < projects.size(); i++) { 345 | Project project = projects.get(i); 346 | System.out.println("\t" + (i+1) + ": " + project.getName()); 347 | } 348 | Scanner scan = new Scanner(System.in); 349 | String option = ""; 350 | while (true) { 351 | System.out.print("\nChoose project number:"); 352 | option = scan.next(); 353 | if (option.equalsIgnoreCase("q")) { 354 | System.out.println("Good bye!"); 355 | System.exit(0); 356 | } 357 | if (!Pattern.matches("[0-9]+", option)) { 358 | System.out.println("You must enter a number"); 359 | continue; 360 | } 361 | int index = Integer.parseInt(option); 362 | if (index < 0 || index > projects.size()) { 363 | System.out.println("Option out of range"); 364 | continue; 365 | } 366 | if (index == 0) 367 | return null; 368 | return projects.get(index - 1); 369 | } 370 | } 371 | 372 | public static void main(String[] args) throws IOException, ClassNotFoundException { 373 | Project project = select(); 374 | System.out.println(project.getName()); 375 | } 376 | } 377 | --------------------------------------------------------------------------------