Refactoring the Java classes

The classes generated by the reverse engineering process can be improved upon with a little refactoring to make the code more readable and easier to understand. Some of the autogenerated properties and fields have id in their name when we are actually referring to classes, while the collection of java.util.List objects have list in their name. Let's start with the Company.java file.

The Company.java file

This file represents the Company entity. Double-click on the file to open it in the editor and browse through the contents. This class is a simple POJO with set and get methods for each property in addition to the standard hashCode, equals, and toString methods. The class has a no-arg constructor (required by the JPA specification as domain objects must be created dynamically without any properties), a second constructor that takes only the primary key, and a full (all arguments) constructor. We will make the code more readable by making a few minor changes to the Company.java file.

The first change is to rename the field projectList to projects everywhere in the file. This can be easily achieved by selecting the projectList field, and then selecting Refactor | Rename from the menu:

The Company.java file

You can now change the field name to projects. Make sure that you also select the Rename Getters and Setters option before clicking on the Refactor button.

The Company.java file

Making these changes will change the field name and generate new get and set methods for the projects field.

The final change for the Company.java file is renaming the mappedBy property from idCompany to company. The appropriate lines should now look like the following code:

@OneToMany(cascade = CascadeType.ALL, mappedBy = "company")
private List<Project> projects;

The final refactored Company.java file should now look like the following code snippet:

package com.gieman.tttracker.domain;

import java.io.Serializable;
import java.util.List;
import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

@Entity
@Table(name = "ttt_company")
@NamedQueries({
    @NamedQuery(name = "Company.findAll", query = "SELECT c FROM Company c"),
    @NamedQuery(name = "Company.findByIdCompany", query = "SELECT c FROM Company c WHERE c.idCompany = :idCompany"),
    @NamedQuery(name = "Company.findByCompanyName", query = "SELECT c FROM Company c WHERE c.companyName = :companyName")})
