diff --git a/rest-api/src/main/java/lcsb/mapviewer/api/BaseController.java b/rest-api/src/main/java/lcsb/mapviewer/api/BaseController.java index 46aeb77d0cf57a95f8004b3b547d58bd65d4dce8..2a845a6ed6ac844fa77ca077e6126be329b441e1 100644 --- a/rest-api/src/main/java/lcsb/mapviewer/api/BaseController.java +++ b/rest-api/src/main/java/lcsb/mapviewer/api/BaseController.java @@ -1,6 +1,9 @@ package lcsb.mapviewer.api; import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.HashMap; import java.util.Map; import org.apache.log4j.Logger; @@ -17,6 +20,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import lcsb.mapviewer.common.Configuration; +import lcsb.mapviewer.common.exception.InvalidStateException; public abstract class BaseController { private Logger logger = Logger.getLogger(BaseController.class); @@ -29,6 +33,9 @@ public abstract class BaseController { return new ResponseEntity<Object>("{\"error\" : \"Access denied.\",\"reason\":\"" + e.getMessage() + "\"}", new HttpHeaders(), HttpStatus.FORBIDDEN); } else if (e instanceof ObjectNotFoundException) { return new ResponseEntity<Object>("{\"error\" : \"Object not found.\",\"reason\":\"" + e.getMessage() + "\"}", new HttpHeaders(), HttpStatus.NOT_FOUND); + } else if (e instanceof ObjectExistsException) { + return new ResponseEntity<Object>( + "{\"error\" : \"Object already exists.\",\"reason\":\"" + e.getMessage() + "\"}", new HttpHeaders(), HttpStatus.CONFLICT); } else if (e instanceof QueryException) { logger.error(e, e); return new ResponseEntity<Object>( @@ -52,4 +59,27 @@ public abstract class BaseController { return (Map<String, Object>) node.get(objectName); } + protected Map<String, String> parsePostBody(String body) { + Map<String, String> result = new HashMap<>(); + String[] parameters = body.split("&"); + for (String string : parameters) { + int position = string.indexOf("="); + if (position < 0) { + position = string.length(); + } + String key = string.substring(0, position); + String value = null; + if (position < string.length()) { + value = string.substring(position + 1, string.length()); + try { + value = URLDecoder.decode(value, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new InvalidStateException("Cannot decode input", e); + } + } + result.put(key, value); + } + return result; + } + } diff --git a/rest-api/src/main/java/lcsb/mapviewer/api/BaseRestImpl.java b/rest-api/src/main/java/lcsb/mapviewer/api/BaseRestImpl.java index ac78d8b8d3665ee9c2839b91150ed323cc98d978..9918dfa4cfdfe63d7387a30621efa46f51058987 100644 --- a/rest-api/src/main/java/lcsb/mapviewer/api/BaseRestImpl.java +++ b/rest-api/src/main/java/lcsb/mapviewer/api/BaseRestImpl.java @@ -18,6 +18,9 @@ import lcsb.mapviewer.annotation.services.PubmedParser; import lcsb.mapviewer.annotation.services.PubmedSearchException; import lcsb.mapviewer.common.exception.InvalidArgumentException; import lcsb.mapviewer.common.exception.InvalidStateException; +import lcsb.mapviewer.converter.IConverter; +import lcsb.mapviewer.converter.model.celldesigner.CellDesignerXmlParser; +import lcsb.mapviewer.converter.model.sbgnml.SbgnmlXmlConverter; import lcsb.mapviewer.model.map.BioEntity; import lcsb.mapviewer.model.map.MiriamData; import lcsb.mapviewer.model.map.MiriamType; @@ -282,4 +285,16 @@ public abstract class BaseRestImpl { this.projectService = projectService; } + protected IConverter getModelParser(String handlerClass) throws QueryException { + IConverter parser; + if (SbgnmlXmlConverter.class.getCanonicalName().equals(handlerClass)) { + parser = new SbgnmlXmlConverter(); + } else if (CellDesignerXmlParser.class.getCanonicalName().equals(handlerClass)) { + parser = new CellDesignerXmlParser(); + } else { + throw new QueryException("Unknown handlerClass: " + handlerClass); + } + return parser; + } + } diff --git a/rest-api/src/main/java/lcsb/mapviewer/api/ObjectExistsException.java b/rest-api/src/main/java/lcsb/mapviewer/api/ObjectExistsException.java new file mode 100644 index 0000000000000000000000000000000000000000..8528592e8446c187314b6853444dd3bca881e5e5 --- /dev/null +++ b/rest-api/src/main/java/lcsb/mapviewer/api/ObjectExistsException.java @@ -0,0 +1,38 @@ +package lcsb.mapviewer.api; + +/** + * Thrown when object cannot be found via API. + * + * @author Piotr Gawron + * + */ +public class ObjectExistsException extends QueryException { + + /** + * + */ + private static final long serialVersionUID = 1L; + + /** + * Default constructor. + * + * @param message + * error message + */ + public ObjectExistsException(String message) { + super(message); + } + + /** + * Constructor with error message and parent exception. + * + * @param message + * error message + * @param reason + * parent exception that caused this one + */ + public ObjectExistsException(String message, Exception reason) { + super(message, reason); + } + +} diff --git a/rest-api/src/main/java/lcsb/mapviewer/api/projects/ProjectController.java b/rest-api/src/main/java/lcsb/mapviewer/api/projects/ProjectController.java index 0f5e9a63fadc3198bc62cc848d100718f8b521a3..eb4bfd9d6c459b78289347f4d1565c5132e84e44 100644 --- a/rest-api/src/main/java/lcsb/mapviewer/api/projects/ProjectController.java +++ b/rest-api/src/main/java/lcsb/mapviewer/api/projects/ProjectController.java @@ -4,9 +4,13 @@ import java.io.IOException; import java.util.List; import java.util.Map; +import javax.servlet.ServletContext; + +import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; @@ -29,9 +33,13 @@ import lcsb.mapviewer.services.SecurityException; @RestController public class ProjectController extends BaseController { - + private Logger logger = Logger.getLogger(ProjectController.class); + + @Autowired + private ServletContext context; + @Autowired - private ProjectRestImpl projectController; + private ProjectRestImpl projectController; @RequestMapping(value = "/projects/{projectId:.+}", method = { RequestMethod.GET }, produces = { MediaType.APPLICATION_JSON_VALUE }) public ProjectMetaData getProject(// @@ -53,6 +61,16 @@ public class ProjectController extends BaseController { } + @RequestMapping(value = "/projects/{projectId:.+}", method = { RequestMethod.POST }, produces = { MediaType.APPLICATION_JSON_VALUE }) + public ProjectMetaData addProject(// + @RequestBody MultiValueMap<String,String> formData, // + @PathVariable(value = "projectId") String projectId, // + @CookieValue(value = Configuration.AUTH_TOKEN) String token // + ) throws SecurityException, IOException, QueryException { + return projectController.addProject(token, projectId, formData, context.getRealPath("/")); + + } + @RequestMapping(value = "/projects/", method = { RequestMethod.GET }, produces = { MediaType.APPLICATION_JSON_VALUE }) public List<ProjectMetaData> getProjects(// @CookieValue(value = Configuration.AUTH_TOKEN) String token // @@ -127,4 +145,20 @@ public class ProjectController extends BaseController { .body(file.getFileContent()); } + /** + * @return the context + * @see #context + */ + public ServletContext getContext() { + return context; + } + + /** + * @param context the context to set + * @see #context + */ + public void setContext(ServletContext context) { + this.context = context; + } + } \ No newline at end of file diff --git a/rest-api/src/main/java/lcsb/mapviewer/api/projects/ProjectRestImpl.java b/rest-api/src/main/java/lcsb/mapviewer/api/projects/ProjectRestImpl.java index fd0e399b6305c9b16ae808e836035092b4199dac..265fb97e790f66baddc16439b39cd94b4e72a9fd 100644 --- a/rest-api/src/main/java/lcsb/mapviewer/api/projects/ProjectRestImpl.java +++ b/rest-api/src/main/java/lcsb/mapviewer/api/projects/ProjectRestImpl.java @@ -2,10 +2,14 @@ package lcsb.mapviewer.api.projects; import java.awt.geom.Path2D; import java.awt.geom.PathIterator; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -18,8 +22,10 @@ import org.apache.commons.io.IOUtils; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.MultiValueMap; import lcsb.mapviewer.api.BaseRestImpl; +import lcsb.mapviewer.api.ObjectExistsException; import lcsb.mapviewer.api.ObjectNotFoundException; import lcsb.mapviewer.api.QueryException; import lcsb.mapviewer.api.projects.models.publications.PublicationsRestImpl; @@ -37,8 +43,6 @@ import lcsb.mapviewer.converter.IConverter; import lcsb.mapviewer.converter.graphics.AbstractImageGenerator.Params; import lcsb.mapviewer.converter.graphics.DrawingException; import lcsb.mapviewer.converter.graphics.ImageGenerators; -import lcsb.mapviewer.converter.model.celldesigner.CellDesignerXmlParser; -import lcsb.mapviewer.converter.model.sbgnml.SbgnmlXmlConverter; import lcsb.mapviewer.model.Project; import lcsb.mapviewer.model.cache.FileEntry; import lcsb.mapviewer.model.cache.UploadedFileEntry; @@ -59,6 +63,7 @@ import lcsb.mapviewer.model.user.User; import lcsb.mapviewer.services.SecurityException; import lcsb.mapviewer.services.interfaces.ILayoutService; import lcsb.mapviewer.services.utils.ColorSchemaReader; +import lcsb.mapviewer.services.utils.CreateProjectParams; import lcsb.mapviewer.services.utils.data.BuildInLayout; import lcsb.mapviewer.services.utils.gmap.CoordinationConverter; import lcsb.mapviewer.services.view.AuthenticationToken; @@ -320,14 +325,7 @@ public class ProjectRestImpl extends BaseRestImpl { SubModelCommand subModelCommand = new SubModelCommand(originalModel, polygon); Model part = subModelCommand.execute(); - IConverter parser; - if (SbgnmlXmlConverter.class.getCanonicalName().equals(handlerClass)) { - parser = new SbgnmlXmlConverter(); - } else if (CellDesignerXmlParser.class.getCanonicalName().equals(handlerClass)) { - parser = new CellDesignerXmlParser(); - } else { - throw new QueryException("Unknown handlerClass: " + handlerClass); - } + IConverter parser = getModelParser(handlerClass); InputStream is = parser.exportModelToInputStream(part); String fileExtension = parser.getFileExtension(); @@ -467,4 +465,83 @@ public class ProjectRestImpl extends BaseRestImpl { } } + public ProjectMetaData addProject(String token, String projectId, MultiValueMap<String, String> data, String path) + throws SecurityException, QueryException, IOException { + AuthenticationToken authenticationToken = getUserService().getToken(token); + User user = getUserService().getUserByToken(authenticationToken); + Project project = getProjectService().getProjectByProjectId(projectId, authenticationToken); + if (project != null) { + throw new ObjectExistsException("Project with given id already exists"); + } + CreateProjectParams params = new CreateProjectParams(); + String directory = path + "/../map_images/" + md5(projectId) + "/"; + String fileContent = getFirstValue(data.get("file-content")); + if (fileContent == null) { + throw new QueryException("file-content is obligatory"); + } + String parserClass = getFirstValue(data.get("parser")); + if (parserClass == null) { + throw new QueryException("parser is obligatory"); + } + IConverter parser = getModelParser(parserClass); + + params.addUser(user.getLogin(), null); + params.async(true); + params.parser(parser); + params.authenticationToken(authenticationToken); + params.autoResize(getFirstValue(data.get("auto-resize"))); + params.cacheModel(getFirstValue(data.get("cache"))); + params.description(getFirstValue(data.get("description"))); + params.images(true); + params.notifyEmail(getFirstValue(data.get("notify-email"))); + params.projectDir(directory); + params.projectDisease(getFirstValue(data.get("disease"))); + params.projectFile(new ByteArrayInputStream(fileContent.getBytes(StandardCharsets.UTF_8))); + params.projectId(projectId); + params.projectName(getFirstValue(data.get("name"))); + params.projectOrganism(getFirstValue(data.get("organism"))); + params.sbgnFormat(getFirstValue(data.get("sbgn"))); + params.semanticZoom(getFirstValue(data.get("semantic-zoom"))); + params.version(getFirstValue(data.get("version"))); + + getProjectService().createProject(params); + return getProject(projectId, token); + } + + private String getFirstValue(List<String> list) { + if (list == null) { + return null; + } + if (list.size() > 0) { + return list.get(0); + } + return null; + } + + /** + * Method that computes md5 hash for a given {@link String}. + * + * @param data + * input string + * @return md5 hash for input string + */ + private String md5(String data) { + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] mdbytes = md.digest(data.getBytes()); + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < mdbytes.length; i++) { + // CHECKSTYLE:OFF + // this magic formula transforms int into hex value + sb.append(Integer.toString((mdbytes[i] & 0xff) + 0x100, 16).substring(1)); + // CHECKSTYLE:ON + } + return sb.toString(); + } catch (NoSuchAlgorithmException e) { + logger.fatal("Problem with instance of MD5 encoder", e); + } + + return null; + } + } diff --git a/rest-api/src/test/java/lcsb/mapviewer/api/BaseControllerTest.java b/rest-api/src/test/java/lcsb/mapviewer/api/BaseControllerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5c708c8888278669b95e7b7ed8bd1e24f76c6d6f --- /dev/null +++ b/rest-api/src/test/java/lcsb/mapviewer/api/BaseControllerTest.java @@ -0,0 +1,59 @@ +package lcsb.mapviewer.api; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.CALLS_REAL_METHODS; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +public class BaseControllerTest { + + @AfterClass + public static void tearDownAfterClass() throws Exception { + } + + @Before + public void setUp() throws Exception { + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void testParsePostBody() { + BaseController controller = Mockito.mock(BaseController.class, CALLS_REAL_METHODS); + Map<String, String> res = controller.parsePostBody("x=y&"); + assertEquals("y", res.get("x")); + } + + @Test + public void testParsePostBody2() { + BaseController controller = Mockito.mock(BaseController.class, CALLS_REAL_METHODS); + Map<String, String> res = controller.parsePostBody("x="); + assertEquals("", res.get("x")); + } + + @Test + public void testParsePostBody3() { + BaseController controller = Mockito.mock(BaseController.class, CALLS_REAL_METHODS); + Map<String, String> res = controller.parsePostBody("x"); + assertTrue(res.keySet().contains("x")); + assertEquals(null, res.get("x")); + } + @Test + public void testParsePostBody4() { + BaseController controller = Mockito.mock(BaseController.class, CALLS_REAL_METHODS); + Map<String, String> res = controller.parsePostBody("a=b&c=test%20string%20%3D%20xyz"); + assertEquals("b", res.get("a")); + assertEquals("test string = xyz", res.get("c")); + } + + +} diff --git a/service/src/main/java/lcsb/mapviewer/services/impl/ProjectService.java b/service/src/main/java/lcsb/mapviewer/services/impl/ProjectService.java index 9739b285c9e7e2b647ab03b5b3f403f906af63d5..a15818fb09a7252ca59c83b1d2199ba296bf3d28 100644 --- a/service/src/main/java/lcsb/mapviewer/services/impl/ProjectService.java +++ b/service/src/main/java/lcsb/mapviewer/services/impl/ProjectService.java @@ -87,6 +87,7 @@ import lcsb.mapviewer.persist.dao.ProjectDao; import lcsb.mapviewer.persist.dao.map.CommentDao; import lcsb.mapviewer.persist.dao.map.ModelDao; import lcsb.mapviewer.persist.dao.user.UserDao; +import lcsb.mapviewer.services.SecurityException; import lcsb.mapviewer.services.UserAccessException; import lcsb.mapviewer.services.interfaces.ICommentService; import lcsb.mapviewer.services.interfaces.IConfigurationService; @@ -686,7 +687,11 @@ public class ProjectService implements IProjectService { if (params.isComplex()) { try { - ComplexZipConverter parser = new ComplexZipConverter(CellDesignerXmlParser.class); + Class<? extends IConverter> clazz = CellDesignerXmlParser.class; + if (params.getParser() != null) { + clazz = params.getParser().getClass(); + } + ComplexZipConverter parser = new ComplexZipConverter(clazz); ComplexZipConverterParams complexParams; complexParams = new ComplexZipConverterParams().zipFile(params.getProjectFile()); complexParams.visualizationDir(params.getProjectDir()); @@ -700,7 +705,9 @@ public class ProjectService implements IProjectService { } } else { IConverter parser; - if (params.getProjectFile().endsWith("sbgn")) { + if (params.getParser() != null) { + parser = params.getParser(); + } else if (params.getProjectFile().endsWith("sbgn")) { parser = new SbgnmlXmlConverter(); } else { parser = new CellDesignerXmlParser(); @@ -902,7 +909,10 @@ public class ProjectService implements IProjectService { } @Override - public void createProject(final CreateProjectParams params) { + public void createProject(final CreateProjectParams params) throws SecurityException { + if (!userService.userHasPrivilege(params.getAuthenticationToken(), PrivilegeType.ADD_MAP)) { + throw new SecurityException("Adding projects not allowed."); + } // this count down is used to wait for asynchronous thread to initialize // data in the db (probably it would be better to move the initialization to diff --git a/service/src/main/java/lcsb/mapviewer/services/interfaces/IProjectService.java b/service/src/main/java/lcsb/mapviewer/services/interfaces/IProjectService.java index 36035412c901ef307dde799f32f2a241d1641237..509e0d421d9313bdfd161175e4a4d8beb17cd0d4 100644 --- a/service/src/main/java/lcsb/mapviewer/services/interfaces/IProjectService.java +++ b/service/src/main/java/lcsb/mapviewer/services/interfaces/IProjectService.java @@ -6,6 +6,7 @@ import org.primefaces.model.TreeNode; import lcsb.mapviewer.model.Project; import lcsb.mapviewer.model.user.User; +import lcsb.mapviewer.services.SecurityException; import lcsb.mapviewer.services.UserAccessException; import lcsb.mapviewer.services.utils.CreateProjectParams; import lcsb.mapviewer.services.view.AuthenticationToken; @@ -122,8 +123,9 @@ public interface IProjectService { * * @param params * information about project to create + * @throws SecurityException */ - void createProject(CreateProjectParams params); + void createProject(CreateProjectParams params) throws SecurityException; /** * Creates empty {@link ProjectView}. diff --git a/service/src/main/java/lcsb/mapviewer/services/utils/CreateProjectParams.java b/service/src/main/java/lcsb/mapviewer/services/utils/CreateProjectParams.java index 4e1f886cb2e823d2b72e509be84bcc940dcc3d0a..69b792a9bb8d1ed9d4a8e1641c76927df4afb7a6 100644 --- a/service/src/main/java/lcsb/mapviewer/services/utils/CreateProjectParams.java +++ b/service/src/main/java/lcsb/mapviewer/services/utils/CreateProjectParams.java @@ -16,6 +16,7 @@ import java.util.Set; import org.apache.commons.io.IOUtils; import org.primefaces.model.TreeNode; +import lcsb.mapviewer.converter.IConverter; import lcsb.mapviewer.converter.zip.ZipEntryFile; import lcsb.mapviewer.model.map.BioEntity; import lcsb.mapviewer.model.map.MiriamType; @@ -33,119 +34,121 @@ public class CreateProjectParams { /** * User defined project identifier. */ - private String projectId; + private String projectId; /** * Name of the project. */ - private String projectName; + private String projectName; /** * Disease associated to the project. */ - private String disease; + private String disease; /** * Organism associated to the project. */ - private String organism; + private String organism; /** * Path to a file used as a model. */ - private String projectFile; + private String projectFile; + + private IConverter parser; /** * Is the project a complex multi-file project. */ - private boolean complex; - private boolean semanticZoom; + private boolean complex; + private boolean semanticZoom; /** * List of zip entries in the complex model definition. */ - private List<ZipEntryFile> zipEntries = new ArrayList<ZipEntryFile>(); + private List<ZipEntryFile> zipEntries = new ArrayList<ZipEntryFile>(); /** * Should the elements be annotated by the external resources. */ - private boolean updateAnnotations = false; + private boolean updateAnnotations = false; /** * Do we want to generate images for all available layers. */ - private boolean images = false; + private boolean images = false; /** * Should the map be autoresized after processing (to trim unnecessary * margins). */ - private boolean autoResize = true; + private boolean autoResize = true; /** * Should the data from external resources but linked to model be cached. */ - private boolean cacheModel = true; + private boolean cacheModel = true; /** * Should the process of creation of the project be asynchronous (in separate * thread). */ - private boolean async = false; + private boolean async = false; /** * Should the map be displayed in SBGN format. */ - private boolean sbgnFormat = false; + private boolean sbgnFormat = false; /** * Is the {@link lcsb.mapviewer.services.utils.data.BuildInLayout#NORMAL} * default {@link lcsb.mapviewer.model.map.layout.Layout Layout} when * generating new project. */ - private boolean networkLayoutAsDefault = false; + private boolean networkLayoutAsDefault = false; /** * Do we want to analyze annotations after processing of the map. If yes then * in the email with summary of the generation process information about * improper annotations will be sent. */ - private boolean analyzeAnnotations = false; + private boolean analyzeAnnotations = false; /** * Email address that should be notified after everything is done. */ - private String notifyEmail = null; + private String notifyEmail = null; /** * Description of the project. */ - private String description = ""; + private String description = ""; /** * Version of the map. */ - private String version = "0"; + private String version = "0"; /** * Users that should have access to the map. */ - private List<String[]> users = new ArrayList<String[]>(); + private List<String[]> users = new ArrayList<String[]>(); /** * Directory with the static images that will be stored on server. This * directory is relative and it's a simple uniqe name within folder with * images. */ - private String projectDir; + private String projectDir; - private AuthenticationToken authenticationToken; + private AuthenticationToken authenticationToken; /** * Map that contains informnation what kind of annotators should be used for * specific class. */ - private Map<Class<?>, List<String>> annotatorsMap = null; + private Map<Class<?>, List<String>> annotatorsMap = null; /** * Map that contains information which {@link MiriamType miriam types} are @@ -169,6 +172,14 @@ public class CreateProjectParams { this.projectId = projectId; return this; } + + public CreateProjectParams parser(IConverter parser) { + this.parser = parser; + return this; + } + + + /** * @param notifyEmail @@ -203,6 +214,10 @@ public class CreateProjectParams { return this; } + public CreateProjectParams autoResize(String value) { + return this.autoResize("true".equalsIgnoreCase(value)); + } + /** * Sets input stream from which projects should be generated. * @@ -420,6 +435,10 @@ public class CreateProjectParams { return this; } + public CreateProjectParams cacheModel(String value) { + return this.cacheModel("true".equalsIgnoreCase(value)); + } + /** * @return the submodels * @see #zipEntries @@ -462,8 +481,13 @@ public class CreateProjectParams { * the sbgnFormat to set * @see #sbgnFormat */ - public void sbgnFormat(boolean sbgnFormat) { + public CreateProjectParams sbgnFormat(boolean sbgnFormat) { this.sbgnFormat = sbgnFormat; + return this; + } + + public CreateProjectParams sbgnFormat(String value) { + return this.sbgnFormat("true".equalsIgnoreCase(value)); } /** @@ -728,8 +752,16 @@ public class CreateProjectParams { return this; } + public CreateProjectParams semanticZoom(String value) { + return this.semanticZoom("true".equals(value)); + } + public boolean isSemanticZoom() { return this.semanticZoom; } + public IConverter getParser() { + return this.parser ; + } + }