public class Company implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    @Column(name = "id_company")
    private Integer idCompany;
    @Basic(optional = false)
    @NotNull
    @Size(min = 1, max = 200)
    @Column(name = "company_name")
    private String companyName;
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "company")
    private List<Project> projects;

    public Company() {
    }

    public Company(Integer idCompany) {
        this.idCompany = idCompany;
    }

    public Company(Integer idCompany, String companyName) {
        this.idCompany = idCompany;
        this.companyName = companyName;
    }

    public Integer getIdCompany() {
        return idCompany;
    }

    public void setIdCompany(Integer idCompany) {
        this.idCompany = idCompany;
    }

    public String getCompanyName() {
        return companyName;
    }

    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    public List<Project> getProjects() {
        return projects;
    }

    public void setProjects(List<Project> projects) {
        this.projects = projects;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (idCompany != null ? idCompany.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        if (!(object instanceof Company)) {
            return false;
        }
        Company other = (Company) object;
        if ((this.idCompany == null && other.idCompany != null) || (this.idCompany != null && !this.idCompany.equals(other.idCompany))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "com.gieman.tttracker.domain.Company[ idCompany=" + idCompany + " ]";
    }
    
}

JPA uses the convention-over-configuration concept to simplify the configuration of entities. This is achieved by using annotations with sensible defaults to keep the entity definitions lean. Now, let's look at the key JPA annotations in this file.

The @Entity annotation

This is a marker annotation that indicates to the JPA persistence provider that the Company class is an entity. JPA scans for the @Entity annotations when exclude-unlisted-classes is set to false in the persistence.xml file. Without the @Entity annotation, the persistence engine will ignore the class.

The @Table annotation

The @Table annotation defines the underlying database table that is represented by this entity class. The @Table(name = "ttt_company") line tells the persistence provider that the Company class represents the ttt_company table. Only one table annotation can be defined in any entity class.

The @Id annotation

The @Id annotation defines the primary key field in the class and is required for each entity. The persistence provider will throw an exception if the @Id annotation is not present. The Company class property representing the primary key in the ttt_company table is the Integer idCompany field. There are three additional annotations attached to this field, of which the following annotation is specific to primary keys.

The @GeneratedValue annotation

This annotation identifies how the persistence engine should generate new primary key values for the insertion of records into the table. The strategy=GenerationType.IDENTITY line will use the MySQL autoincrement strategy in the background to insert records into the ttt_company table. Different databases may require different strategies. For example, an Oracle database table could use a sequence as the basis for primary key generation by defining the following generator annotations:

@GeneratedValue(generator="gen_seq_company")
@SequenceGenerator(name="gen_seq_company", sequenceName="seq_id_company")

Note

The primary key generation is independent of the class itself. The persistence engine will handle the generation of the primary key for you as defined by the generation strategy.

The @Basic annotation

This is an optional annotation that is used to identify the nullability of the field. The @Basic(optional = false) line is used to specify that the field is not optional (may not be null). Likewise, the @Basic(optional = true) line could be used for other fields that may be nullable.

The @Column annotation

This annotation specifies the column to which the field is mapped. The @Column(name = "id_company") line will, hence, map the id_company column in the ttt_company table to the idCompany field in the class.

The @NotNull and @Size annotations

These annotations are part of the javax.validation.constraints package (the Bean Validation package was introduced in Java EE 6) and define that the field cannot be null as well as the minimum and maximum sizes for the field. The company_name column in the ttt_company table was defined as varchar(200) not null, which is the reason why these annotations were created during the reverse engineering process.

The @OneToMany annotation

A Company class may have zero or more Projects entities. This relationship is defined by the @OneToMany annotation. In words, we can describe this relationship as One Company can have Many Projects. In JPA, an entity is associated with a collection of other entities by defining this annotation with a mappedBy property. We have refactored the original mappedBy value to company. This will be the name of the field in the Project.java file after we have refactored the Project file in the next section.

The @NamedQueries annotation

The @NamedQueries annotations deserve an explanation in their own right. We will look at these in detail later.

The Projects.java file

As you may have guessed by now, this file represents the Project entity and maps to the ttt_project table. Double-click on the file to open it in the editor and browse the contents. We will once again do a bit of refactoring to clarify the autogenerated fields:

  • Rename the autogenerated idCompany field to company using the refactoring process. Don't forget to rename the get and set methods.
  • Rename the autogenerated taskList field to tasks. Don't forget the get and set methods again!
  • Rename the mappedBy value from idProject to project.

The final refactored file should now look like the following code:

package com.gieman.tttracker.domain;

import java.io.Serializable;
import java.util.List;
import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

@Entity
@Table(name = "ttt_project")
@NamedQueries({
    @NamedQuery(name = "Project.findAll", query = "SELECT p FROM Project p"),
    @NamedQuery(name = "Project.findByIdProject", query = "SELECT p FROM Project p WHERE p.idProject = :idProject"),
    @NamedQuery(name = "Project.findByProjectName", query = "SELECT p FROM Project p WHERE p.projectName = :projectName")})
public class Project implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    @Column(name = "id_project")
    private Integer idProject;
    @Basic(optional = false)
    @NotNull
    @Size(min = 1, max = 200)
    @Column(name = "project_name")
    private String projectName;
    @JoinColumn(name = "id_company", referencedColumnName = "id_company")
    @ManyToOne(optional = false)
    private Company company;
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "project")
    private List<Task> tasks;

    public Project() {
    }
    public Project(Integer idProject) {
        this.idProject = idProject;
    }

    public Project(Integer idProject, String projectName) {
        this.idProject = idProject;
        this.projectName = projectName;
    }

    public Integer getIdProject() {
        return idProject;
    }

    public void setIdProject(Integer idProject) {
        this.idProject = idProject;
    }

    public String getProjectName() {
        return projectName;
    }

    public void setProjectName(String projectName) {
        this.projectName = projectName;
    }

    public Company getCompany() {
        return company;
    }

    public void setCompany(Company company) {
        this.company = company;
    }

    public List<Task> getTasks() {
        return tasks;
    }

    public void setTasks(List<Task> tasks) {
        this.tasks = tasks;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (idProject != null ? idProject.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        if (!(object instanceof Project)) {
            return false;
        }
        Project other = (Project) object;
        if ((this.idProject == null && other.idProject != null) || (this.idProject != null && !this.idProject.equals(other.idProject))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "com.gieman.tttracker.domain.Project[ idProject=" + idProject + " ]";
    }
    
}

The @ManyToOne annotation

This annotation represents a relationship between entities; it is the reverse of the @OneToMany annotation. For the Project entity, we can say that Many Projects have One Company. In other words, a Project entity belongs to a single Company class, and (inversely) a Company class can have any number of Projects entities. This relationship is defined at the database level (that is, the foreign key relationship in the underlying tables) and is achieved in the @JoinColumn annotation:

@JoinColumn(name = "id_company", referencedColumnName = "id_company")

The name property defines the name of the column in the ttt_project table that is the foreign key to the referencedColumnName column in the ttt_company table.

Bidirectional mapping and owning entities

It is essential to grasp the very important concept of how one entity is related to another through the @ManyToOne and @OneToMany annotations. The Company class has a list of mapped Projects entities defined as follows:

  @OneToMany(cascade = CascadeType.ALL, mappedBy = "company")
  private List<Project> projects;

Whereas, the Project class has exactly one mapped Company entity:

  @JoinColumn(name="id_company", referencedColumnName="id_company")
  @ManyToOne(optional=false)
  private Company company;

This is known as bidirectional mapping, one mapping on each class for each direction. A many-to-one mapping back to the source, as in the Project entity back to the Company entity, implies a corresponding one-to-many mapping on the source (Company) back to the target (Project). The terms source and target can be defined as follows:

  • Source: This is an entity that can exist in a relationship in its own right. The source entity does not require the target entity to exist and the @OneToMany collection can be empty. In our example, a Company entity can exist without a Project entity.
  • Target: This is an entity that cannot exist on its own without a reference to a valid source. The @ManyToOne entity defined on the target cannot be null. A Project entity cannot exist in our design without a valid Company entity.

The owning entity is an entity that understands the other entity from a database perspective. In simple terms, the owning entity has the @JoinColumn definition describing the underlying columns that form the relationship. In the Company-Project relationship, Project is the owning entity. Note that an entity can be both a target as well as a source as shown in the following Project.java file snippet:

  @OneToMany(cascade = CascadeType.ALL, mappedBy = "project")
  private List<Task> tasks;

Here, Project is the source for the Task entity relationship and we would expect a reverse @ManyToOne annotation on the Task class. This is exactly what we will find.

The Task.java file

This file defines the Task entity that represents the ttt_task table. Open the file and perform the following refactoring:

  • Delete the autogenerated taskLogList field and also delete the associated get and set methods. Why do we do this? There may be many millions of task logs in the system for each Task instance and it is not advisable to hold a reference to such a large set of TaskLog instances within the Task object.
  • Rename the autogenerated idProject field to project. Don't forget to delete the get and set methods again.

After making the preceding changes, you will see that some of the imports are no longer required and are highlighted by the NetBeans IDE:

The Task.java file

The keyboard combination of Ctrl + Shift + I will remove all the unused imports. Another alternative is to click on the icon, shown in the following screenshot, to open the menu and select a Remove option:

The Task.java file

It is good practice to have clean code and removing the unused imports is a simple process.

The final refactored file should now look like the following code snippet:

package com.gieman.tttracker.domain;

import java.io.Serializable;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

@Entity
@Table(name = "ttt_task")
@NamedQueries({
    @NamedQuery(name = "Task.findAll", query = "SELECT t FROM Task t"),
    @NamedQuery(name = "Task.findByIdTask", query = "SELECT t FROM Task t WHERE t.idTask = :idTask"),
    @NamedQuery(name = "Task.findByTaskName", query = "SELECT t FROM Task t WHERE t.taskName = :taskName")})
public class Task implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    @Column(name = "id_task")
    private Integer idTask;
    @Basic(optional = false)
    @NotNull
    @Size(min = 1, max = 200)
    @Column(name = "task_name")
    private String taskName;
    @JoinColumn(name = "id_project", referencedColumnName = "id_project")
    @ManyToOne(optional = false)
    private Project project;

    public Task() {
    }

    public Task(Integer idTask) {
        this.idTask = idTask;
    }

    public Task(Integer idTask, String taskName) {
        this.idTask = idTask;
        this.taskName = taskName;
    }

    public Integer getIdTask() {
        return idTask;
    }

    public void setIdTask(Integer idTask) {
        this.idTask = idTask;
    }

    public String getTaskName() {
        return taskName;
    }

    public void setTaskName(String taskName) {
        this.taskName = taskName;
    }

    public Project getProject() {
        return project;
    }

    public void setProject(Project project) {
        this.project = project;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (idTask != null ? idTask.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        if (!(object instanceof Task)) {
            return false;
        }
        Task other = (Task) object;
        if ((this.idTask == null && other.idTask != null) || (this.idTask != null && !this.idTask.equals(other.idTask))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "com.gieman.tttracker.domain.Task[ idTask=" + idTask + " ]";
    }    
}

Note the @ManyToOne annotation referencing the Project class using the @JoinColumn definition. The Task object owns this relationship.

The User.java file

The User entity represents the underlying ttt_user table. The generated class has a @OneToMany definition for the relationship to the TaskLog class:

  @OneToMany(cascade = CascadeType.ALL, mappedBy = "username")
  private List<TaskLog> taskLogList;

Refactoring in this file will once again delete this relationship completely. As noted in the Tasks.java section, a User entity may also have many thousands of task logs. By understanding the application's requirements and data structure, it is often far cleaner to remove unnecessary relationships completely.

You will also note that the @Pattern annotation is commented out by default during the reverse engineering process. The email field name indicated to NetBeans that this might be an e-mail field and NetBeans added the annotation for use if required. We will uncomment this annotation to enable e-mail pattern checking for the field and add the required import:

import javax.validation.constraints.Pattern;

The refactored User.java file will now look like the following code snippet:

package com.gieman.tttracker.domain;

import java.io.Serializable;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

@Entity
@Table(name = "ttt_user")
@NamedQueries({
    @NamedQuery(name = "User.findAll", query = "SELECT u FROM User u"),
    @NamedQuery(name = "User.findByUsername", query = "SELECT u FROM User u WHERE u.username = :username"),
    @NamedQuery(name = "User.findByFirstName", query = "SELECT u FROM User u WHERE u.firstName = :firstName"),
    @NamedQuery(name = "User.findByLastName", query = "SELECT u FROM User u WHERE u.lastName = :lastName"),
    @NamedQuery(name = "User.findByEmail", query = "SELECT u FROM User u WHERE u.email = :email"),
    @NamedQuery(name = "User.findByPassword", query = "SELECT u FROM User u WHERE u.password = :password"),
    @NamedQuery(name = "User.findByAdminRole", query = "SELECT u FROM User u WHERE u.adminRole = :adminRole")})
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @Basic(optional = false)
    @NotNull
    @Size(min = 1, max = 10)
    @Column(name = "username")
    private String username;
    @Basic(optional = false)
    @NotNull
    @Size(min = 1, max = 100)
    @Column(name = "first_name")
    private String firstName;
    @Basic(optional = false)
    @NotNull
    @Size(min = 1, max = 100)
    @Column(name = "last_name")
    private String lastName;
    @Pattern(regexp="[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?", message="Invalid email")
    @Basic(optional = false)
    @NotNull
    @Size(min = 1, max = 100)
    @Column(name = "email")
    private String email;
    @Basic(optional = false)
    @NotNull
    @Size(min = 1, max = 100)
    @Column(name = "password")
    private String password;
    @Column(name = "admin_role")
    private Character adminRole;

    public User() {
    }

    public User(String username) {
        this.username = username;
    }

    public User(String username, String firstName, String lastName, String email, String password) {
        this.username = username;
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Character getAdminRole() {
        return adminRole;
    }

    public void setAdminRole(Character adminRole) {
        this.adminRole = adminRole;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (username != null ? username.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
         if (!(object instanceof User)) {
            return false;
        }
        User other = (User) object;
        if ((this.username == null && other.username != null) || (this.username != null && !this.username.equals(other.username))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "com.gieman.tttracker.domain.User[ username=" + username + " ]";
    }   
}

The TaskLog.java file

The final entity in our application represents the ttt_task_log table. The refactoring required here is to rename the idTask field to task (remember to also rename the get and set methods) and then rename the username field to user. The file should now look like the following code snippet:

package com.tttracker.domain;

import java.io.Serializable;
import java.util.Date;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

@Entity
@Table(name = "ttt_task_log")
@NamedQueries({
  @NamedQuery(name = "TaskLog.findAll", query = "SELECT t FROM TaskLog t"),
  @NamedQuery(name = "TaskLog.findByIdTaskLog", query = "SELECT t FROM TaskLog t WHERE t.idTaskLog = :idTaskLog"),
  @NamedQuery(name = "TaskLog.findByTaskDescription", query = "SELECT t FROM TaskLog t WHERE t.taskDescription = :taskDescription"),
  @NamedQuery(name = "TaskLog.findByTaskLogDate", query = "SELECT t FROM TaskLog t WHERE t.taskLogDate = :taskLogDate"),
  @NamedQuery(name = "TaskLog.findByTaskMinutes", query = "SELECT t FROM TaskLog t WHERE t.taskMinutes = :taskMinutes")})
public class TaskLog implements Serializable {
  private static final long serialVersionUID = 1L;
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Basic(optional = false)
  @Column(name = "id_task_log")
  private Integer idTaskLog;
  @Basic(optional = false)
  @NotNull
  @Size(min = 1, max = 2000)
  @Column(name = "task_description")
  private String taskDescription;
  @Basic(optional = false)
  @NotNull
  @Column(name = "task_log_date")
  @Temporal(TemporalType.DATE)
  private Date taskLogDate;
  @Basic(optional = false)
  @NotNull
  @Column(name = "task_minutes")
  private int taskMinutes;
  @JoinColumn(name = "username", referencedColumnName = "username")
  @ManyToOne(optional = false)
  private User user;
  @JoinColumn(name = "id_task", referencedColumnName = "id_task")
  @ManyToOne(optional = false)
  private Task task;

  public TaskLog() {
  }

  public TaskLog(Integer idTaskLog) {
    this.idTaskLog = idTaskLog;
  }

  public TaskLog(Integer idTaskLog, String taskDescription, Date taskLogDate, int taskMinutes) {
    this.idTaskLog = idTaskLog;
    this.taskDescription = taskDescription;
    this.taskLogDate = taskLogDate;
    this.taskMinutes = taskMinutes;
  }

  public Integer getIdTaskLog() {
    return idTaskLog;
  }

  public void setIdTaskLog(Integer idTaskLog) {
    this.idTaskLog = idTaskLog;
  }

  public String getTaskDescription() {
    return taskDescription;
  }

  public void setTaskDescription(String taskDescription) {
    this.taskDescription = taskDescription;
  }

  public Date getTaskLogDate() {
    return taskLogDate;
  }

  public void setTaskLogDate(Date taskLogDate) {
    this.taskLogDate = taskLogDate;
  }

  public int getTaskMinutes() {
    return taskMinutes;
  }

  public void setTaskMinutes(int taskMinutes) {
    this.taskMinutes = taskMinutes;
  }

  public User getUser() {
    return user;
  }

  public void setUser(User user) {
    this.user = user;
  }

  public Task getTask() {
    return task;
  }

  public void setTask(Task task) {
    this.task = task;
  }

  @Override
  public int hashCode() {
    int hash = 0;
    hash += (idTaskLog != null ? idTaskLog.hashCode() : 0);
    return hash;
  }

  @Override
  public boolean equals(Object object) {
    if (!(object instanceof TaskLog)) {
      return false;
    }
    TaskLog other = (TaskLog) object;
    if ((this.idTaskLog == null && other.idTaskLog != null) || (this.idTaskLog != null && !this.idTaskLog.equals(other.idTaskLog))) {
      return false;
    }
    return true;
  }

  @Override
  public String toString() {
    return "com.tttracker.domain.TaskLog[ idTaskLog=" + idTaskLog + " ]";
  }
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.245.1